mirror of
https://github.com/ii64/sonic.git
synced 2026-06-21 00:46:43 +08:00
fix: marshal struct field with omitempty tag as encoding/json (#114)
This commit is contained in:
parent
530d423243
commit
3eca433cb9
4 changed files with 162 additions and 180 deletions
|
|
@ -232,8 +232,6 @@ var _OpFuncTab = [256]func(*_Assembler, *_Instr) {
|
|||
_OP_is_zero_4 : (*_Assembler)._asm_OP_is_zero_4,
|
||||
_OP_is_zero_8 : (*_Assembler)._asm_OP_is_zero_8,
|
||||
_OP_is_zero_map : (*_Assembler)._asm_OP_is_zero_map,
|
||||
_OP_is_zero_mem : (*_Assembler)._asm_OP_is_zero_mem,
|
||||
_OP_is_zero_safe : (*_Assembler)._asm_OP_is_zero_safe,
|
||||
_OP_goto : (*_Assembler)._asm_OP_goto,
|
||||
_OP_map_iter : (*_Assembler)._asm_OP_map_iter,
|
||||
_OP_map_stop : (*_Assembler)._asm_OP_map_stop,
|
||||
|
|
@ -698,79 +696,6 @@ func (self *_Assembler) encode_string(doubleQuote bool) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Zero Value Check Routine **/
|
||||
|
||||
func (self *_Assembler) check_zero(nb int, dest int) {
|
||||
i := int64(0)
|
||||
e := int64(nb)
|
||||
|
||||
/* special case: zero-sized value, always empty */
|
||||
if e == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
/* 32-byte test */
|
||||
for i <= e - 32 {
|
||||
self.Emit("VMOVDQU", jit.Ptr(_SP_p, i), _Y0) // VMOVDQU (SP.p), Y0
|
||||
self.Emit("VPTEST" , _Y0, _Y0) // VPTEST Y0, Y0
|
||||
self.Sjmp("JNZ" , "_not_zero_z_{n}") // JNZ _not_zero_z_{n}
|
||||
i += 32
|
||||
}
|
||||
|
||||
/* VZEROUPPER to avoid AVX-SSE transition penalty */
|
||||
if e >= 32 {
|
||||
self.Emit("VZEROUPPER")
|
||||
}
|
||||
|
||||
/* 16-byte test */
|
||||
if i <= e - 16 {
|
||||
self.Emit("MOVOU", jit.Ptr(_SP_p, i), _X0) // MOVOU (SP.p), X0
|
||||
self.Emit("PTEST", _X0, _X0) // PTEST X0, X0
|
||||
self.Sjmp("JNZ" , "_not_zero_{n}") // JNZ _not_zero_{n}
|
||||
i += 16
|
||||
}
|
||||
|
||||
/* 8-byte test */
|
||||
if i <= e - 8 {
|
||||
self.Emit("CMPQ", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPQ i(SP.p), $0
|
||||
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
|
||||
i += 8
|
||||
}
|
||||
|
||||
/* 4 byte test */
|
||||
if i <= e - 4 {
|
||||
self.Emit("CMPL", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPL i(SP.p), $0
|
||||
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
|
||||
i += 4
|
||||
}
|
||||
|
||||
/* 2 byte test */
|
||||
if i <= e - 2 {
|
||||
self.Emit("CMPW", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPW i(SP.p), $0
|
||||
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
|
||||
i += 2
|
||||
}
|
||||
|
||||
/* the last byte */
|
||||
if i < e {
|
||||
self.Emit("CMPB", jit.Ptr(_SP_p, i), jit.Imm(0)) // CMPB i(SP.p), $0
|
||||
self.Sjmp("JNE" , "_not_zero_{n}") // JNE _not_zero_{n}
|
||||
}
|
||||
|
||||
/* value is not zero */
|
||||
if e < 32 {
|
||||
self.Xjmp("JMP", dest)
|
||||
self.Link("_not_zero_{n}")
|
||||
return
|
||||
}
|
||||
|
||||
/* VZEROUPPER to avoid AVX-SSE transition penalty */
|
||||
self.Xjmp("JMP", dest)
|
||||
self.Link("_not_zero_z_{n}")
|
||||
self.Emit("VZEROUPPER")
|
||||
self.Link("_not_zero_{n}")
|
||||
}
|
||||
|
||||
/** OpCode Assembler Functions **/
|
||||
|
||||
var (
|
||||
|
|
@ -787,7 +712,6 @@ var (
|
|||
|
||||
var (
|
||||
_F_memmove = jit.Func(memmove)
|
||||
_F_isZeroTyped = jit.Func(isZeroTyped)
|
||||
_F_error_number = jit.Func(error_number)
|
||||
_F_isValidNumber = jit.Func(isValidNumber)
|
||||
)
|
||||
|
|
@ -1092,20 +1016,6 @@ func (self *_Assembler) _asm_OP_is_zero_map(p *_Instr) {
|
|||
self.Xjmp("JE" , p.vi()) // JE p.vi()
|
||||
}
|
||||
|
||||
func (self *_Assembler) _asm_OP_is_zero_mem(p *_Instr) {
|
||||
self.check_zero(p.vlen(), p.vi())
|
||||
}
|
||||
|
||||
func (self *_Assembler) _asm_OP_is_zero_safe(p *_Instr) {
|
||||
self.check_zero(p.vlen(), p.vi()) // CHECKZ $p.vlen(), p.vi()
|
||||
self.Emit("MOVQ", jit.Type(p.vt()), _AX) // MOVQ $p.vt(), AX
|
||||
self.Emit("MOVQ", _SP_p, jit.Ptr(_SP, 0)) // MOVQ SP.p, (SP)
|
||||
self.Emit("MOVQ", _AX, jit.Ptr(_SP, 8)) // MOVQ AX, 8(SP)
|
||||
self.call_go(_F_isZeroTyped) // CALL_GO isZeroTyped
|
||||
self.Emit("CMPQ", jit.Ptr(_SP, 16), jit.Imm(0)) // CMPQ 16(SP), $0
|
||||
self.Xjmp("JNE" , p.vi()) // JNE p.vi()
|
||||
}
|
||||
|
||||
func (self *_Assembler) _asm_OP_goto(p *_Instr) {
|
||||
self.Xjmp("JMP", p.vi())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,8 +64,6 @@ const (
|
|||
_OP_is_zero_4
|
||||
_OP_is_zero_8
|
||||
_OP_is_zero_map
|
||||
_OP_is_zero_mem
|
||||
_OP_is_zero_safe
|
||||
_OP_goto
|
||||
_OP_map_iter
|
||||
_OP_map_stop
|
||||
|
|
@ -127,8 +125,6 @@ var _OpNames = [256]string {
|
|||
_OP_is_zero_4 : "is_zero_4",
|
||||
_OP_is_zero_8 : "is_zero_8",
|
||||
_OP_is_zero_map : "is_zero_map",
|
||||
_OP_is_zero_mem : "is_zero_mem",
|
||||
_OP_is_zero_safe : "is_zero_safe",
|
||||
_OP_goto : "goto",
|
||||
_OP_map_iter : "map_iter",
|
||||
_OP_map_stop : "map_stop",
|
||||
|
|
@ -259,8 +255,6 @@ func (self _Instr) isBranch() bool {
|
|||
case _OP_is_zero_2 : fallthrough
|
||||
case _OP_is_zero_4 : fallthrough
|
||||
case _OP_is_zero_8 : fallthrough
|
||||
case _OP_is_zero_mem : fallthrough
|
||||
case _OP_is_zero_safe : fallthrough
|
||||
case _OP_map_check_key : fallthrough
|
||||
case _OP_map_write_key : fallthrough
|
||||
case _OP_slice_next : fallthrough
|
||||
|
|
@ -291,8 +285,6 @@ func (self _Instr) disassemble() string {
|
|||
case _OP_cond_testc : fallthrough
|
||||
case _OP_map_check_key : fallthrough
|
||||
case _OP_map_write_key : return fmt.Sprintf("%-18sL_%d", self.op().String(), self.vi())
|
||||
case _OP_is_zero_mem : fallthrough
|
||||
case _OP_is_zero_safe : fallthrough
|
||||
case _OP_slice_next : return fmt.Sprintf("%-18sL_%d, %s", self.op().String(), self.vi(), self.vt())
|
||||
default : return self.op().String()
|
||||
}
|
||||
|
|
@ -686,7 +678,7 @@ func (self *_Compiler) compileStructBody(p *_Program, sp int, vt reflect.Type) {
|
|||
}
|
||||
|
||||
/* check for "omitempty" option */
|
||||
if (fv.Opts & resolver.F_omitempty) != 0 {
|
||||
if fv.Type.Kind() != reflect.Struct && fv.Type.Kind() != reflect.Array && (fv.Opts & resolver.F_omitempty) != 0 {
|
||||
s = append(s, p.pc())
|
||||
self.compileStructFieldZero(p, fv.Type)
|
||||
}
|
||||
|
|
@ -799,7 +791,6 @@ func (self *_Compiler) compileStructFieldZero(p *_Program, vt reflect.Type) {
|
|||
case reflect.Map : p.add(_OP_is_zero_map)
|
||||
case reflect.Ptr : p.add(_OP_is_nil)
|
||||
case reflect.Slice : p.add(_OP_is_nil_p1)
|
||||
case reflect.Struct : self.compileStructFieldNonTrivialZero(p, vt)
|
||||
default : panic(error_type(vt))
|
||||
}
|
||||
}
|
||||
|
|
@ -810,14 +801,6 @@ func (self *_Compiler) compileStructFieldQuoted(p *_Program, sp int, vt reflect.
|
|||
p.int(_OP_byte, '"')
|
||||
}
|
||||
|
||||
func (self *_Compiler) compileStructFieldNonTrivialZero(p *_Program, vt reflect.Type) {
|
||||
if isTrivialZeroable(vt) {
|
||||
p.rtt(_OP_is_zero_mem, vt)
|
||||
} else {
|
||||
p.rtt(_OP_is_zero_safe, vt)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *_Compiler) compileInterface(p *_Program, vt reflect.Type) {
|
||||
x := p.pc()
|
||||
p.add(_OP_is_nil_p1)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ package encoder
|
|||
import (
|
||||
`encoding`
|
||||
`encoding/json`
|
||||
`reflect`
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/native`
|
||||
|
|
@ -92,74 +91,4 @@ func encodeTextMarshaler(buf *[]byte, val encoding.TextMarshaler) error {
|
|||
} else {
|
||||
return encodeString(buf, rt.Mem2Str(ret))
|
||||
}
|
||||
}
|
||||
|
||||
func isZeroSafe(p unsafe.Pointer, vt *rt.GoType) bool {
|
||||
if native.Lzero(p, int(vt.Size)) == 0 {
|
||||
return true
|
||||
} else {
|
||||
return isZeroTyped(p, vt)
|
||||
}
|
||||
}
|
||||
|
||||
func isZeroTyped(p unsafe.Pointer, vt *rt.GoType) bool {
|
||||
switch vt.Kind() {
|
||||
case reflect.Map : return (*(**rt.GoMap)(p)).Count == 0
|
||||
case reflect.Slice : return (*rt.GoSlice)(p).Len == 0
|
||||
case reflect.String : return (*rt.GoString)(p).Len == 0
|
||||
case reflect.Struct : return isZeroStruct(p, vt)
|
||||
case reflect.Interface : return (*rt.GoEface)(p).Value == nil
|
||||
default : return false
|
||||
}
|
||||
}
|
||||
|
||||
func isZeroStruct(p unsafe.Pointer, vt *rt.GoType) bool {
|
||||
var dp uintptr
|
||||
var fp unsafe.Pointer
|
||||
|
||||
/* check for each field */
|
||||
for _, fv := range (*rt.GoStructType)(unsafe.Pointer(vt)).Fields {
|
||||
dp = fv.OffEmbed >> 1
|
||||
fp = unsafe.Pointer(uintptr(p) + dp)
|
||||
|
||||
/* check for the field */
|
||||
if !isZeroSafe(fp, fv.Type) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/* all tests are passed */
|
||||
return true
|
||||
}
|
||||
|
||||
func isTrivialZeroable(vt reflect.Type) bool {
|
||||
switch vt.Kind() {
|
||||
case reflect.Bool : return true
|
||||
case reflect.Int : return true
|
||||
case reflect.Int8 : return true
|
||||
case reflect.Int16 : return true
|
||||
case reflect.Int32 : return true
|
||||
case reflect.Int64 : return true
|
||||
case reflect.Uint : return true
|
||||
case reflect.Uint8 : return true
|
||||
case reflect.Uint16 : return true
|
||||
case reflect.Uint32 : return true
|
||||
case reflect.Uint64 : return true
|
||||
case reflect.Uintptr : return true
|
||||
case reflect.Float32 : return true
|
||||
case reflect.Float64 : return true
|
||||
case reflect.String : return false
|
||||
case reflect.Array : return true
|
||||
case reflect.Interface : return false
|
||||
case reflect.Map : return false
|
||||
case reflect.Ptr : return true
|
||||
case reflect.Slice : return false
|
||||
case reflect.Struct : return isStructTrivialZeroable(vt)
|
||||
default : return false
|
||||
}
|
||||
}
|
||||
|
||||
func isStructTrivialZeroable(vt reflect.Type) bool {
|
||||
for i := 0; i < vt.NumField(); i++ { if !isTrivialZeroable(vt.Field(i).Type) { return false } }
|
||||
return true
|
||||
}
|
||||
}
|
||||
160
issue_test/issue113_test.go
Normal file
160
issue_test/issue113_test.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package issue_test
|
||||
|
||||
import (
|
||||
`testing`
|
||||
|
||||
`github.com/bytedance/sonic/encoder`
|
||||
`github.com/stretchr/testify/require`
|
||||
)
|
||||
|
||||
type Issue113_OmitemptyOpt struct {
|
||||
Bool bool `json:"bool,omitempty"`
|
||||
Int int `json:"int,omitempty"`
|
||||
Int8 int8 `json:"int8,omitempty"`
|
||||
Int16 int16 `json:"int16,omitempty"`
|
||||
Int32 int32 `json:"int32,omitempty"`
|
||||
Int64 int64 `json:"int64,omitempty"`
|
||||
Uint uint `json:"uint,omitempty"`
|
||||
Uint8 uint8 `json:"uint8,omitempty"`
|
||||
Uint16 uint16 `json:"uint16,omitempty"`
|
||||
Uint32 uint32 `json:"uint32,omitempty"`
|
||||
Uint64 uint64 `json:"uint64,omitempty"`
|
||||
Float32 float32 `json:"float32,omitempty"`
|
||||
Float64 float64 `json:"float64,omitempty"`
|
||||
Uintptr uintptr `json:"uintptr,omitempty"`
|
||||
String string `json:"string,omitempty"`
|
||||
Array0 [0]uint `json:"array0,omitempty"`
|
||||
Array [2]int `json:"array,omitempty"`
|
||||
Interface interface{} `json:"interface,omitempty"`
|
||||
Map0 map[int]interface{} `json:"map0,omitempty"`
|
||||
Map map[string]float64 `json:"map,omitempty"`
|
||||
Slice0 []int `json:"slice0,omitempty"`
|
||||
Slice []byte `json:"slice,omitempty"`
|
||||
Ptr * Issue113_Inner `json:"ptr,omitempty"`
|
||||
Struct1 Issue113_Inner `json:"struct1,omitempty"`
|
||||
Struct2 struct{} `json:"struct2,omitempty"`
|
||||
}
|
||||
|
||||
type Issue113_Inner struct {
|
||||
S string `json:"s,"`
|
||||
So string `json:"so,omitempty"`
|
||||
}
|
||||
|
||||
var issue13ExpectedEmptyOpt = `{
|
||||
"array": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"struct1": {
|
||||
"s": ""
|
||||
},
|
||||
"struct2": {}
|
||||
}`
|
||||
|
||||
var issue13ExpectedNonemptyOpt = `{
|
||||
"bool": true,
|
||||
"int": 1,
|
||||
"int8": -1,
|
||||
"int16": 1,
|
||||
"int32": 2,
|
||||
"int64": 64,
|
||||
"uint": 1,
|
||||
"uint8": 8,
|
||||
"uint16": 16,
|
||||
"uint32": 32,
|
||||
"uint64": 64,
|
||||
"float32": 1,
|
||||
"float64": -2.34e64,
|
||||
"uintptr": 1,
|
||||
"string": "string",
|
||||
"array": [
|
||||
0,
|
||||
-1
|
||||
],
|
||||
"interface": {
|
||||
"s": "not omit"
|
||||
},
|
||||
"map0": {
|
||||
"0": "zero"
|
||||
},
|
||||
"map": {
|
||||
"key": 0
|
||||
},
|
||||
"slice0": [
|
||||
0
|
||||
],
|
||||
"slice": "Yg==",
|
||||
"ptr": {
|
||||
"s": "not omit"
|
||||
},
|
||||
"struct1": {
|
||||
"s": "not omit"
|
||||
},
|
||||
"struct2": {}
|
||||
}`
|
||||
|
||||
func TestIssue113_MarshalEmptyFieldsWithOmitemptyOpt(t *testing.T) {
|
||||
var obj Issue113_OmitemptyOpt
|
||||
obj.Slice0 = make([]int, 0, 100) // empty slice
|
||||
obj.Map0 = make(map[int]interface{}) // empty map
|
||||
|
||||
got, err := encoder.EncodeIndented(&obj, "", " ", 0)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, issue13ExpectedEmptyOpt, string(got))
|
||||
}
|
||||
|
||||
func TestIssue113_MarshalNonemptyFieldsWithOmitemptyOpt(t *testing.T) {
|
||||
var inner = & Issue113_Inner {
|
||||
S : "not omit",
|
||||
}
|
||||
|
||||
var obj = & Issue113_OmitemptyOpt{
|
||||
Bool : true,
|
||||
Int : 1,
|
||||
Int8 : -1,
|
||||
Int16 : 1,
|
||||
Int32 : 2,
|
||||
Int64 : 64,
|
||||
Uint : 1,
|
||||
Uint8 : 8,
|
||||
Uint16 : 16,
|
||||
Uint32 : 32,
|
||||
Uint64 : 64,
|
||||
Float32 : 1.0,
|
||||
Float64 : -2.34e+64,
|
||||
Uintptr : uintptr(0x1),
|
||||
String : "string",
|
||||
Array0 : [0]uint{},
|
||||
Array : [2]int{0, -1},
|
||||
Interface : *inner,
|
||||
Map0 : map[int]interface{}{0 : "zero"},
|
||||
Map : map[string]float64{"key" : 0.0},
|
||||
Slice0 : make([]int, 1, 1),
|
||||
Slice : []byte("b"),
|
||||
Ptr : inner,
|
||||
Struct1 : *inner,
|
||||
Struct2 : struct{}{},
|
||||
}
|
||||
|
||||
got, err := encoder.EncodeIndented(&obj, "", " ", 0)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, issue13ExpectedNonemptyOpt, string(got))
|
||||
}
|
||||
Loading…
Reference in a new issue