mirror of
https://github.com/ii64/sonic.git
synced 2026-06-22 17:36:48 +08:00
feat (encoder): add encoder option NoNullSliceOrMap (#218)
* feat (encoder): add encoder option `NoNullSliceOrMap` * feat: add option on `sonic.Config` * build: specify `self-host` to x64 machine
This commit is contained in:
parent
08c7640684
commit
07d7b867d4
9 changed files with 161 additions and 25 deletions
4
.github/workflows/license-check.yml
vendored
4
.github/workflows/license-check.yml
vendored
|
|
@ -4,11 +4,11 @@ on: push
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: self-hosted
|
runs-on: [self-hosted, X64]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Check License Header
|
- name: Check License Header
|
||||||
uses: apache/skywalking-eyes@main
|
uses: apache/skywalking-eyes/header@main
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
||||||
1
api.go
1
api.go
|
|
@ -31,6 +31,7 @@ import (
|
||||||
UseUnicodeErrors bool
|
UseUnicodeErrors bool
|
||||||
DisallowUnknownFields bool
|
DisallowUnknownFields bool
|
||||||
CopyString bool
|
CopyString bool
|
||||||
|
NoNullSliceOrMap bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,13 @@
|
||||||
package sonic
|
package sonic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
`os`
|
|
||||||
`bytes`
|
`bytes`
|
||||||
`encoding`
|
`encoding`
|
||||||
`encoding/json`
|
`encoding/json`
|
||||||
`fmt`
|
`fmt`
|
||||||
`log`
|
`log`
|
||||||
`math`
|
`math`
|
||||||
|
`os`
|
||||||
`reflect`
|
`reflect`
|
||||||
`regexp`
|
`regexp`
|
||||||
`runtime`
|
`runtime`
|
||||||
|
|
@ -36,6 +36,7 @@ import (
|
||||||
`unsafe`
|
`unsafe`
|
||||||
|
|
||||||
`github.com/bytedance/sonic/encoder`
|
`github.com/bytedance/sonic/encoder`
|
||||||
|
`github.com/stretchr/testify/assert`
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -1153,3 +1154,18 @@ func TestMarshalerError(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarshalNullNil(t *testing.T) {
|
||||||
|
var v = struct {
|
||||||
|
A []int
|
||||||
|
B map[string]int
|
||||||
|
}{}
|
||||||
|
o, e := Marshal(v)
|
||||||
|
assert.Nil(t, e)
|
||||||
|
assert.Equal(t, `{"A":null,"B":null}`, string(o))
|
||||||
|
o, e = Config{
|
||||||
|
NoNullSliceOrMap: true,
|
||||||
|
}.Froze().Marshal(v)
|
||||||
|
assert.Nil(t, e)
|
||||||
|
assert.Equal(t, `{"A":[],"B":{}}`, string(o))
|
||||||
|
}
|
||||||
|
|
@ -89,11 +89,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_IM_null = 0x6c6c756e // 'null'
|
_IM_null = 0x6c6c756e // 'null'
|
||||||
_IM_true = 0x65757274 // 'true'
|
_IM_true = 0x65757274 // 'true'
|
||||||
_IM_fals = 0x736c6166 // 'fals' ('false' without the 'e')
|
_IM_fals = 0x736c6166 // 'fals' ('false' without the 'e')
|
||||||
_IM_open = 0x00225c22 // '"\"∅'
|
_IM_open = 0x00225c22 // '"\"∅'
|
||||||
_IM_mulv = -0x5555555555555555
|
_IM_array = 0x5d5b // '[]'
|
||||||
|
_IM_object = 0x7d7b // '{}'
|
||||||
|
_IM_mulv = -0x5555555555555555
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -204,6 +206,8 @@ func (self *_Assembler) compile() {
|
||||||
|
|
||||||
var _OpFuncTab = [256]func(*_Assembler, *_Instr) {
|
var _OpFuncTab = [256]func(*_Assembler, *_Instr) {
|
||||||
_OP_null : (*_Assembler)._asm_OP_null,
|
_OP_null : (*_Assembler)._asm_OP_null,
|
||||||
|
_OP_empty_arr : (*_Assembler)._asm_OP_empty_arr,
|
||||||
|
_OP_empty_obj : (*_Assembler)._asm_OP_empty_obj,
|
||||||
_OP_bool : (*_Assembler)._asm_OP_bool,
|
_OP_bool : (*_Assembler)._asm_OP_bool,
|
||||||
_OP_i8 : (*_Assembler)._asm_OP_i8,
|
_OP_i8 : (*_Assembler)._asm_OP_i8,
|
||||||
_OP_i16 : (*_Assembler)._asm_OP_i16,
|
_OP_i16 : (*_Assembler)._asm_OP_i16,
|
||||||
|
|
@ -767,6 +771,30 @@ func (self *_Assembler) _asm_OP_null(_ *_Instr) {
|
||||||
self.Emit("ADDQ", jit.Imm(4), _RL) // ADDQ $4, RL
|
self.Emit("ADDQ", jit.Imm(4), _RL) // ADDQ $4, RL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) _asm_OP_empty_arr(_ *_Instr) {
|
||||||
|
self.Emit("BTQ", jit.Imm(int64(bitNoNullSliceOrMap)), _ARG_fv)
|
||||||
|
self.Sjmp("JC", "_empty_arr_{n}")
|
||||||
|
self._asm_OP_null(nil)
|
||||||
|
self.Sjmp("JMP", "_empty_arr_end_{n}")
|
||||||
|
self.Link("_empty_arr_{n}")
|
||||||
|
self.check_size(2)
|
||||||
|
self.Emit("MOVW", jit.Imm(_IM_array), jit.Sib(_RP, _RL, 1, 0))
|
||||||
|
self.Emit("ADDQ", jit.Imm(2), _RL)
|
||||||
|
self.Link("_empty_arr_end_{n}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) _asm_OP_empty_obj(_ *_Instr) {
|
||||||
|
self.Emit("BTQ", jit.Imm(int64(bitNoNullSliceOrMap)), _ARG_fv)
|
||||||
|
self.Sjmp("JC", "_empty_obj_{n}")
|
||||||
|
self._asm_OP_null(nil)
|
||||||
|
self.Sjmp("JMP", "_empty_obj_end_{n}")
|
||||||
|
self.Link("_empty_obj_{n}")
|
||||||
|
self.check_size(2)
|
||||||
|
self.Emit("MOVW", jit.Imm(_IM_object), jit.Sib(_RP, _RL, 1, 0))
|
||||||
|
self.Emit("ADDQ", jit.Imm(2), _RL)
|
||||||
|
self.Link("_empty_obj_end_{n}")
|
||||||
|
}
|
||||||
|
|
||||||
func (self *_Assembler) _asm_OP_bool(_ *_Instr) {
|
func (self *_Assembler) _asm_OP_bool(_ *_Instr) {
|
||||||
self.Emit("CMPB", jit.Ptr(_SP_p, 0), jit.Imm(0)) // CMPB (SP.p), $0
|
self.Emit("CMPB", jit.Ptr(_SP_p, 0), jit.Imm(0)) // CMPB (SP.p), $0
|
||||||
self.Sjmp("JE" , "_false_{n}") // JE _false_{n}
|
self.Sjmp("JE" , "_false_{n}") // JE _false_{n}
|
||||||
|
|
|
||||||
|
|
@ -91,11 +91,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_IM_null = 0x6c6c756e // 'null'
|
_IM_null = 0x6c6c756e // 'null'
|
||||||
_IM_true = 0x65757274 // 'true'
|
_IM_true = 0x65757274 // 'true'
|
||||||
_IM_fals = 0x736c6166 // 'fals' ('false' without the 'e')
|
_IM_fals = 0x736c6166 // 'fals' ('false' without the 'e')
|
||||||
_IM_open = 0x00225c22 // '"\"∅'
|
_IM_open = 0x00225c22 // '"\"∅'
|
||||||
_IM_mulv = -0x5555555555555555
|
_IM_array = 0x5d5b // '[]'
|
||||||
|
_IM_object = 0x7d7b // '{}'
|
||||||
|
_IM_mulv = -0x5555555555555555
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -130,16 +132,16 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ST = jit.Reg("R15") // can't use R14 since it's always scratched by Go...
|
_ST = jit.Reg("R15") // can't use R14 since it's always scratched by Go...
|
||||||
_RP = jit.Reg("DI")
|
_RP = jit.Reg("DI")
|
||||||
_RL = jit.Reg("SI")
|
_RL = jit.Reg("SI")
|
||||||
_RC = jit.Reg("DX")
|
_RC = jit.Reg("DX")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_LR = jit.Reg("R9")
|
_LR = jit.Reg("R9")
|
||||||
_ET = jit.Reg("AX")
|
_ET = jit.Reg("AX")
|
||||||
_EP = jit.Reg("BX")
|
_EP = jit.Reg("BX")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -209,6 +211,8 @@ func (self *_Assembler) compile() {
|
||||||
|
|
||||||
var _OpFuncTab = [256]func(*_Assembler, *_Instr) {
|
var _OpFuncTab = [256]func(*_Assembler, *_Instr) {
|
||||||
_OP_null : (*_Assembler)._asm_OP_null,
|
_OP_null : (*_Assembler)._asm_OP_null,
|
||||||
|
_OP_empty_arr : (*_Assembler)._asm_OP_empty_arr,
|
||||||
|
_OP_empty_obj : (*_Assembler)._asm_OP_empty_obj,
|
||||||
_OP_bool : (*_Assembler)._asm_OP_bool,
|
_OP_bool : (*_Assembler)._asm_OP_bool,
|
||||||
_OP_i8 : (*_Assembler)._asm_OP_i8,
|
_OP_i8 : (*_Assembler)._asm_OP_i8,
|
||||||
_OP_i16 : (*_Assembler)._asm_OP_i16,
|
_OP_i16 : (*_Assembler)._asm_OP_i16,
|
||||||
|
|
@ -780,6 +784,30 @@ func (self *_Assembler) _asm_OP_null(_ *_Instr) {
|
||||||
self.Emit("ADDQ", jit.Imm(4), _RL) // ADDQ $4, RL
|
self.Emit("ADDQ", jit.Imm(4), _RL) // ADDQ $4, RL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) _asm_OP_empty_arr(_ *_Instr) {
|
||||||
|
self.Emit("BTQ", jit.Imm(int64(bitNoNullSliceOrMap)), _ARG_fv)
|
||||||
|
self.Sjmp("JC", "_empty_arr_{n}")
|
||||||
|
self._asm_OP_null(nil)
|
||||||
|
self.Sjmp("JMP", "_empty_arr_end_{n}")
|
||||||
|
self.Link("_empty_arr_{n}")
|
||||||
|
self.check_size(2)
|
||||||
|
self.Emit("MOVW", jit.Imm(_IM_array), jit.Sib(_RP, _RL, 1, 0))
|
||||||
|
self.Emit("ADDQ", jit.Imm(2), _RL)
|
||||||
|
self.Link("_empty_arr_end_{n}")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *_Assembler) _asm_OP_empty_obj(_ *_Instr) {
|
||||||
|
self.Emit("BTQ", jit.Imm(int64(bitNoNullSliceOrMap)), _ARG_fv)
|
||||||
|
self.Sjmp("JC", "_empty_obj_{n}")
|
||||||
|
self._asm_OP_null(nil)
|
||||||
|
self.Sjmp("JMP", "_empty_obj_end_{n}")
|
||||||
|
self.Link("_empty_obj_{n}")
|
||||||
|
self.check_size(2)
|
||||||
|
self.Emit("MOVW", jit.Imm(_IM_object), jit.Sib(_RP, _RL, 1, 0))
|
||||||
|
self.Emit("ADDQ", jit.Imm(2), _RL)
|
||||||
|
self.Link("_empty_obj_end_{n}")
|
||||||
|
}
|
||||||
|
|
||||||
func (self *_Assembler) _asm_OP_bool(_ *_Instr) {
|
func (self *_Assembler) _asm_OP_bool(_ *_Instr) {
|
||||||
self.Emit("CMPB", jit.Ptr(_SP_p, 0), jit.Imm(0)) // CMPB (SP.p), $0
|
self.Emit("CMPB", jit.Ptr(_SP_p, 0), jit.Imm(0)) // CMPB (SP.p), $0
|
||||||
self.Sjmp("JE" , "_false_{n}") // JE _false_{n}
|
self.Sjmp("JE" , "_false_{n}") // JE _false_{n}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ type _Op uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_OP_null _Op = iota + 1
|
_OP_null _Op = iota + 1
|
||||||
|
_OP_empty_arr
|
||||||
|
_OP_empty_obj
|
||||||
_OP_bool
|
_OP_bool
|
||||||
_OP_i8
|
_OP_i8
|
||||||
_OP_i16
|
_OP_i16
|
||||||
|
|
@ -93,6 +95,8 @@ const (
|
||||||
|
|
||||||
var _OpNames = [256]string {
|
var _OpNames = [256]string {
|
||||||
_OP_null : "null",
|
_OP_null : "null",
|
||||||
|
_OP_empty_arr : "empty_arr",
|
||||||
|
_OP_empty_obj : "empty_obj",
|
||||||
_OP_bool : "bool",
|
_OP_bool : "bool",
|
||||||
_OP_i8 : "i8",
|
_OP_i8 : "i8",
|
||||||
_OP_i16 : "i16",
|
_OP_i16 : "i16",
|
||||||
|
|
@ -481,19 +485,19 @@ func (self *_Compiler) compileOps(p *_Program, sp int, vt reflect.Type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compileNil(p *_Program, sp int, vt reflect.Type, fn func(*_Program, int, reflect.Type)) {
|
func (self *_Compiler) compileNil(p *_Program, sp int, vt reflect.Type, nil_op _Op, fn func(*_Program, int, reflect.Type)) {
|
||||||
x := p.pc()
|
x := p.pc()
|
||||||
p.add(_OP_is_nil)
|
p.add(_OP_is_nil)
|
||||||
fn(p, sp, vt)
|
fn(p, sp, vt)
|
||||||
e := p.pc()
|
e := p.pc()
|
||||||
p.add(_OP_goto)
|
p.add(_OP_goto)
|
||||||
p.pin(x)
|
p.pin(x)
|
||||||
p.add(_OP_null)
|
p.add(nil_op)
|
||||||
p.pin(e)
|
p.pin(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compilePtr(p *_Program, sp int, vt reflect.Type) {
|
func (self *_Compiler) compilePtr(p *_Program, sp int, vt reflect.Type) {
|
||||||
self.compileNil(p, sp, vt, self.compilePtrBody)
|
self.compileNil(p, sp, vt, _OP_null, self.compilePtrBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compilePtrBody(p *_Program, sp int, vt reflect.Type) {
|
func (self *_Compiler) compilePtrBody(p *_Program, sp int, vt reflect.Type) {
|
||||||
|
|
@ -505,7 +509,7 @@ func (self *_Compiler) compilePtrBody(p *_Program, sp int, vt reflect.Type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compileMap(p *_Program, sp int, vt reflect.Type) {
|
func (self *_Compiler) compileMap(p *_Program, sp int, vt reflect.Type) {
|
||||||
self.compileNil(p, sp, vt, self.compileMapBody)
|
self.compileNil(p, sp, vt, _OP_empty_obj, self.compileMapBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compileMapBody(p *_Program, sp int, vt reflect.Type) {
|
func (self *_Compiler) compileMapBody(p *_Program, sp int, vt reflect.Type) {
|
||||||
|
|
@ -591,7 +595,7 @@ func (self *_Compiler) compileMapBodyUtextPtr(p *_Program, vk reflect.Type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compileSlice(p *_Program, sp int, vt reflect.Type) {
|
func (self *_Compiler) compileSlice(p *_Program, sp int, vt reflect.Type) {
|
||||||
self.compileNil(p, sp, vt, self.compileSliceBody)
|
self.compileNil(p, sp, vt, _OP_empty_arr, self.compileSliceBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *_Compiler) compileSliceBody(p *_Program, sp int, vt reflect.Type) {
|
func (self *_Compiler) compileSliceBody(p *_Program, sp int, vt reflect.Type) {
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const (
|
||||||
bitEscapeHTML
|
bitEscapeHTML
|
||||||
bitCompactMarshaler
|
bitCompactMarshaler
|
||||||
bitNoQuoteTextMarshaler
|
bitNoQuoteTextMarshaler
|
||||||
|
bitNoNullSliceOrMap
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -52,12 +53,17 @@ const (
|
||||||
|
|
||||||
// CompactMarshaler indicates that the output JSON from json.Marshaler
|
// CompactMarshaler indicates that the output JSON from json.Marshaler
|
||||||
// is always compact and needs no validation
|
// is always compact and needs no validation
|
||||||
CompactMarshaler Options = 1 << bitCompactMarshaler
|
CompactMarshaler Options = 1 << bitCompactMarshaler
|
||||||
|
|
||||||
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
|
// NoQuoteTextMarshaler indicates that the output text from encoding.TextMarshaler
|
||||||
// is always escaped string and needs no quoting
|
// is always escaped string and needs no quoting
|
||||||
NoQuoteTextMarshaler Options = 1 << bitNoQuoteTextMarshaler
|
NoQuoteTextMarshaler Options = 1 << bitNoQuoteTextMarshaler
|
||||||
|
|
||||||
|
// NoNullSliceOrMap indicates all empty Array or Object are encoded as '[]' or '{}',
|
||||||
|
// instead of 'null'
|
||||||
|
NoNullSliceOrMap Options = 1 << bitNoNullSliceOrMap
|
||||||
|
|
||||||
|
// CompatibleWithStd is used to be compatible with std encoder.
|
||||||
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
CompatibleWithStd Options = SortMapKeys | EscapeHTML | CompactMarshaler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,56 @@ func TestGC(t *testing.T) {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sample struct {
|
||||||
|
M map[string]interface{}
|
||||||
|
S []interface{}
|
||||||
|
A [0]interface{}
|
||||||
|
MP *map[string]interface{}
|
||||||
|
SP *[]interface{}
|
||||||
|
AP *[0]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionSliceOrMapNoNull(t *testing.T) {
|
||||||
|
obj := sample{}
|
||||||
|
out, err := Encode(obj, NoNullSliceOrMap)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
require.Equal(t, `{"M":{},"S":[],"A":[],"MP":null,"SP":null,"AP":null}`, string(out))
|
||||||
|
|
||||||
|
obj2 := sample{}
|
||||||
|
out, err = Encode(obj2, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
require.Equal(t, `{"M":null,"S":null,"A":[],"MP":null,"SP":null,"AP":null}`, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkOptionSliceOrMapNoNull(b *testing.B) {
|
||||||
|
b.Run("true", func (b *testing.B) {
|
||||||
|
obj := sample{}
|
||||||
|
_, err := Encode(obj, NoNullSliceOrMap)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i:=0;i<b.N;i++{
|
||||||
|
_, _ = Encode(obj, NoNullSliceOrMap)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("false", func (b *testing.B) {
|
||||||
|
obj2 := sample{}
|
||||||
|
_, err := Encode(obj2, 0)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
for i:=0;i<b.N;i++{
|
||||||
|
_, _ = Encode(obj2, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func runEncoderTest(t *testing.T, fn func(string)string, exp string, arg string) {
|
func runEncoderTest(t *testing.T, fn func(string)string, exp string, arg string) {
|
||||||
require.Equal(t, exp, fn(arg))
|
require.Equal(t, exp, fn(arg))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
sonic.go
3
sonic.go
|
|
@ -85,6 +85,9 @@ func (cfg Config) Froze() API {
|
||||||
if cfg.CopyString {
|
if cfg.CopyString {
|
||||||
api.decoderOpts |= decoder.OptionCopyString
|
api.decoderOpts |= decoder.OptionCopyString
|
||||||
}
|
}
|
||||||
|
if cfg.NoNullSliceOrMap {
|
||||||
|
api.encoderOpts |= encoder.NoNullSliceOrMap
|
||||||
|
}
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue