From eee40c8a3c559565f07e89448478560a2c50f43c Mon Sep 17 00:00:00 2001 From: David It Date: Sun, 25 Aug 2024 17:18:15 -0400 Subject: [PATCH] schema: add encoder.SetOmitEmptyDefault() method to properly encode ptrs By default encoder encodes all nil ptr fields as "null" and decoder is not able to decode them. Encoding such fields as empty ("") also doesn't work properly as decoder always initializes ptr fields to zero values even empty value provided. The only option is to add omitempty tags. encoder.SetOmitEmptyDefault() allows to make omitempty to be a default behaviour. This closes issue #216 --- encoder.go | 19 +++++++++++++++---- encoder_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/encoder.go b/encoder.go index 52f2c10..74be9a6 100644 --- a/encoder.go +++ b/encoder.go @@ -11,8 +11,9 @@ type encoderFunc func(reflect.Value) string // Encoder encodes values from a struct into url.Values. type Encoder struct { - cache *cache - regenc map[reflect.Type]encoderFunc + cache *cache + regenc map[reflect.Type]encoderFunc + omitEmpty bool } // NewEncoder returns a new Encoder with defaults. @@ -40,6 +41,16 @@ func (e *Encoder) SetAliasTag(tag string) { e.cache.tag = tag } +// SetOmitEmptyDefault allows to set omit empty behaviour by default. +// When pointers to values are used in fields it is required to use omitempty to avoid +// encoding them as 'null' (which are not decodable by Decoder). This function allows +// to make omitempty behaviour to be a default, rather than requiring to specify +// per field tag. +// The default is false, i.e. one should specify 'omitempty' in tags as usual. +func (e *Encoder) SetOmitEmptyDefault(omit bool) { + e.omitEmpty = omit +} + // isValidStructPointer test if input value is a valid struct pointer. func isValidStructPointer(v reflect.Value) bool { return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct @@ -106,7 +117,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { // Encode non-slice types and custom implementations immediately. if encFunc != nil { value := encFunc(v.Field(i)) - if opts.Contains("omitempty") && isZero(v.Field(i)) { + if (e.omitEmpty || opts.Contains("omitempty")) && isZero(v.Field(i)) { continue } @@ -132,7 +143,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error { } // Encode a slice. - if v.Field(i).Len() == 0 && opts.Contains("omitempty") { + if v.Field(i).Len() == 0 && (e.omitEmpty || opts.Contains("omitempty")) { continue } diff --git a/encoder_test.go b/encoder_test.go index 092f0de..58924dc 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -523,3 +523,32 @@ func TestRegisterEncoderWithPtrType(t *testing.T) { valExists(t, "DateStart", ss.DateStart.time.String(), vals) valExists(t, "DateEnd", "", vals) } + +func TestPtrsWithDefaultOmitEmpty(t *testing.T) { + type S1 struct { + S *string `schema:"s"` + B *bool `schema:"b"` + } + + ss := S1{} // all fields are nil + + // default encoder encodes ptrs as "null", which are not decodable + // so one has to use omitempty tags + encoder := NewEncoder() + + vals := map[string][]string{} + err := encoder.Encode(ss, vals) + + noError(t, err) + valsLength(t, 2, vals) + valExists(t, "s", "null", vals) + valExists(t, "b", "null", vals) + + // however, if encoder.SetOmitEmptyDefault(true) is used such fields should be ignored + vals = map[string][]string{} + encoder.SetOmitEmptyDefault(true) + err = encoder.Encode(ss, vals) + + noError(t, err) + valsLength(t, 0, vals) +}