diff --git a/json/codec.go b/json/codec.go index 22d35fd..ca429de 100644 --- a/json/codec.go +++ b/json/codec.go @@ -677,7 +677,9 @@ func appendStructFields(fields []structField, t reflect.Type, offset uintptr, se if embfield.pointer { subfield.codec = constructEmbeddedStructPointerCodec(embfield.subtype.typ, embfield.unexported, subfield.offset, subfield.codec) + subfield.ptrOffset += subfield.offset subfield.offset = embfield.offset + subfield.ptr = true } else { subfield.offset += embfield.offset } @@ -931,7 +933,6 @@ type structType struct { fieldsIndex map[string]*structField ficaseIndex map[string]*structField typ reflect.Type - inlined bool } type structField struct { @@ -946,6 +947,8 @@ type structField struct { typ reflect.Type zero reflect.Value index int + ptr bool + ptrOffset uintptr } func unmarshalTypeError(b []byte, t reflect.Type) error { diff --git a/json/encode.go b/json/encode.go index 1d54297..f584a3e 100644 --- a/json/encode.go +++ b/json/encode.go @@ -767,8 +767,17 @@ func (e encoder) encodeStruct(b []byte, p unsafe.Pointer, st *structType) ([]byt f := &st.fields[i] v := unsafe.Pointer(uintptr(p) + f.offset) - if f.omitempty && f.empty(v) { - continue + if f.omitempty { + if f.ptr { + v2 := *(*unsafe.Pointer)(v) + v3 := unsafe.Pointer(uintptr(v2) + f.ptrOffset) + + if f.empty(v3) { + continue + } + } else if f.empty(v) { + continue + } } if escapeHTML { diff --git a/json/json_test.go b/json/json_test.go index 5f3f8a2..db1081e 100644 --- a/json/json_test.go +++ b/json/json_test.go @@ -1578,7 +1578,6 @@ func TestGithubIssue41(t *testing.T) { "expected: ", expectedString, ) } - } func TestGithubIssue44(t *testing.T) { @@ -1602,6 +1601,105 @@ func (r *rawJsonString) UnmarshalJSON(b []byte) error { return nil } +// See https://github.com/segmentio/encoding/issues/63 +// In short, embedding a struct pointer resulted in an incorrect memory address +// as we were still looking to the parent struct to start and only using offsets +// which resulted in the wrong values being extracted. +func TestGithubIssue63(t *testing.T) { + spec := []struct { + description string + input func() interface{} + expected string + }{ + { + description: "original", + expected: `{"my_field":"test","code":0}`, + input: func() interface{} { + type MyStruct struct { + MyField string `json:"my_field,omitempty"` + } + + type MyStruct2 struct { + *MyStruct + Code int `json:"code"` + } + + return MyStruct2{ + MyStruct: &MyStruct{ + MyField: "test", + }, + Code: 0, + } + }, + }, + { + description: "additional fields", + expected: `{"my_field":"test","my_other_field":"testing","code":1}`, + input: func() interface{} { + type MyStruct struct { + MyField string `json:"my_field,omitempty"` + MyOtherField string `json:"my_other_field"` + MyEmptyField string `json:"my_empty_field,omitempty"` + } + + type MyStruct2 struct { + *MyStruct + Code int `json:"code"` + } + + return MyStruct2{ + MyStruct: &MyStruct{ + MyField: "test", + MyOtherField: "testing", + }, + Code: 1, + } + }, + }, + { + description: "multiple embed levels", + expected: `{"my_field":"test","my_other_field":"testing","code":1}`, + input: func() interface{} { + type MyStruct struct { + MyField string `json:"my_field,omitempty"` + MyOtherField string `json:"my_other_field"` + MyEmptyField string `json:"my_empty_field,omitempty"` + } + + type MyStruct2 struct { + *MyStruct + Code int `json:"code"` + } + + type MyStruct3 struct { + *MyStruct2 + } + + return MyStruct3{ + MyStruct2: &MyStruct2{ + MyStruct: &MyStruct{ + MyField: "test", + MyOtherField: "testing", + }, + Code: 1, + }, + } + }, + }, + } + + for _, test := range spec { + t.Run(test.description, func(t *testing.T) { + if b, err := Marshal(test.input()); err != nil { + t.Error(err) + } else if string(b) != test.expected { + t.Errorf("got: %s", string(b)) + t.Errorf("expected: %s", test.expected) + } + }) + } +} + func TestSetTrustRawMessage(t *testing.T) { buf := &bytes.Buffer{} enc := NewEncoder(buf)