Browse Source

codec: support IsZero and reflect.Type.Comparable for efficient comparison of structs to zero value

With this, we now support using IsZero method on a type to determine if it's equal to the Zero value.
Also, if the type is comparable, we just compare it to the zero value directly.

Updates #224
Ugorji Nwoke 8 years ago
parent
commit
ae82d72256
3 changed files with 30 additions and 9 deletions
  1. 11 3
      codec/gen.go
  2. 9 2
      codec/helper.go
  3. 10 4
      codec/helper_internal.go

+ 11 - 3
codec/gen.go

@@ -863,11 +863,19 @@ func (x *genRunner) encZero(t reflect.Type) {
 func (x *genRunner) encOmitEmptyLine(t2 reflect.StructField, varname string, buf *genBuf) {
 	// smartly check omitEmpty on a struct type, as it may contain uncomparable map/slice/etc.
 	// also, for maps/slices/arrays, check if len ! 0 (not if == zero value)
+	varname2 := varname + "." + t2.Name
 	switch t2.Type.Kind() {
 	case reflect.Struct:
 		// fmt.Printf(">>>> structfield: omitempty: type: %s, field: %s\n", t2.Type.Name(), t2.Name)
+		if t2.Type.Comparable() {
+			buf.s(varname2).s(" != ").s(x.genZeroValueR(t2.Type))
+			break
+		}
+		if t2.Type.Implements(iszeroTyp) || reflect.PtrTo(t2.Type).Implements(iszeroTyp) {
+			buf.s("!(").s(varname2).s(".IsZero())")
+			break
+		}
 		buf.s("true ")
-		varname2 := varname + "." + t2.Name
 		for i, n := 0, t2.Type.NumField(); i < n; i++ {
 			f := t2.Type.Field(i)
 			if f.PkgPath != "" { // unexported
@@ -877,9 +885,9 @@ func (x *genRunner) encOmitEmptyLine(t2 reflect.StructField, varname string, buf
 			x.encOmitEmptyLine(f, varname2, buf)
 		}
 	case reflect.Map, reflect.Slice, reflect.Array, reflect.Chan:
-		buf.s("len(").s(varname).s(".").s(t2.Name).s(") != 0")
+		buf.s("len(").s(varname2).s(") != 0")
 	default:
-		buf.s(varname).s(".").s(t2.Name).s(" != ").s(x.genZeroValueR(t2.Type))
+		buf.s(varname2).s(" != ").s(x.genZeroValueR(t2.Type))
 	}
 }
 

+ 9 - 2
codec/helper.go

@@ -283,6 +283,10 @@ type jsonUnmarshaler interface {
 	UnmarshalJSON([]byte) error
 }
 
+type isZeroer interface {
+	IsZero() bool
+}
+
 // type byteAccepter func(byte) bool
 
 var (
@@ -313,6 +317,7 @@ var (
 	jsonUnmarshalerTyp = reflect.TypeOf((*jsonUnmarshaler)(nil)).Elem()
 
 	selferTyp = reflect.TypeOf((*Selfer)(nil)).Elem()
+	iszeroTyp = reflect.TypeOf((*isZeroer)(nil)).Elem()
 
 	uint8TypId      = rt2id(uint8Typ)
 	uint8SliceTypId = rt2id(uint8SliceTyp)
@@ -945,7 +950,8 @@ type typeInfo struct {
 
 	numMeth uint16 // number of methods
 
-	anyOmitEmpty bool
+	comparable   bool // true if a struct, and is comparable
+	anyOmitEmpty bool // true if a struct, and any of the fields are tagged "omitempty"
 
 	mbs bool // base type (T or *T) is a MapBySlice
 
@@ -967,7 +973,7 @@ type typeInfo struct {
 	csp bool // *T is a Selfer
 
 	toArray bool      // whether this (struct) type should be encoded as an array
-	keyType valueType // what type of key: default is string
+	keyType valueType // if struct, how is the field name stored in a stream? default is string
 }
 
 // define length beyond which we do a binary search instead of a linear search.
@@ -1080,6 +1086,7 @@ func (x *TypeInfos) get(rtid uintptr, rt reflect.Type) (pti *typeInfo) {
 	ti := typeInfo{rt: rt, rtid: rtid}
 	// ti.rv0 = reflect.Zero(rt)
 
+	ti.comparable = rt.Comparable()
 	ti.numMeth = uint16(rt.NumMethod())
 
 	ti.bm, ti.bmp = implIntf(rt, binaryMarshalerTyp)

+ 10 - 4
codec/helper_internal.go

@@ -12,6 +12,13 @@ import (
 )
 
 func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
+	if !v.IsValid() {
+		return true
+	}
+	vt := v.Type()
+	if vt.Implements(iszeroTyp) {
+		return rv2i(v).(isZeroer).IsZero()
+	}
 	switch v.Kind() {
 	case reflect.Invalid:
 		return true
@@ -34,17 +41,16 @@ func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
 		}
 		return v.IsNil()
 	case reflect.Struct:
-		// check for time.Time, and return true if IsZero
 		if rv2rtid(v) == timeTypId {
 			return rv2i(v).(time.Time).IsZero()
 		}
 		if !checkStruct {
 			return false
 		}
+		if vt.Comparable() {
+			return v.Interface() == reflect.Zero(vt).Interface()
+		}
 		// return true if all fields are empty. else return false.
-		// we cannot use equality check, because some fields may be maps/slices/etc
-		// and consequently the structs are not comparable.
-		// return v.Interface() == reflect.Zero(v.Type()).Interface()
 		for i, n := 0, v.NumField(); i < n; i++ {
 			if !hIsEmptyValue(v.Field(i), deref, checkStruct) {
 				return false