Skip to content

Commit ae82d72

Browse files
committed
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
1 parent 8badb25 commit ae82d72

File tree

3 files changed

+30
-9
lines changed

3 files changed

+30
-9
lines changed

codec/gen.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -863,11 +863,19 @@ func (x *genRunner) encZero(t reflect.Type) {
863863
func (x *genRunner) encOmitEmptyLine(t2 reflect.StructField, varname string, buf *genBuf) {
864864
// smartly check omitEmpty on a struct type, as it may contain uncomparable map/slice/etc.
865865
// also, for maps/slices/arrays, check if len ! 0 (not if == zero value)
866+
varname2 := varname + "." + t2.Name
866867
switch t2.Type.Kind() {
867868
case reflect.Struct:
868869
// fmt.Printf(">>>> structfield: omitempty: type: %s, field: %s\n", t2.Type.Name(), t2.Name)
870+
if t2.Type.Comparable() {
871+
buf.s(varname2).s(" != ").s(x.genZeroValueR(t2.Type))
872+
break
873+
}
874+
if t2.Type.Implements(iszeroTyp) || reflect.PtrTo(t2.Type).Implements(iszeroTyp) {
875+
buf.s("!(").s(varname2).s(".IsZero())")
876+
break
877+
}
869878
buf.s("true ")
870-
varname2 := varname + "." + t2.Name
871879
for i, n := 0, t2.Type.NumField(); i < n; i++ {
872880
f := t2.Type.Field(i)
873881
if f.PkgPath != "" { // unexported
@@ -877,9 +885,9 @@ func (x *genRunner) encOmitEmptyLine(t2 reflect.StructField, varname string, buf
877885
x.encOmitEmptyLine(f, varname2, buf)
878886
}
879887
case reflect.Map, reflect.Slice, reflect.Array, reflect.Chan:
880-
buf.s("len(").s(varname).s(".").s(t2.Name).s(") != 0")
888+
buf.s("len(").s(varname2).s(") != 0")
881889
default:
882-
buf.s(varname).s(".").s(t2.Name).s(" != ").s(x.genZeroValueR(t2.Type))
890+
buf.s(varname2).s(" != ").s(x.genZeroValueR(t2.Type))
883891
}
884892
}
885893

codec/helper.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ type jsonUnmarshaler interface {
283283
UnmarshalJSON([]byte) error
284284
}
285285

286+
type isZeroer interface {
287+
IsZero() bool
288+
}
289+
286290
// type byteAccepter func(byte) bool
287291

288292
var (
@@ -313,6 +317,7 @@ var (
313317
jsonUnmarshalerTyp = reflect.TypeOf((*jsonUnmarshaler)(nil)).Elem()
314318

315319
selferTyp = reflect.TypeOf((*Selfer)(nil)).Elem()
320+
iszeroTyp = reflect.TypeOf((*isZeroer)(nil)).Elem()
316321

317322
uint8TypId = rt2id(uint8Typ)
318323
uint8SliceTypId = rt2id(uint8SliceTyp)
@@ -945,7 +950,8 @@ type typeInfo struct {
945950

946951
numMeth uint16 // number of methods
947952

948-
anyOmitEmpty bool
953+
comparable bool // true if a struct, and is comparable
954+
anyOmitEmpty bool // true if a struct, and any of the fields are tagged "omitempty"
949955

950956
mbs bool // base type (T or *T) is a MapBySlice
951957

@@ -967,7 +973,7 @@ type typeInfo struct {
967973
csp bool // *T is a Selfer
968974

969975
toArray bool // whether this (struct) type should be encoded as an array
970-
keyType valueType // what type of key: default is string
976+
keyType valueType // if struct, how is the field name stored in a stream? default is string
971977
}
972978

973979
// 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) {
10801086
ti := typeInfo{rt: rt, rtid: rtid}
10811087
// ti.rv0 = reflect.Zero(rt)
10821088

1089+
ti.comparable = rt.Comparable()
10831090
ti.numMeth = uint16(rt.NumMethod())
10841091

10851092
ti.bm, ti.bmp = implIntf(rt, binaryMarshalerTyp)

codec/helper_internal.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ import (
1212
)
1313

1414
func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
15+
if !v.IsValid() {
16+
return true
17+
}
18+
vt := v.Type()
19+
if vt.Implements(iszeroTyp) {
20+
return rv2i(v).(isZeroer).IsZero()
21+
}
1522
switch v.Kind() {
1623
case reflect.Invalid:
1724
return true
@@ -34,17 +41,16 @@ func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
3441
}
3542
return v.IsNil()
3643
case reflect.Struct:
37-
// check for time.Time, and return true if IsZero
3844
if rv2rtid(v) == timeTypId {
3945
return rv2i(v).(time.Time).IsZero()
4046
}
4147
if !checkStruct {
4248
return false
4349
}
50+
if vt.Comparable() {
51+
return v.Interface() == reflect.Zero(vt).Interface()
52+
}
4453
// return true if all fields are empty. else return false.
45-
// we cannot use equality check, because some fields may be maps/slices/etc
46-
// and consequently the structs are not comparable.
47-
// return v.Interface() == reflect.Zero(v.Type()).Interface()
4854
for i, n := 0, v.NumField(); i < n; i++ {
4955
if !hIsEmptyValue(v.Field(i), deref, checkStruct) {
5056
return false

0 commit comments

Comments
 (0)