diff --git a/nbt/encode.go b/nbt/encode.go index cf1e132..dfd097c 100644 --- a/nbt/encode.go +++ b/nbt/encode.go @@ -197,6 +197,10 @@ func (e *Encoder) writeValue(val reflect.Value, tagType byte) error { } tagProps := parseTag(f, v, tag) + if tagProps.OmitEmpty && isEmptyValue(v) { + continue + } + if err := e.marshal(val.Field(i), tagProps.Type, tagProps.Name); err != nil { return err } @@ -318,11 +322,17 @@ func getTagTypeByType(vk reflect.Type) byte { } type tagProps struct { - Name string - Type byte + Name string + Type byte + OmitEmpty bool } func parseTag(f reflect.StructField, v reflect.Value, tagName string) (result tagProps) { + if strings.HasSuffix(tagName, ",omitempty") { + result.OmitEmpty = true + tagName = tagName[:len(tagName)-10] + } + if tagName != "" { result.Name = tagName } else { @@ -381,3 +391,22 @@ func (e *Encoder) writeInt64(n int64) error { byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n)}) return err } + +// Copied from encoding/json/encode.go +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Pointer: + return v.IsNil() + } + return false +} diff --git a/nbt/encode_test.go b/nbt/encode_test.go index f634663..a1b7ea7 100644 --- a/nbt/encode_test.go +++ b/nbt/encode_test.go @@ -273,3 +273,51 @@ func TestEncoder_Encode_textMarshaler(t *testing.T) { t.Errorf("get %v, want %v", data, wants) } } + +func TestEncoder_Encode_omitempty(t *testing.T) { + type Struct struct { + S string `nbt:"test,omitempty"` + B []byte `nbt:",omitempty"` + I int32 `nbt:",omitempty"` + } + + tests := []struct { + name string + args any + want []byte + }{ + { + name: "Empty struct", + args: Struct{}, + want: []byte{ + TagCompound, 0x00, 0x00, + TagEnd, + }, + }, { + name: "Non-empty struct", + args: Struct{ + S: "ab", + B: []byte{4, 5}, + I: 9, + }, + want: []byte{ + TagCompound, 0x00, 0x00, + TagString, 0x00, 4, 't', 'e', 's', 't', 0, 2, 'a', 'b', + TagByteArray, 0x00, 1, 'B', 0x00, 0x00, 0, 2, 4, 5, + TagInt, 0x00, 1, 'I', 0x00, 0x00, 0x00, 0x09, + TagEnd, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data, err := Marshal(tt.args) + if err != nil { + t.Error(err) + } else if !bytes.Equal(data, tt.want) { + t.Errorf("Marshal([]struct{}) got = % 02x, want % 02x", data, tt.want) + return + } + }) + } +}