Prechádzať zdrojové kódy

codec: support recursive (deep) omitEmpty check

This supports omitEmpty checks where we introspect
the actual value behind a pointer or interface, and
also recursively check each field of a struct.

Introduce the RecursiveEmptyCheck flag on EncodeOptions,
to control the (potentially expensive) omitEmpty check
with recursive descent.

Fixes #163
Ugorji Nwoke 9 rokov pred
rodič
commit
74c6a81d3a
3 zmenil súbory, kde vykonal 12 pridanie a 13 odobranie
  1. 10 4
      codec/encode.go
  2. 0 7
      codec/helper.go
  3. 2 2
      codec/helper_internal.go

+ 10 - 4
codec/encode.go

@@ -119,6 +119,12 @@ type EncodeOptions struct {
 	// This is opt-in, as there may be a performance hit to checking circular references.
 	CheckCircularRef bool
 
+	// RecursiveEmptyCheck controls whether we descend into interfaces, structs and pointers
+	// when checking if a value is empty.
+	//
+	// Note that this may make OmitEmpty more expensive, as it incurs a lot more reflect calls.
+	RecursiveEmptyCheck bool
+
 	// AsSymbols defines what should be encoded as symbols.
 	//
 	// Encoding as symbols can reduce the encoded size significantly.
@@ -524,20 +530,20 @@ func (f *encFnInfo) kStruct(rv reflect.Value) {
 	}
 	newlen = 0
 	var kv stringRv
+	recur := e.h.RecursiveEmptyCheck
 	for _, si := range tisfi {
 		kv.r = si.field(rv, false)
 		if toMap {
-			if si.omitEmpty && isEmptyValue(kv.r) {
+			if si.omitEmpty && isEmptyValue(kv.r, recur, recur) {
 				continue
 			}
 			kv.v = si.encName
 		} else {
 			// use the zero value.
 			// if a reference or struct, set to nil (so you do not output too much)
-			if si.omitEmpty && isEmptyValue(kv.r) {
+			if si.omitEmpty && isEmptyValue(kv.r, recur, recur) {
 				switch kv.r.Kind() {
-				case reflect.Struct, reflect.Interface, reflect.Ptr, reflect.Array,
-					reflect.Map, reflect.Slice:
+				case reflect.Struct, reflect.Interface, reflect.Ptr, reflect.Array, reflect.Map, reflect.Slice:
 					kv.r = reflect.Value{} //encode as nil
 				}
 			}

+ 0 - 7
codec/helper.go

@@ -137,13 +137,6 @@ const (
 	// Note that this will always cause rpc tests to fail, since they need io.EOF sent via panic.
 	recoverPanicToErr = true
 
-	// if checkStructForEmptyValue, check structs fields to see if an empty value.
-	// This could be an expensive call, so possibly disable it.
-	checkStructForEmptyValue = false
-
-	// if derefForIsEmptyValue, deref pointers and interfaces when checking isEmptyValue
-	derefForIsEmptyValue = false
-
 	// if resetSliceElemToZeroValue, then on decoding a slice, reset the element to a zero value first.
 	// Only concern is that, if the slice already contained some garbage, we will decode into that garbage.
 	// The chances of this are slim, so leave this "optimization".

+ 2 - 2
codec/helper_internal.go

@@ -70,8 +70,8 @@ func hIsEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
 	return false
 }
 
-func isEmptyValue(v reflect.Value) bool {
-	return hIsEmptyValue(v, derefForIsEmptyValue, checkStructForEmptyValue)
+func isEmptyValue(v reflect.Value, deref, checkStruct bool) bool {
+	return hIsEmptyValue(v, deref, checkStruct)
 }
 
 func pruneSignExt(v []byte, pos bool) (n int) {