Skip to content

Commit

Permalink
Omit empty simple sturcts (#301)
Browse files Browse the repository at this point in the history
* Omit empty simple sturcts

Currently, we only omit default values for primitives, interfaces and,
pointers. This makes it difficult to determine if a primitive was
present in the unpacked message.

Whilst unpacking you can determine if a value is present by implementing
something similar to standard lib's database/sql.NullInt64 (or any of
the other Null* types), but then the "Valid" field will be included when
you marshal the struct.

To resovle this, we must check if the given struct is simple enough
(meaning it doesn't have any hidden fields) to compare to a
zero-initialized version of the struct. If it is simple enough, and the
provided struct is zero-initialized, and the struct is tagged with
"omitempty" we can exclude it from the resulting bytes.

* Move and rename omit-empty-zero-struct example

* Evaluate zeroness trough IsZero

Allows for more control over when a type ought to be seen as a ZeroValue
including for types that aren't a struct.

* Use type-assertion over reflection.Implements

Seems to work the same, requires one less var to live in the namespace,
and provides the isZeroer immediately instead of having to do the cast
after the Implements check.

Co-authored-by: Mathijs van den Worm <[email protected]>
  • Loading branch information
mthjs and Mathijs van den Worm authored Mar 18, 2021
1 parent 483293b commit 2d5c939
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
65 changes: 65 additions & 0 deletions msgpack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,68 @@ func TestSetSortMapKeys(t *testing.T) {
require.Equal(t, in, out)
}
}

type NullInt struct {
Valid bool
Int int
}

func (i *NullInt) Set(j int) {
i.Int = j
i.Valid = true
}

func (i NullInt) IsZero() bool {
return !i.Valid
}

func (i NullInt) MarshalMsgpack() ([]byte, error) {
return msgpack.Marshal(i.Int)
}

func (i *NullInt) UnmarshalMsgpack(b []byte) error {
if err := msgpack.Unmarshal(b, &i.Int); err != nil {
return err
}
i.Valid = true
return nil
}

type Secretive struct {
Visible bool
hidden bool
}

type T struct {
I NullInt `msgpack:",omitempty"`
J NullInt
// Secretive is not a "simple" struct because it has an hidden field.
S Secretive `msgpack:",omitempty"`
}

func ExampleMarshal_ignore_simple_zero_structs_when_tagged_with_omitempty() {
var t1 T
raw, err := msgpack.Marshal(t1)
if err != nil {
panic(err)
}
var t2 T
if err = msgpack.Unmarshal(raw, &t2); err != nil {
panic(err)
}
fmt.Printf("%#v\n", t2)

t2.I.Set(42)
t2.S.hidden = true // won't be included because it is a hidden field
raw, err = msgpack.Marshal(t2)
if err != nil {
panic(err)
}
var t3 T
if err = msgpack.Unmarshal(raw, &t3); err != nil {
panic(err)
}
fmt.Printf("%#v\n", t3)
// Output: msgpack_test.T{I:msgpack_test.NullInt{Valid:false, Int:0}, J:msgpack_test.NullInt{Valid:true, Int:0}, S:msgpack_test.Secretive{Visible:false, hidden:false}}
// msgpack_test.T{I:msgpack_test.NullInt{Valid:true, Int:42}, J:msgpack_test.NullInt{Valid:true, Int:0}, S:msgpack_test.Secretive{Visible:false, hidden:false}}
}
7 changes: 7 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,14 @@ func shouldInline(fs *fields, typ reflect.Type, f *field, tag string) bool {
return true
}

type isZeroer interface {
IsZero() bool
}

func isEmptyValue(v reflect.Value) bool {
if z, ok := v.Interface().(isZeroer); ok {
return z.IsZero()
}
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
Expand Down

0 comments on commit 2d5c939

Please sign in to comment.