From 1fef581454b48b5606c548f79ed8e323b84a3569 Mon Sep 17 00:00:00 2001 From: Ben Krieger Date: Thu, 24 Oct 2024 13:33:52 -0400 Subject: [PATCH] cbor: omit empty slices/maps/arrays with omitempty struct tag option Signed-off-by: Ben Krieger --- cbor/cbor.go | 10 +++++++++- cbor/cbor_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/cbor/cbor.go b/cbor/cbor.go index 697b654..f3cdb2a 100644 --- a/cbor/cbor.go +++ b/cbor/cbor.go @@ -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:]...) } } diff --git a/cbor/cbor_test.go b/cbor/cbor_test.go index 11d55c3..7def67b 100644 --- a/cbor/cbor_test.go +++ b/cbor/cbor_test.go @@ -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}