diff --git a/cover_marshal_json_test.go b/cover_marshal_json_test.go index 660ed1a..ca569a3 100644 --- a/cover_marshal_json_test.go +++ b/cover_marshal_json_test.go @@ -609,123 +609,228 @@ func TestCoverMarshalJSON(t *testing.T) { }{A: nil, B: nil, C: nil}, }, + // PtrHeadMarshalJSONZeroMultiFields + { + name: "PtrHeadMarshalJSONZeroMultiFields", + data: &struct { + A coverMarshalJSON `json:"a"` + B coverMarshalJSON `json:"b"` + }{}, + }, + { + name: "PtrHeadMarshalJSONZeroMultiFieldsOmitEmpty", + data: &struct { + A coverMarshalJSON `json:"a,omitempty"` + B coverMarshalJSON `json:"b,omitempty"` + }{}, + }, + { + name: "PtrHeadMarshalJSONZeroMultiFieldsString", + data: &struct { + A coverMarshalJSON `json:"a,string"` + B coverMarshalJSON `json:"b,string"` + }{}, + }, + { + name: "PtrHeadPtrMarshalJSONZeroMultiFields", + data: &struct { + A coverPtrMarshalJSON `json:"a"` + B coverPtrMarshalJSON `json:"b"` + }{}, + }, + { + name: "PtrHeadPtrMarshalJSONZeroMultiFieldsOmitEmpty", + data: &struct { + A coverPtrMarshalJSON `json:"a,omitempty"` + B coverPtrMarshalJSON `json:"b,omitempty"` + }{}, + }, + { + name: "PtrHeadPtrMarshalJSONZeroMultiFieldsString", + data: &struct { + A coverPtrMarshalJSON `json:"a,string"` + B coverPtrMarshalJSON `json:"b,string"` + }{}, + }, + + // PtrHeadMarshalJSONMultiFields + { + name: "PtrHeadMarshalJSONMultiFields", + data: &struct { + A coverMarshalJSON `json:"a"` + B coverMarshalJSON `json:"b"` + }{A: coverMarshalJSON{}, B: coverMarshalJSON{}}, + }, + { + name: "PtrHeadMarshalJSONMultiFieldsOmitEmpty", + data: &struct { + A coverMarshalJSON `json:"a,omitempty"` + B coverMarshalJSON `json:"b,omitempty"` + }{A: coverMarshalJSON{}, B: coverMarshalJSON{}}, + }, + { + name: "PtrHeadMarshalJSONMultiFieldsString", + data: &struct { + A coverMarshalJSON `json:"a,string"` + B coverMarshalJSON `json:"b,string"` + }{A: coverMarshalJSON{}, B: coverMarshalJSON{}}, + }, + { + name: "PtrHeadPtrMarshalJSONMultiFields", + data: &struct { + A coverPtrMarshalJSON `json:"a"` + B coverPtrMarshalJSON `json:"b"` + }{A: coverPtrMarshalJSON{}, B: coverPtrMarshalJSON{}}, + }, + { + name: "PtrHeadPtrMarshalJSONMultiFieldsOmitEmpty", + data: &struct { + A coverPtrMarshalJSON `json:"a,omitempty"` + B coverPtrMarshalJSON `json:"b,omitempty"` + }{A: coverPtrMarshalJSON{}, B: coverPtrMarshalJSON{}}, + }, + { + name: "PtrHeadPtrMarshalJSONMultiFieldsString", + data: &struct { + A coverPtrMarshalJSON `json:"a,string"` + B coverPtrMarshalJSON `json:"b,string"` + }{A: coverPtrMarshalJSON{}, B: coverPtrMarshalJSON{}}, + }, + + // PtrHeadMarshalJSONPtrMultiFields + { + name: "PtrHeadMarshalJSONPtrMultiFields", + data: &struct { + A *coverMarshalJSON `json:"a"` + B *coverMarshalJSON `json:"b"` + }{A: &coverMarshalJSON{}, B: &coverMarshalJSON{}}, + }, + { + name: "PtrHeadMarshalJSONPtrMultiFieldsOmitEmpty", + data: &struct { + A *coverMarshalJSON `json:"a,omitempty"` + B *coverMarshalJSON `json:"b,omitempty"` + }{A: &coverMarshalJSON{}, B: &coverMarshalJSON{}}, + }, + { + name: "PtrHeadMarshalJSONPtrMultiFieldsString", + data: &struct { + A *coverMarshalJSON `json:"a,string"` + B *coverMarshalJSON `json:"b,string"` + }{A: &coverMarshalJSON{}, B: &coverMarshalJSON{}}, + }, + { + name: "PtrHeadPtrMarshalJSONPtrMultiFields", + data: &struct { + A *coverPtrMarshalJSON `json:"a"` + B *coverPtrMarshalJSON `json:"b"` + }{A: &coverPtrMarshalJSON{}, B: &coverPtrMarshalJSON{}}, + }, + { + name: "PtrHeadPtrMarshalJSONPtrMultiFieldsOmitEmpty", + data: &struct { + A *coverPtrMarshalJSON `json:"a,omitempty"` + B *coverPtrMarshalJSON `json:"b,omitempty"` + }{A: &coverPtrMarshalJSON{}, B: &coverPtrMarshalJSON{}}, + }, + { + name: "PtrHeadPtrMarshalJSONPtrMultiFieldsString", + data: &struct { + A *coverPtrMarshalJSON `json:"a,string"` + B *coverPtrMarshalJSON `json:"b,string"` + }{A: &coverPtrMarshalJSON{}, B: &coverPtrMarshalJSON{}}, + }, + + // PtrHeadMarshalJSONPtrNilMultiFields + { + name: "PtrHeadMarshalJSONPtrNilMultiFields", + data: &struct { + A *coverMarshalJSON `json:"a"` + B *coverMarshalJSON `json:"b"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadMarshalJSONPtrNilMultiFieldsOmitEmpty", + data: &struct { + A *coverMarshalJSON `json:"a,omitempty"` + B *coverMarshalJSON `json:"b,omitempty"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadMarshalJSONPtrNilMultiFieldsString", + data: &struct { + A *coverMarshalJSON `json:"a,string"` + B *coverMarshalJSON `json:"b,string"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadPtrMarshalJSONPtrNilMultiFields", + data: &struct { + A *coverPtrMarshalJSON `json:"a"` + B *coverPtrMarshalJSON `json:"b"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadPtrMarshalJSONPtrNilMultiFieldsOmitEmpty", + data: &struct { + A *coverPtrMarshalJSON `json:"a,omitempty"` + B *coverPtrMarshalJSON `json:"b,omitempty"` + }{A: nil, B: nil}, + }, + { + name: "PtrHeadPtrMarshalJSONPtrNilMultiFieldsString", + data: &struct { + A *coverPtrMarshalJSON `json:"a,string"` + B *coverPtrMarshalJSON `json:"b,string"` + }{A: nil, B: nil}, + }, + + // PtrHeadMarshalJSONNilMultiFields + { + name: "PtrHeadMarshalJSONNilMultiFields", + data: (*struct { + A coverMarshalJSON `json:"a"` + B coverMarshalJSON `json:"b"` + })(nil), + }, + { + name: "PtrHeadMarshalJSONNilMultiFieldsOmitEmpty", + data: (*struct { + A coverMarshalJSON `json:"a,omitempty"` + B coverMarshalJSON `json:"b,omitempty"` + })(nil), + }, + { + name: "PtrHeadMarshalJSONNilMultiFieldsString", + data: (*struct { + A coverMarshalJSON `json:"a,string"` + B coverMarshalJSON `json:"b,string"` + })(nil), + }, + { + name: "PtrHeadPtrMarshalJSONNilMultiFields", + data: (*struct { + A coverPtrMarshalJSON `json:"a"` + B coverPtrMarshalJSON `json:"b"` + })(nil), + }, + { + name: "PtrHeadPtrMarshalJSONNilMultiFieldsOmitEmpty", + data: (*struct { + A coverPtrMarshalJSON `json:"a,omitempty"` + B coverPtrMarshalJSON `json:"b,omitempty"` + })(nil), + }, + { + name: "PtrHeadPtrMarshalJSONNilMultiFieldsString", + data: (*struct { + A coverPtrMarshalJSON `json:"a,string"` + B coverPtrMarshalJSON `json:"b,string"` + })(nil), + }, + /* - // PtrHeadMarshalJSONZeroMultiFields - { - name: "PtrHeadMarshalJSONZeroMultiFields", - data: &struct { - A [2]int `json:"a"` - B [2]int `json:"b"` - }{}, - }, - { - name: "PtrHeadMarshalJSONZeroMultiFieldsOmitEmpty", - data: &struct { - A [2]int `json:"a,omitempty"` - B [2]int `json:"b,omitempty"` - }{}, - }, - { - name: "PtrHeadMarshalJSONZeroMultiFieldsString", - data: &struct { - A [2]int `json:"a,string"` - B [2]int `json:"b,string"` - }{}, - }, - - // PtrHeadMarshalJSONMultiFields - { - name: "PtrHeadMarshalJSONMultiFields", - data: &struct { - A [2]int `json:"a"` - B [2]int `json:"b"` - }{A: [2]int{-1}, B: [2]int{1}}, - }, - { - name: "PtrHeadMarshalJSONMultiFieldsOmitEmpty", - data: &struct { - A [2]int `json:"a,omitempty"` - B [2]int `json:"b,omitempty"` - }{A: [2]int{-1}, B: [2]int{1}}, - }, - { - name: "PtrHeadMarshalJSONMultiFieldsString", - data: &struct { - A [2]int `json:"a,string"` - B [2]int `json:"b,string"` - }{A: [2]int{-1}, B: [2]int{1}}, - }, - - // PtrHeadMarshalJSONPtrMultiFields - { - name: "PtrHeadMarshalJSONPtrMultiFields", - data: &struct { - A *[2]int `json:"a"` - B *[2]int `json:"b"` - }{A: arrayptr([2]int{-1}), B: arrayptr([2]int{-2})}, - }, - { - name: "PtrHeadMarshalJSONPtrMultiFieldsOmitEmpty", - data: &struct { - A *[2]int `json:"a,omitempty"` - B *[2]int `json:"b,omitempty"` - }{A: arrayptr([2]int{-1}), B: arrayptr([2]int{-2})}, - }, - { - name: "PtrHeadMarshalJSONPtrMultiFieldsString", - data: &struct { - A *[2]int `json:"a,string"` - B *[2]int `json:"b,string"` - }{A: arrayptr([2]int{-1}), B: arrayptr([2]int{-2})}, - }, - - // PtrHeadMarshalJSONPtrNilMultiFields - { - name: "PtrHeadMarshalJSONPtrNilMultiFields", - data: &struct { - A *[2]int `json:"a"` - B *[2]int `json:"b"` - }{A: nil, B: nil}, - }, - { - name: "PtrHeadMarshalJSONPtrNilMultiFieldsOmitEmpty", - data: &struct { - A *[2]int `json:"a,omitempty"` - B *[2]int `json:"b,omitempty"` - }{A: nil, B: nil}, - }, - { - name: "PtrHeadMarshalJSONPtrNilMultiFieldsString", - data: &struct { - A *[2]int `json:"a,string"` - B *[2]int `json:"b,string"` - }{A: nil, B: nil}, - }, - - // PtrHeadMarshalJSONNilMultiFields - { - name: "PtrHeadMarshalJSONNilMultiFields", - data: (*struct { - A [2]int `json:"a"` - B [2]int `json:"b"` - })(nil), - }, - { - name: "PtrHeadMarshalJSONNilMultiFieldsOmitEmpty", - data: (*struct { - A [2]int `json:"a,omitempty"` - B [2]int `json:"b,omitempty"` - })(nil), - }, - { - name: "PtrHeadMarshalJSONNilMultiFieldsString", - data: (*struct { - A [2]int `json:"a,string"` - B [2]int `json:"b,string"` - })(nil), - }, - // PtrHeadMarshalJSONNilMultiFields { name: "PtrHeadMarshalJSONNilMultiFields", diff --git a/encode_compile.go b/encode_compile.go index 285f258..1de2ca5 100644 --- a/encode_compile.go +++ b/encode_compile.go @@ -1231,6 +1231,7 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) fieldNum := typ.NumField() indirect := ifaceIndir(typ) fieldIdx := 0 + disableIndirectConversion := false var ( head *opcode code *opcode @@ -1263,6 +1264,7 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) fieldOpcodeIndex := ctx.opcodeIndex fieldPtrIndex := ctx.ptrIndex ctx.incIndex() + nilcheck := indirect var valueCode *opcode if i == 0 && fieldNum == 1 && isPtr && rtype_ptrTo(fieldType).Implements(marshalJSONType) && !fieldType.Implements(marshalJSONType) { // *struct{ field implementedMarshalJSONType } => struct { field *implementedMarshalJSONType } @@ -1273,8 +1275,17 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) return nil, err } valueCode = code + nilcheck = false indirect = false - isPtr = false + disableIndirectConversion = true + } else if isPtr && fieldNum > 1 && fieldType.Kind() != reflect.Ptr && !fieldType.Implements(marshalJSONType) && rtype_ptrTo(fieldType).Implements(marshalJSONType) { + ctx.typ = rtype_ptrTo(fieldType) + code, err := encodeCompileMarshalJSON(ctx) + if err != nil { + return nil, err + } + nilcheck = false + valueCode = code } else { code, err := encodeCompile(ctx.withType(fieldType), i == 0 && isPtr) if err != nil { @@ -1301,6 +1312,7 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) key := fmt.Sprintf(`"%s":`, tag.key) escapedKey := fmt.Sprintf(`%s:`, string(encodeEscapedString([]byte{}, tag.key))) valueCode.indirect = indirect + valueCode.nilcheck = nilcheck fieldCode := &opcode{ typ: valueCode.typ, displayIdx: fieldOpcodeIndex, @@ -1314,6 +1326,7 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) displayKey: tag.key, offset: field.Offset, indirect: indirect, + nilcheck: nilcheck, } if fieldIdx == 0 { fieldCode.headIdx = fieldCode.idx @@ -1373,8 +1386,8 @@ func encodeCompileStruct(ctx *encodeCompileContext, isPtr bool) (*opcode, error) delete(ctx.structTypeToCompiledCode, typeptr) - if !code.indirect && isPtr { - code.indirect = true + if !disableIndirectConversion && !head.indirect && isPtr { + head.indirect = true } return ret, nil diff --git a/encode_opcode.go b/encode_opcode.go index cfb98d5..eb98ffe 100644 --- a/encode_opcode.go +++ b/encode_opcode.go @@ -20,6 +20,7 @@ type opcode struct { anonymousKey bool // whether anonymous key root bool // whether root indirect bool // whether indirect or not + nilcheck bool // whether needs to nilcheck or not rshiftNum uint8 // use to take bit for judging whether negative integer or not mask uint64 // mask for number indent int // indent number @@ -93,6 +94,7 @@ func (c *opcode) copy(codeMap map[uintptr]*opcode) *opcode { anonymousKey: c.anonymousKey, root: c.root, indirect: c.indirect, + nilcheck: c.nilcheck, indent: c.indent, idx: c.idx, headIdx: c.headIdx, diff --git a/encode_vm.go b/encode_vm.go index cbfadad..0643526 100644 --- a/encode_vm.go +++ b/encode_vm.go @@ -3234,7 +3234,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco } b = append(b, '{') b = append(b, code.key...) - if p == 0 || code.indirect && code.typ.Kind() == reflect.Ptr && ptrToPtr(p) == 0 { + if p == 0 || code.nilcheck && code.typ.Kind() == reflect.Ptr && ptrToPtr(p) == 0 { b = encodeNull(b) } else { bb, err := encodeMarshalJSON(b, ptrToInterface(code, p+code.offset)) @@ -3264,7 +3264,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco break } b = append(b, '{') - if p == 0 || code.indirect && code.typ.Kind() == reflect.Ptr && ptrToPtr(p) == 0 { + if p == 0 || code.nilcheck && code.typ.Kind() == reflect.Ptr && ptrToPtr(p) == 0 { code = code.nextField } else { b = append(b, code.key...) @@ -4093,7 +4093,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco p := load(ctxptr, code.headIdx) b = append(b, code.key...) p += code.offset - if code.typ.Kind() == reflect.Ptr && p != 0 && ptrToPtr(p) == 0 { + if code.nilcheck && code.typ.Kind() == reflect.Ptr && p != 0 && ptrToPtr(p) == 0 { b = encodeNull(b) } else { v := ptrToInterface(code, p) @@ -4108,7 +4108,7 @@ func encodeRun(ctx *encodeRuntimeContext, b []byte, codeSet *opcodeSet, opt Enco case opStructFieldOmitEmptyMarshalJSON: ptr := load(ctxptr, code.headIdx) p := ptr + code.offset - if code.typ.Kind() == reflect.Ptr && p != 0 && ptrToPtr(p) == 0 { + if code.nilcheck && code.typ.Kind() == reflect.Ptr && p != 0 && ptrToPtr(p) == 0 { code = code.nextField break }