Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat:(decoder) add option CaseSensitive #709

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions decoder/decoder_compat.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build (!amd64 && !arm64) || go1.24 || !go1.17 || (arm64 && !go1.20)
// +build !amd64,!arm64 go1.24 !go1.17 arm64,!go1.20

/*
Expand All @@ -19,30 +20,32 @@
package decoder

import (
`bytes`
`encoding/json`
`io`
`reflect`
`unsafe`

`github.com/bytedance/sonic/internal/native/types`
`github.com/bytedance/sonic/option`
"bytes"
"encoding/json"
"io"
"reflect"
"unsafe"

"github.com/bytedance/sonic/internal/decoder/consts"
"github.com/bytedance/sonic/internal/native/types"
"github.com/bytedance/sonic/option"
)

func init() {
println("WARNING: sonic/decoder only supports (Go1.17~1.23 && CPU amd64) or (go1.20~1.23 && CPU arm64), but your environment is not suitable")
}

const (
_F_use_int64 = 0
_F_disable_urc = 2
_F_disable_unknown = 3
_F_copy_string = 4
_F_use_int64 = consts.F_use_int64
_F_disable_urc = consts.F_disable_unknown
_F_disable_unknown = consts.F_disable_unknown
_F_copy_string = consts.F_copy_string

_F_use_number = types.B_USE_NUMBER
_F_validate_string = types.B_VALIDATE_STRING
_F_allow_control = types.B_ALLOW_CONTROL
_F_no_validate_json = types.B_NO_VALIDATE_JSON
_F_use_number = consts.F_use_number
_F_validate_string = consts.F_validate_string
_F_allow_control = consts.F_allow_control
_F_no_validate_json = consts.F_no_validate_json
_F_case_sensitive = consts.F_case_sensitive
)

type Options uint64
Expand All @@ -55,6 +58,7 @@ const (
OptionCopyString Options = 1 << _F_copy_string
OptionValidateString Options = 1 << _F_validate_string
OptionNoValidateJSON Options = 1 << _F_no_validate_json
OptionCaseSensitive Options = 1 << _F_case_sensitive
)

func (self *Decoder) SetOptions(opts Options) {
Expand Down
1 change: 1 addition & 0 deletions decoder/decoder_native.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
OptionCopyString Options = api.OptionCopyString
OptionValidateString Options = api.OptionValidateString
OptionNoValidateJSON Options = api.OptionNoValidateJSON
OptionCaseSensitive Options = api.OptionCaseSensitive
)

// StreamDecoder is the decoder context object for streaming input.
Expand Down
62 changes: 62 additions & 0 deletions decoder/decoder_native_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,68 @@ import (
"github.com/stretchr/testify/require"
)

func TestDecoder_OptionCaseSensitive(t *testing.T) {
var js = `{"a":1,"normallllll":1,"longllllllllllllllllllllllllllllllllll":1}`
type TS struct{
A int
Normallllll int
Longllllllllllllllllllllllllllllllllll int
}
var obj = TS{}
d := NewDecoder(js)
d.SetOptions(OptionCaseSensitive)
err := d.Decode(&obj)
require.NoError(t, err)
require.Equal(t, TS{}, obj)
}


func TestDecoder_MapWithIndirectElement(t *testing.T) {
var v map[string]struct { A [129]byte }
_, err := decode(`{"":{"A":[1,2,3,4,5]}}`, &v, false)
require.NoError(t, err)
assert.Equal(t, [129]byte{1, 2, 3, 4, 5}, v[""].A)
}

func TestDecoder_OptionCaseSensitiveForManyKeys(t *testing.T) {
var js = `{"a":1,"b":2,"C":3,"DD":4,"eE":5,"fF":6,"G":7,"H":8,"I":9,"J":10,"K":11,"L":12,"M":13}`
type TS struct{
A int
B int
C int `json:"c"`
Dd int `json:"dd"`
Ee int `json:"ee"`
Ff int `json:"Ff"`
G int `json:"g"`
H int `json:"h"`
I int `json:"i"`
J int `json:"j"`
K int `json:"k"`
L int `json:"l"`
M int `json:"m"`
}

{
var obj = TS{}
err := json.Unmarshal([]byte(js), &obj)
require.NoError(t, err)

var obj2 = TS{}
d := NewDecoder(js)
// d.SetOptions(OptionCaseSensitive)
err2 := d.Decode(&obj2)
require.NoError(t, err2)
require.Equal(t, obj, obj2)
}

var obj = TS{}
d := NewDecoder(js)
d.SetOptions(OptionCaseSensitive)
err := d.Decode(&obj)
require.NoError(t, err)
require.Equal(t, TS{}, obj)
}


func BenchmarkSkipValidate(b *testing.B) {
type skiptype struct {
Expand Down
9 changes: 0 additions & 9 deletions decoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
Expand Down Expand Up @@ -287,14 +286,6 @@ func TestDecoder_Binding(t *testing.T) {
assert.Equal(t, _BindingValue, v, 0)
}


func TestDecoder_MapWithIndirectElement(t *testing.T) {
var v map[string]struct { A [129]byte }
_, err := decode(`{"":{"A":[1,2,3,4,5]}}`, &v, false)
require.NoError(t, err)
assert.Equal(t, [129]byte{1, 2, 3, 4, 5}, v[""].A)
}

func BenchmarkDecoder_Generic_Sonic(b *testing.B) {
var w interface{}
_, _ = decode(TwitterJson, &w, true)
Expand Down
2 changes: 2 additions & 0 deletions internal/decoder/api/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
_F_use_int64 = consts.F_use_int64
_F_use_number = consts.F_use_number
_F_validate_string = consts.F_validate_string
_F_case_sensitive = consts.F_case_sensitive

_MaxStack = consts.MaxStack

Expand All @@ -45,6 +46,7 @@ const (
OptionCopyString = consts.OptionCopyString
OptionValidateString = consts.OptionValidateString
OptionNoValidateJSON = consts.OptionNoValidateJSON
OptionCaseSensitive = consts.OptionCaseSensitive
)

type (
Expand Down
3 changes: 2 additions & 1 deletion internal/decoder/consts/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const (
F_disable_unknown = 3
F_copy_string = 4


F_use_number = types.B_USE_NUMBER
F_validate_string = types.B_VALIDATE_STRING
F_allow_control = types.B_ALLOW_CONTROL
F_no_validate_json = types.B_NO_VALIDATE_JSON
F_case_sensitive = 7
)

type Options uint64
Expand All @@ -29,6 +29,7 @@ const (
OptionCopyString Options = 1 << F_copy_string
OptionValidateString Options = 1 << F_validate_string
OptionNoValidateJSON Options = 1 << F_no_validate_json
OptionCaseSensitive Options = 1 << F_case_sensitive
)

const (
Expand Down
3 changes: 3 additions & 0 deletions internal/decoder/jitdec/assembler_regabi_amd64.go
Original file line number Diff line number Diff line change
Expand Up @@ -1789,13 +1789,16 @@ func (self *_Assembler) _asm_OP_struct_field(p *_Instr) {
self.Emit("MOVQ" , _R8, _VAR_sr) // MOVQ R8, sr
self.Sjmp("JMP" , "_end_{n}") // JMP _end_{n}
self.Link("_try_lowercase_{n}") // _try_lowercase_{n}:
self.Emit("BTQ" , jit.Imm(_F_case_sensitive), _ARG_fv) // check if enble option CaseSensitive
self.Sjmp("JC" , "_unknown_{n}")
self.Emit("MOVQ" , jit.Imm(referenceFields(p.vf())), _AX) // MOVQ ${p.vf()}, AX
self.Emit("MOVQ", _ARG_sv_p, _BX) // MOVQ sv, BX
self.Emit("MOVQ", _ARG_sv_n, _CX) // MOVQ sv, CX
self.call_go(_F_FieldMap_GetCaseInsensitive) // CALL_GO FieldMap::GetCaseInsensitive
self.Emit("MOVQ" , _AX, _VAR_sr) // MOVQ AX, _VAR_sr
self.Emit("TESTQ", _AX, _AX) // TESTQ AX, AX
self.Sjmp("JNS" , "_end_{n}") // JNS _end_{n}
self.Link("_unknown_{n}")
self.Emit("BTQ" , jit.Imm(_F_disable_unknown), _ARG_fv) // BTQ ${_F_disable_unknown}, fv
self.Sjmp("JC" , _LB_field_error) // JC _field_error
self.Link("_end_{n}") // _end_{n}:
Expand Down
1 change: 1 addition & 0 deletions internal/decoder/jitdec/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
_F_use_number = consts.F_use_number
_F_no_validate_json = consts.F_no_validate_json
_F_validate_string = consts.F_validate_string
_F_case_sensitive = consts.F_case_sensitive
)

var (
Expand Down
3 changes: 2 additions & 1 deletion internal/decoder/optdec/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"reflect"
"unsafe"

"github.com/bytedance/sonic/internal/decoder/consts"
caching "github.com/bytedance/sonic/internal/optcaching"
"github.com/bytedance/sonic/internal/resolver"
)
Expand Down Expand Up @@ -38,7 +39,7 @@ func (d *structDecoder) FromDom(vp unsafe.Pointer, node Node, ctx *context) erro
next = val.Next()

// find field idx
idx := d.fieldMap.Get(key)
idx := d.fieldMap.Get(key, ctx.Options()&uint64(consts.OptionCaseSensitive) != 0)
if idx == -1 {
if Options(ctx.Options())&OptionDisableUnknown != 0 {
return error_field(key)
Expand Down
2 changes: 1 addition & 1 deletion internal/native/neon/f32toa_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ _Digits:
WORD $0x37393639 // .ascii 4, '96979899'
WORD $0x39393839 // .ascii 4, '9899'
WORD $0x00000000 // .p2align 3, 0x00
_LB_15828841: // _pow10_ceil_sig_f32.g
_LB_3c57fe76: // _pow10_ceil_sig_f32.g
WORD $0x4b43fcf5; WORD $0x81ceb32c // .quad -9093133594791772939
WORD $0x5e14fc32; WORD $0xa2425ff7 // .quad -6754730975062328270
WORD $0x359a3b3f; WORD $0xcad2f7f5 // .quad -3831727700400522433
Expand Down
2 changes: 1 addition & 1 deletion internal/native/neon/f64toa_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -1232,7 +1232,7 @@ _Digits:
WORD $0x37393639 // .ascii 4, '96979899'
WORD $0x39393839 // .ascii 4, '9899'
// .p2align 3, 0x00
_LB_3b41de77: // _pow10_ceil_sig.g
_LB_f262bdcb: // _pow10_ceil_sig.g
WORD $0xbebcdc4f; WORD $0xff77b1fc // .quad -38366372719436721
WORD $0x13bb0f7b; WORD $0x25e8e89c // .quad 2731688931043774331
WORD $0xf73609b1; WORD $0x9faacf3d // .quad -6941508010590729807
Expand Down
Loading
Loading