Skip to content

Commit

Permalink
cbor: omit empty slices/maps/arrays with omitempty struct tag option
Browse files Browse the repository at this point in the history
Signed-off-by: Ben Krieger <[email protected]>
  • Loading branch information
ben-krieger committed Oct 24, 2024
1 parent 9b17722 commit d0d03da
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 1 deletion.
10 changes: 9 additions & 1 deletion cbor/cbor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1281,13 +1281,21 @@ func (e *Encoder) encodeArray(size int, get func(int) reflect.Value) error {
return nil
}

func isEmpty(v reflect.Value) bool {
return v.IsZero() ||
(v.Kind() == reflect.Slice && v.Len() == 0) ||
(v.Kind() == reflect.Map && v.Len() == 0) ||
(v.Kind() == reflect.Array && v.Len() == 0) ||
(v.Kind() == reflect.Pointer && v.Elem().Kind() == reflect.Array && v.Len() == 0)
}

func (e *Encoder) encodeStruct(size int, get func([]int) reflect.Value, field func([]int) reflect.StructField) error {
// Get encoding order of fields
indices, omittable := fieldOrder(size, func(i int) reflect.StructField { return field([]int{i}) })

// Filter omittable fields which are the zero value for the associated type
for i, idx := range indices {
if omittable(idx) && get(idx).IsZero() {
if omittable(idx) && isEmpty(get(idx)) {
indices = append(indices[:i], indices[i+1:]...)
}
}
Expand Down
50 changes: 50 additions & 0 deletions cbor/cbor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2118,6 +2118,56 @@ func TestMarshalEmbeddedPointer(t *testing.T) {
})
}

func TestEmbeddedOmitEmptyTag(t *testing.T) {
type Embed struct {
Two []byte
Three []string `cbor:",omitempty"`
}
type Data struct {
One string
Embed
Four int
}
expectData := Data{
One: "hello",
Embed: Embed{
Two: []byte("world"),
},
Four: 42,
}
expectCBOR := []byte{0x83,
0x65, 0x68, 0x65, 0x6C, 0x6C, 0x6F,
0x45, 0x77, 0x6F, 0x72, 0x6C, 0x64,
0x18, 0x2A}

t.Run("marshal", func(t *testing.T) {
b, err := cbor.Marshal(Data{
One: "hello",
Embed: Embed{
Two: []byte("world"),
Three: []string{}, // Ensure that empty slices with omitempty are not omitted
},
Four: 42,
})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(b, expectCBOR) {
t.Fatalf("expected % x, got % x", expectCBOR, b)
}
})

t.Run("unmarshal", func(t *testing.T) {
var d Data
if err := cbor.Unmarshal(expectCBOR, &d); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(d, expectData) {
t.Fatalf("expected %+v, got %+v", expectData, d)
}
})
}

func TestOmitEmptyType(t *testing.T) {
t.Run("Marshal", func(t *testing.T) {
v := cbor.OmitEmpty[int]{Val: 0}
Expand Down

0 comments on commit d0d03da

Please sign in to comment.