From b57146602eb6de8c2cddcefadcc8b985ffec8bd2 Mon Sep 17 00:00:00 2001 From: IncSW Date: Sun, 11 Apr 2021 21:45:59 +0300 Subject: [PATCH] Fix encoding custom types zero values with omitempty when marshaller exists --- cover_bool_test.go | 181 ++++++++++++++++++++++++++++++ internal/encoder/encoder.go | 11 ++ internal/encoder/vm_escaped/vm.go | 7 +- 3 files changed, 198 insertions(+), 1 deletion(-) diff --git a/cover_bool_test.go b/cover_bool_test.go index 6dd6cc5..49415fa 100644 --- a/cover_bool_test.go +++ b/cover_bool_test.go @@ -2,11 +2,20 @@ package json_test import ( "bytes" + stdjson "encoding/json" "testing" "github.com/goccy/go-json" ) +type customBool bool + +type customBoolWithMarshaler bool + +func (b customBoolWithMarshaler) MarshalJSON() ([]byte, error) { + return stdjson.Marshal(bool(b)) +} + func TestCoverBool(t *testing.T) { type structBool struct { A bool `json:"a"` @@ -28,6 +37,14 @@ func TestCoverBool(t *testing.T) { A *bool `json:"a,string"` } + type structCustomBoolOmitEmpty struct { + A customBool `json:"a,omitempty"` + } + + type structCustomBoolWithMarshalerOmitEmpty struct { + A customBoolWithMarshaler `json:"a,omitempty"` + } + tests := []struct { name string data interface{} @@ -1400,6 +1417,46 @@ func TestCoverBool(t *testing.T) { B: false, }, }, + { + name: "AnonymousHeadCustomBoolOmitEmpty", + data: struct { + structCustomBoolOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolOmitEmpty: structCustomBoolOmitEmpty{A: true}, + B: false, + }, + }, + { + name: "AnonymousHeadCustomBoolOmitEmptyFalse", + data: struct { + structCustomBoolOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolOmitEmpty: structCustomBoolOmitEmpty{}, + B: false, + }, + }, + { + name: "AnonymousHeadCustomBoolWithMarshalerOmitEmpty", + data: struct { + structCustomBoolWithMarshalerOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolWithMarshalerOmitEmpty: structCustomBoolWithMarshalerOmitEmpty{A: true}, + B: false, + }, + }, + { + name: "AnonymousHeadCustomBoolWithMarshalerOmitEmptyFalse", + data: struct { + structCustomBoolWithMarshalerOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolWithMarshalerOmitEmpty: structCustomBoolWithMarshalerOmitEmpty{}, + B: false, + }, + }, { name: "AnonymousHeadBoolString", data: struct { @@ -1432,6 +1489,46 @@ func TestCoverBool(t *testing.T) { B: false, }, }, + { + name: "PtrAnonymousHeadCustomBoolOmitEmpty", + data: struct { + *structCustomBoolOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolOmitEmpty: &structCustomBoolOmitEmpty{A: true}, + B: false, + }, + }, + { + name: "PtrAnonymousHeadCustomBoolOmitEmptyFalse", + data: struct { + *structCustomBoolOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolOmitEmpty: &structCustomBoolOmitEmpty{}, + B: false, + }, + }, + { + name: "PtrAnonymousHeadCustomBoolWithMarshalerOmitEmpty", + data: struct { + *structCustomBoolWithMarshalerOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolWithMarshalerOmitEmpty: &structCustomBoolWithMarshalerOmitEmpty{A: true}, + B: false, + }, + }, + { + name: "PtrAnonymousHeadCustomBoolWithMarshalerOmitEmptyFalse", + data: struct { + *structCustomBoolWithMarshalerOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolWithMarshalerOmitEmpty: &structCustomBoolWithMarshalerOmitEmpty{}, + B: false, + }, + }, { name: "PtrAnonymousHeadBoolString", data: struct { @@ -1464,6 +1561,26 @@ func TestCoverBool(t *testing.T) { B: true, }, }, + { + name: "NilPtrAnonymousHeadCustomBoolOmitEmpty", + data: struct { + *structCustomBoolOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolOmitEmpty: nil, + B: true, + }, + }, + { + name: "NilPtrAnonymousHeadCustomBoolWithMarshalerOmitEmpty", + data: struct { + *structCustomBoolWithMarshalerOmitEmpty + B bool `json:"b,omitempty"` + }{ + structCustomBoolWithMarshalerOmitEmpty: nil, + B: true, + }, + }, { name: "NilPtrAnonymousHeadBoolString", data: struct { @@ -1620,6 +1737,30 @@ func TestCoverBool(t *testing.T) { structBoolOmitEmpty: structBoolOmitEmpty{A: true}, }, }, + { + name: "AnonymousHeadCustomBoolOnlyOmitEmpty", + data: struct { + structCustomBoolOmitEmpty + }{ + structCustomBoolOmitEmpty: structCustomBoolOmitEmpty{A: true}, + }, + }, + { + name: "AnonymousHeadCustomBoolWithMarshalerOnlyOmitEmpty", + data: struct { + structCustomBoolWithMarshalerOmitEmpty + }{ + structCustomBoolWithMarshalerOmitEmpty: structCustomBoolWithMarshalerOmitEmpty{A: true}, + }, + }, + { + name: "AnonymousHeadCustomBoolWithMarshalerOnlyOmitEmptyFalse", + data: struct { + structCustomBoolWithMarshalerOmitEmpty + }{ + structCustomBoolWithMarshalerOmitEmpty: structCustomBoolWithMarshalerOmitEmpty{}, + }, + }, { name: "AnonymousHeadBoolOnlyString", data: struct { @@ -1646,6 +1787,30 @@ func TestCoverBool(t *testing.T) { structBoolOmitEmpty: &structBoolOmitEmpty{A: true}, }, }, + { + name: "PtrAnonymousHeadCustomBoolOnlyOmitEmpty", + data: struct { + *structCustomBoolOmitEmpty + }{ + structCustomBoolOmitEmpty: &structCustomBoolOmitEmpty{A: true}, + }, + }, + { + name: "PtrAnonymousHeadCustomBoolWithMarshalerOnlyOmitEmpty", + data: struct { + *structCustomBoolWithMarshalerOmitEmpty + }{ + structCustomBoolWithMarshalerOmitEmpty: &structCustomBoolWithMarshalerOmitEmpty{A: true}, + }, + }, + { + name: "PtrAnonymousHeadCustomBoolWithMarshalerOnlyOmitEmptyFalse", + data: struct { + *structCustomBoolWithMarshalerOmitEmpty + }{ + structCustomBoolWithMarshalerOmitEmpty: &structCustomBoolWithMarshalerOmitEmpty{}, + }, + }, { name: "PtrAnonymousHeadBoolOnlyString", data: struct { @@ -1672,6 +1837,22 @@ func TestCoverBool(t *testing.T) { structBoolOmitEmpty: nil, }, }, + { + name: "NilPtrAnonymousHeadCustomBoolOnlyOmitEmpty", + data: struct { + *structCustomBoolOmitEmpty + }{ + structCustomBoolOmitEmpty: nil, + }, + }, + { + name: "NilPtrAnonymousHeadCustomBoolWithMarshalerOnlyOmitEmpty", + data: struct { + *structCustomBoolWithMarshalerOmitEmpty + }{ + structCustomBoolWithMarshalerOmitEmpty: nil, + }, + }, { name: "NilPtrAnonymousHeadBoolOnlyString", data: struct { diff --git a/internal/encoder/encoder.go b/internal/encoder/encoder.go index a7371ec..e0226d0 100644 --- a/internal/encoder/encoder.go +++ b/internal/encoder/encoder.go @@ -560,6 +560,17 @@ func AppendIndent(ctx *RuntimeContext, b []byte, indent int) []byte { func IsNilForMarshaler(v interface{}) bool { rv := reflect.ValueOf(v) switch rv.Kind() { + case reflect.Bool: + return !rv.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return rv.Uint() == 0 + case reflect.Float32, reflect.Float64: + return math.Float64bits(rv.Float()) == 0 + case reflect.Complex64, reflect.Complex128: + c := rv.Complex() + return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Func: return rv.IsNil() case reflect.Slice: diff --git a/internal/encoder/vm_escaped/vm.go b/internal/encoder/vm_escaped/vm.go index b0c608f..846119b 100644 --- a/internal/encoder/vm_escaped/vm.go +++ b/internal/encoder/vm_escaped/vm.go @@ -3646,8 +3646,13 @@ func Run(ctx *encoder.RuntimeContext, b []byte, codeSet *encoder.OpcodeSet, opt code = code.NextField break } + iface := ptrToInterface(code, p) + if code.Nilcheck && encoder.IsNilForMarshaler(iface) { + code = code.NextField + break + } b = append(b, code.EscapedKey...) - bb, err := appendMarshalJSON(code, b, ptrToInterface(code, p), true) + bb, err := appendMarshalJSON(code, b, iface, true) if err != nil { return nil, err }