Repository: bytedance/sonic

Issue:
[Feature Request] case sensitive unmarshal
We are migrating our application to sonic which is super fast, and encounter an problem on some scene.
Some of our code using `k8s.io/apimachinery/pkg/util/json` which backed by `sigs.k8s.io/json` providing some enhancements like `CaseSensitive` and `PreserveInts`.
`PreserveInts` can be replaced by `sonic.Config.UseInt64=true`
However `CaseSensitive` has no suitable solution, and i have verified that sonic is case-insensitive.
Can this be implemented?

----

Test sample

```go
func TestSonic(t *testing.T) {
	t.Run("sonic", func(t *testing.T) {
		config := sonic.Config{
			EscapeHTML : true,
			SortMapKeys: true,
			CompactMarshaler: true,
			CopyString : true,
			ValidateString : true,
			UseInt64: true,
		}.Froze()
		var u2 struct {
			A int64
			B float64 `json:"b"`
		}
		if err := config.UnmarshalFromString(`{"a":2,"b":1.1}`, &u2); err != nil {
			t.Fatalf("failed to unmarshal: %v", err)
		} else {
			t.Logf("unmarshal result: %#v", u2)
		}
	})
	t.Run("kjson", func(t *testing.T) {
		var u2 struct {
			A int64
			B float64 `json:"b"`
		}
		if err := json.Unmarshal([]byte(`{"a":2,"b":1.1}`), &u2); err != nil {
			t.Fatalf("failed to unmarshal: %v", err)
		} else {
			t.Logf("unmarshal result: %#v", u2)
		}
	})
}
```
```
=== RUN   TestSonic
=== RUN   TestSonic/sonic
    utils_test.go:191: unmarshal result: struct { A int64; B float64 "json:\"b\"" }{A:2, B:1.1}
--- PASS: TestSonic/sonic (0.00s)
=== RUN   TestSonic/kjson
    utils_test.go:209: unmarshal result: struct { A int64; B float64 "json:\"b\"" }{A:0, B:1.1}
--- PASS: TestSonic/kjson (0.00s)
--- PASS: TestSonic (0.00s)
PASS
```



Discussion:
@AsterDY: Seems possible. But now sonic is under refactoring. Maybe in our next middle version@AsterDY: Can u try #709 first? @ayanamist @ayanamist: Good job, i see you have already added some unit tests for this feature which is identical to my test sample.

I test `github.com/bytedance/sonic v1.12.5-0.20250103165927-9998182d92ad` which is your branch in my project and all unit tests pass!@ezxfv: @ayanamist How did you replace sigs.k8s.io/json? I'm also looking to use Sonic to optimize this library. I built a Kubernetes gateway that requires frequent serialization and deserialization of Kubernetes objects.
@ayanamist: @ezxfv Just rewrite all related codes. It's not a drop-in replacement.

Solution patch:
diff --git a/decoder/decoder_compat.go b/decoder/decoder_compat.go
index a0e1de4..408d593 100644
--- a/decoder/decoder_compat.go
+++ b/decoder/decoder_compat.go
@@ -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
 
 /*
@@ -19,14 +20,15 @@
 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() {
@@ -34,15 +36,16 @@ func init() {
 }
 
 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
@@ -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) {
diff --git a/decoder/decoder_native.go b/decoder/decoder_native.go
index 450dfb6..bf71e1b 100644
--- a/decoder/decoder_native.go
+++ b/decoder/decoder_native.go
@@ -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.
diff --git a/internal/decoder/api/decoder.go b/internal/decoder/api/decoder.go
index 0dc0199..bae0584 100644
--- a/internal/decoder/api/decoder.go
+++ b/internal/decoder/api/decoder.go
@@ -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
 
@@ -45,6 +46,7 @@ const (
     OptionCopyString       = consts.OptionCopyString
     OptionValidateString   = consts.OptionValidateString
     OptionNoValidateJSON   = consts.OptionNoValidateJSON
+    OptionCaseSensitive    = consts.OptionCaseSensitive
 )
 
 type (
diff --git a/internal/decoder/consts/option.go b/internal/decoder/consts/option.go
index 4195ebd..f8c49d5 100644
--- a/internal/decoder/consts/option.go
+++ b/internal/decoder/consts/option.go
@@ -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
@@ -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 (
diff --git a/internal/decoder/jitdec/assembler_regabi_amd64.go b/internal/decoder/jitdec/assembler_regabi_amd64.go
index 42b8b50..04e854d 100644
--- a/internal/decoder/jitdec/assembler_regabi_amd64.go
+++ b/internal/decoder/jitdec/assembler_regabi_amd64.go
@@ -1794,6 +1794,8 @@ 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 enable 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
@@ -1801,6 +1803,10 @@ func (self *_Assembler) _asm_OP_struct_field(p *_Instr) {
     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}")
+    // HACK: because `_VAR_sr` maybe used in `F_vstring`, so we should clear here again for `_OP_switch`.
+    self.Emit("MOVQ" , jit.Imm(-1), _AX)                        // MOVQ    $-1, AX
+    self.Emit("MOVQ" , _AX, _VAR_sr)                            // MOVQ    AX, sr
     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}:
diff --git a/internal/decoder/jitdec/decoder.go b/internal/decoder/jitdec/decoder.go
index bbb4b4b..de63252 100644
--- a/internal/decoder/jitdec/decoder.go
+++ b/internal/decoder/jitdec/decoder.go
@@ -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 (
diff --git a/internal/decoder/optdec/structs.go b/internal/decoder/optdec/structs.go
index bce2758..8b148ea 100644
--- a/internal/decoder/optdec/structs.go
+++ b/internal/decoder/optdec/structs.go
@@ -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"
 )
@@ -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)
diff --git a/internal/native/neon/f32toa_arm64.s b/internal/native/neon/f32toa_arm64.s
index a4f74da..64eed6d 100644
--- a/internal/native/neon/f32toa_arm64.s
+++ b/internal/native/neon/f32toa_arm64.s
@@ -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
diff --git a/internal/native/neon/f64toa_arm64.s b/internal/native/neon/f64toa_arm64.s
index d1e7f08..12bf452 100644
--- a/internal/native/neon/f64toa_arm64.s
+++ b/internal/native/neon/f64toa_arm64.s
@@ -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
diff --git a/internal/native/neon/lookup_small_key_arm64.s b/internal/native/neon/lookup_small_key_arm64.s
index fb856f1..004c093 100644
--- a/internal/native/neon/lookup_small_key_arm64.s
+++ b/internal/native/neon/lookup_small_key_arm64.s
@@ -64,250 +64,232 @@ _lookup_small_key:
 	WORD $0xf940002b  // ldr	x11, [x1]
 	WORD $0x12001d49  // and	w9, w10, #0xff
 	WORD $0x8b294928  // add	x8, x9, w9, uxtw #2
-	WORD $0x8b08016c  // add	x12, x11, x8
-	WORD $0x39400188  // ldrb	w8, [x12]
-	WORD $0x34001be8  // cbz	w8, LBB0_46 $892(%rip)
-	WORD $0xf940000f  // ldr	x15, [x0]
-	WORD $0xb840118c  // ldur	w12, [x12, #1]
-	WORD $0x1102958d  // add	w13, w12, #165
-	WORD $0x8b0d016d  // add	x13, x11, x13
-	WORD $0x92401d4e  // and	x14, x10, #0xff
+	WORD $0x8b08016d  // add	x13, x11, x8
+	WORD $0x394001a8  // ldrb	w8, [x13]
+	WORD $0x340019e8  // cbz	w8, LBB0_46 $828(%rip)
+	WORD $0xf940000c  // ldr	x12, [x0]
+	WORD $0xb84011ad  // ldur	w13, [x13, #1]
+	WORD $0x110295ae  // add	w14, w13, #165
+	WORD $0x8b0e016e  // add	x14, x11, x14
+	WORD $0x92401d4f  // and	x15, x10, #0xff
 	WORD $0x7100253f  // cmp	w9, #9
 	WORD $0x54000942  // b.hs	LBB0_20 $296(%rip)
 	WORD $0x11000530  // add	w16, w9, #1
-	WORD $0x394001f1  // ldrb	w17, [x15]
+	WORD $0x39400191  // ldrb	w17, [x12]
 	WORD $0x528000e0  // mov	w0, #7
 	WORD $0xaa0803e1  // mov	x1, x8
 	WORD $0x14000007  // b	LBB0_5 $28(%rip)
 LBB0_3:
 	WORD $0x52800003  // mov	w3, #0
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x540007a2  // b.hs	LBB0_19 $244(%rip)
 LBB0_4:
-	WORD $0x8b1001ad  // add	x13, x13, x16
+	WORD $0x8b1001ce  // add	x14, x14, x16
 	WORD $0x71000421  // subs	w1, w1, #1
-	WORD $0x54000b60  // b.eq	LBB0_23 $364(%rip)
+	WORD $0x54000b40  // b.eq	LBB0_23 $360(%rip)
 LBB0_5:
-	WORD $0x394001a3  // ldrb	w3, [x13]
+	WORD $0x394001c3  // ldrb	w3, [x14]
 	WORD $0x6b11007f  // cmp	w3, w17
 	WORD $0x54ffff01  // b.ne	LBB0_3 $-32(%rip)
-	WORD $0x394005a3  // ldrb	w3, [x13, #1]
-	WORD $0x394005e4  // ldrb	w4, [x15, #1]
+	WORD $0x394005c3  // ldrb	w3, [x14, #1]
+	WORD $0x39400584  // ldrb	w4, [x12, #1]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x54000381  // b.ne	LBB0_13 $112(%rip)
-	WORD $0x394009a3  // ldrb	w3, [x13, #2]
-	WORD $0x394009e4  // ldrb	w4, [x15, #2]
+	WORD $0x394009c3  // ldrb	w3, [x14, #2]
+	WORD $0x39400984  // ldrb	w4, [x12, #2]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x54000381  // b.ne	LBB0_14 $112(%rip)
-	WORD $0x39400da3  // ldrb	w3, [x13, #3]
-	WORD $0x39400de4  // ldrb	w4, [x15, #3]
+	WORD $0x39400dc3  // ldrb	w3, [x14, #3]
+	WORD $0x39400d84  // ldrb	w4, [x12, #3]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x54000381  // b.ne	LBB0_15 $112(%rip)
-	WORD $0x394011a3  // ldrb	w3, [x13, #4]
-	WORD $0x394011e4  // ldrb	w4, [x15, #4]
+	WORD $0x394011c3  // ldrb	w3, [x14, #4]
+	WORD $0x39401184  // ldrb	w4, [x12, #4]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x54000381  // b.ne	LBB0_16 $112(%rip)
-	WORD $0x394015a3  // ldrb	w3, [x13, #5]
-	WORD $0x394015e4  // ldrb	w4, [x15, #5]
+	WORD $0x394015c3  // ldrb	w3, [x14, #5]
+	WORD $0x39401584  // ldrb	w4, [x12, #5]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x54000381  // b.ne	LBB0_17 $112(%rip)
-	WORD $0x394019a3  // ldrb	w3, [x13, #6]
-	WORD $0x394019e4  // ldrb	w4, [x15, #6]
+	WORD $0x394019c3  // ldrb	w3, [x14, #6]
+	WORD $0x39401984  // ldrb	w4, [x12, #6]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x54000381  // b.ne	LBB0_18 $112(%rip)
-	WORD $0x39401da3  // ldrb	w3, [x13, #7]
-	WORD $0x39401de4  // ldrb	w4, [x15, #7]
+	WORD $0x39401dc3  // ldrb	w3, [x14, #7]
+	WORD $0x39401d84  // ldrb	w4, [x12, #7]
 	WORD $0x6b04007f  // cmp	w3, w4
 	WORD $0x1a801403  // cinc	w3, w0, eq
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fffba3  // b.lo	LBB0_4 $-140(%rip)
 	WORD $0x14000018  // b	LBB0_19 $96(%rip)
 LBB0_13:
 	WORD $0x52800023  // mov	w3, #1
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fffb23  // b.lo	LBB0_4 $-156(%rip)
 	WORD $0x14000014  // b	LBB0_19 $80(%rip)
 LBB0_14:
 	WORD $0x52800043  // mov	w3, #2
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fffaa3  // b.lo	LBB0_4 $-172(%rip)
 	WORD $0x14000010  // b	LBB0_19 $64(%rip)
 LBB0_15:
 	WORD $0x52800063  // mov	w3, #3
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fffa23  // b.lo	LBB0_4 $-188(%rip)
 	WORD $0x1400000c  // b	LBB0_19 $48(%rip)
 LBB0_16:
 	WORD $0x52800083  // mov	w3, #4
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fff9a3  // b.lo	LBB0_4 $-204(%rip)
 	WORD $0x14000008  // b	LBB0_19 $32(%rip)
 LBB0_17:
 	WORD $0x528000a3  // mov	w3, #5
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fff923  // b.lo	LBB0_4 $-220(%rip)
 	WORD $0x14000004  // b	LBB0_19 $16(%rip)
 LBB0_18:
 	WORD $0x528000c3  // mov	w3, #6
-	WORD $0x6b0e007f  // cmp	w3, w14
+	WORD $0x6b0f007f  // cmp	w3, w15
 	WORD $0x54fff8a3  // b.lo	LBB0_4 $-236(%rip)
 LBB0_19:
-	WORD $0x8b0e01a8  // add	x8, x13, x14
+	WORD $0x8b0f01c8  // add	x8, x14, x15
 	WORD $0x39400100  // ldrb	w0, [x8]
 	WORD $0xa940fbfd  // ldp	fp, lr, [sp, #8]
 	WORD $0x910083ff  // add	sp, sp, #32
 	WORD $0xd65f03c0  // ret
 LBB0_20:
-	WORD $0xad4001e1  // ldp	q1, q0, [x15]
-	WORD $0x9280000f  // mov	x15, #-1
-	WORD $0x9ace21ef  // lsl	x15, x15, x14
-	WORD $0x11000530  // add	w16, w9, #1
+	WORD $0xad400580  // ldp	q0, q1, [x12]
+	WORD $0x92800010  // mov	x16, #-1
+	WORD $0x9acf2210  // lsl	x16, x16, x15
+	WORD $0x11000531  // add	w17, w9, #1
 Lloh0:
-	WORD $0x10fff331  // adr	x17, lCPI0_0 $-412(%rip)
+	WORD $0x10fff320  // adr	x0, lCPI0_0 $-412(%rip)
 Lloh1:
-	WORD $0x3dc00222  // ldr	q2, [x17, lCPI0_0@PAGEOFF] $0(%rip)
+	WORD $0x3dc00002  // ldr	q2, [x0, lCPI0_0@PAGEOFF] $0(%rip)
 Lloh2:
-	WORD $0x10fff371  // adr	x17, lCPI0_1 $-404(%rip)
+	WORD $0x10fff360  // adr	x0, lCPI0_1 $-404(%rip)
 Lloh3:
-	WORD $0x3dc00223  // ldr	q3, [x17, lCPI0_1@PAGEOFF] $0(%rip)
-	WORD $0xaa0803f1  // mov	x17, x8
+	WORD $0x3dc00003  // ldr	q3, [x0, lCPI0_1@PAGEOFF] $0(%rip)
+	WORD $0xaa0803e0  // mov	x0, x8
 LBB0_21:
-	WORD $0xad4015a4  // ldp	q4, q5, [x13]
-	WORD $0x6e248c24  // cmeq.16b	v4, v1, v4
-	WORD $0x6e258c05  // cmeq.16b	v5, v0, v5
+	WORD $0xad4015c4  // ldp	q4, q5, [x14]
+	WORD $0x6e248c04  // cmeq.16b	v4, v0, v4
+	WORD $0x6e258c25  // cmeq.16b	v5, v1, v5
 	WORD $0x4e221c84  // and.16b	v4, v4, v2
 	WORD $0x4e030084  // tbl.16b	v4, { v4 }, v3
 	WORD $0x4e71b884  // addv.8h	h4, v4
-	WORD $0x1e260080  // fmov	w0, s4
+	WORD $0x1e260081  // fmov	w1, s4
 	WORD $0x4e221ca4  // and.16b	v4, v5, v2
 	WORD $0x4e030084  // tbl.16b	v4, { v4 }, v3
 	WORD $0x4e71b884  // addv.8h	h4, v4
-	WORD $0x1e260081  // fmov	w1, s4
-	WORD $0x33103c20  // bfi	w0, w1, #16, #16
-	WORD $0x2a0f0000  // orr	w0, w0, w15
-	WORD $0x3100041f  // cmn	w0, #1
+	WORD $0x1e260083  // fmov	w3, s4
+	WORD $0x33103c61  // bfi	w1, w3, #16, #16
+	WORD $0x2a100021  // orr	w1, w1, w16
+	WORD $0x3100043f  // cmn	w1, #1
 	WORD $0x54fffc80  // b.eq	LBB0_19 $-112(%rip)
-	WORD $0x8b1001ad  // add	x13, x13, x16
-	WORD $0x71000631  // subs	w17, w17, #1
+	WORD $0x8b1101ce  // add	x14, x14, x17
+	WORD $0x71000400  // subs	w0, w0, #1
 	WORD $0x54fffde1  // b.ne	LBB0_21 $-68(%rip)
-	WORD $0x14000002  // b	LBB0_24 $8(%rip)
 LBB0_23:
-	WORD $0xad4001e1  // ldp	q1, q0, [x15]
-LBB0_24:
-	WORD $0x4f05e7e2  // movi.16b	v2, #191
-	WORD $0x4e228424  // add.16b	v4, v1, v2
-	WORD $0x4f00e743  // movi.16b	v3, #26
-	WORD $0x6e243465  // cmhi.16b	v5, v3, v4
-	WORD $0x4f01e404  // movi.16b	v4, #32
-	WORD $0x4e241ca5  // and.16b	v5, v5, v4
-	WORD $0x4e2184a1  // add.16b	v1, v5, v1
-	WORD $0x8b0c016b  // add	x11, x11, x12
+	WORD $0xb100045f  // cmn	x2, #1
+	WORD $0x54000c40  // b.eq	LBB0_46 $392(%rip)
+	WORD $0x3dc00180  // ldr	q0, [x12]
+	WORD $0x4f05e7e1  // movi.16b	v1, #191
+	WORD $0x4e218403  // add.16b	v3, v0, v1
+	WORD $0x4f00e742  // movi.16b	v2, #26
+	WORD $0x6e233444  // cmhi.16b	v4, v2, v3
+	WORD $0x4f01e403  // movi.16b	v3, #32
+	WORD $0x4e231c84  // and.16b	v4, v4, v3
+	WORD $0x4e208480  // add.16b	v0, v4, v0
+	WORD $0x8b0d016b  // add	x11, x11, x13
 	WORD $0x8b02016b  // add	x11, x11, x2
 	WORD $0x92401d4a  // and	x10, x10, #0xff
 	WORD $0x7100253f  // cmp	w9, #9
-	WORD $0x54000922  // b.hs	LBB0_43 $292(%rip)
-	WORD $0x0e013c2c  // umov.b	w12, v1[0]
-	WORD $0x0e033c2d  // umov.b	w13, v1[1]
-	WORD $0x0e053c2e  // umov.b	w14, v1[2]
-	WORD $0x0e073c2f  // umov.b	w15, v1[3]
-	WORD $0x0e093c30  // umov.b	w16, v1[4]
-	WORD $0x0e0b3c31  // umov.b	w17, v1[5]
+	WORD $0x540006e2  // b.hs	LBB0_43 $220(%rip)
+	WORD $0x0e013c0c  // umov.b	w12, v0[0]
+	WORD $0x0e033c0d  // umov.b	w13, v0[1]
+	WORD $0x0e053c0e  // umov.b	w14, v0[2]
+	WORD $0x0e073c0f  // umov.b	w15, v0[3]
+	WORD $0x0e093c10  // umov.b	w16, v0[4]
+	WORD $0x0e0b3c11  // umov.b	w17, v0[5]
 	WORD $0x11000529  // add	w9, w9, #1
-	WORD $0x0e0d3c20  // umov.b	w0, v1[6]
+	WORD $0x0e0d3c00  // umov.b	w0, v0[6]
 	WORD $0x528000e1  // mov	w1, #7
-	WORD $0x0e0f3c22  // umov.b	w2, v1[7]
-	WORD $0x14000007  // b	LBB0_28 $28(%rip)
+	WORD $0x0e0f3c02  // umov.b	w2, v0[7]
 LBB0_26:
-	WORD $0x52800003  // mov	w3, #0
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x540006c2  // b.hs	LBB0_42 $216(%rip)
-LBB0_27:
-	WORD $0x8b09016b  // add	x11, x11, x9
-	WORD $0x71000508  // subs	w8, w8, #1
-	WORD $0x54000aa0  // b.eq	LBB0_46 $340(%rip)
-LBB0_28:
 	WORD $0x39400163  // ldrb	w3, [x11]
 	WORD $0x6b2c007f  // cmp	w3, w12, uxtb
-	WORD $0x54ffff01  // b.ne	LBB0_26 $-32(%rip)
+	WORD $0x540002e1  // b.ne	LBB0_34 $92(%rip)
 	WORD $0x39400563  // ldrb	w3, [x11, #1]
 	WORD $0x6b2d007f  // cmp	w3, w13, uxtb
-	WORD $0x540002c1  // b.ne	LBB0_36 $88(%rip)
+	WORD $0x540002c1  // b.ne	LBB0_35 $88(%rip)
 	WORD $0x39400963  // ldrb	w3, [x11, #2]
 	WORD $0x6b2e007f  // cmp	w3, w14, uxtb
-	WORD $0x540002e1  // b.ne	LBB0_37 $92(%rip)
+	WORD $0x540002a1  // b.ne	LBB0_36 $84(%rip)
 	WORD $0x39400d63  // ldrb	w3, [x11, #3]
 	WORD $0x6b2f007f  // cmp	w3, w15, uxtb
-	WORD $0x54000301  // b.ne	LBB0_38 $96(%rip)
+	WORD $0x54000281  // b.ne	LBB0_37 $80(%rip)
 	WORD $0x39401163  // ldrb	w3, [x11, #4]
 	WORD $0x6b30007f  // cmp	w3, w16, uxtb
-	WORD $0x54000321  // b.ne	LBB0_39 $100(%rip)
+	WORD $0x54000261  // b.ne	LBB0_38 $76(%rip)
 	WORD $0x39401563  // ldrb	w3, [x11, #5]
 	WORD $0x6b31007f  // cmp	w3, w17, uxtb
-	WORD $0x54000341  // b.ne	LBB0_40 $104(%rip)
+	WORD $0x54000241  // b.ne	LBB0_39 $72(%rip)
 	WORD $0x39401963  // ldrb	w3, [x11, #6]
 	WORD $0x6b20007f  // cmp	w3, w0, uxtb
-	WORD $0x54000361  // b.ne	LBB0_41 $108(%rip)
+	WORD $0x54000221  // b.ne	LBB0_40 $68(%rip)
 	WORD $0x39401d63  // ldrb	w3, [x11, #7]
 	WORD $0x6b22007f  // cmp	w3, w2, uxtb
 	WORD $0x1a811423  // cinc	w3, w1, eq
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fffc83  // b.lo	LBB0_27 $-112(%rip)
-	WORD $0x14000018  // b	LBB0_42 $96(%rip)
-LBB0_36:
+	WORD $0x1400000e  // b	LBB0_41 $56(%rip)
+LBB0_34:
+	WORD $0x52800003  // mov	w3, #0
+	WORD $0x1400000c  // b	LBB0_41 $48(%rip)
+LBB0_35:
 	WORD $0x52800023  // mov	w3, #1
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fffc03  // b.lo	LBB0_27 $-128(%rip)
-	WORD $0x14000014  // b	LBB0_42 $80(%rip)
-LBB0_37:
+	WORD $0x1400000a  // b	LBB0_41 $40(%rip)
+LBB0_36:
 	WORD $0x52800043  // mov	w3, #2
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fffb83  // b.lo	LBB0_27 $-144(%rip)
-	WORD $0x14000010  // b	LBB0_42 $64(%rip)
-LBB0_38:
+	WORD $0x14000008  // b	LBB0_41 $32(%rip)
+LBB0_37:
 	WORD $0x52800063  // mov	w3, #3
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fffb03  // b.lo	LBB0_27 $-160(%rip)
-	WORD $0x1400000c  // b	LBB0_42 $48(%rip)
-LBB0_39:
+	WORD $0x14000006  // b	LBB0_41 $24(%rip)
+LBB0_38:
 	WORD $0x52800083  // mov	w3, #4
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fffa83  // b.lo	LBB0_27 $-176(%rip)
-	WORD $0x14000008  // b	LBB0_42 $32(%rip)
-LBB0_40:
+	WORD $0x14000004  // b	LBB0_41 $16(%rip)
+LBB0_39:
 	WORD $0x528000a3  // mov	w3, #5
-	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fffa03  // b.lo	LBB0_27 $-192(%rip)
-	WORD $0x14000004  // b	LBB0_42 $16(%rip)
-LBB0_41:
+	WORD $0x14000002  // b	LBB0_41 $8(%rip)
+LBB0_40:
 	WORD $0x528000c3  // mov	w3, #6
+LBB0_41:
 	WORD $0x6b0a007f  // cmp	w3, w10
-	WORD $0x54fff983  // b.lo	LBB0_27 $-208(%rip)
-LBB0_42:
-	WORD $0x8b0a0168  // add	x8, x11, x10
-	WORD $0x39400100  // ldrb	w0, [x8]
-	WORD $0xa940fbfd  // ldp	fp, lr, [sp, #8]
-	WORD $0x910083ff  // add	sp, sp, #32
-	WORD $0xd65f03c0  // ret
+	WORD $0x540004e2  // b.hs	LBB0_47 $156(%rip)
+	WORD $0x8b09016b  // add	x11, x11, x9
+	WORD $0x71000508  // subs	w8, w8, #1
+	WORD $0x54fffac1  // b.ne	LBB0_26 $-168(%rip)
+	WORD $0x1400001f  // b	LBB0_46 $124(%rip)
 LBB0_43:
-	WORD $0x4e228402  // add.16b	v2, v0, v2
-	WORD $0x6e223462  // cmhi.16b	v2, v3, v2
-	WORD $0x4e241c42  // and.16b	v2, v2, v4
-	WORD $0x4e208440  // add.16b	v0, v2, v0
+	WORD $0x3dc00584  // ldr	q4, [x12, #16]
+	WORD $0x4e218481  // add.16b	v1, v4, v1
+	WORD $0x6e213441  // cmhi.16b	v1, v2, v1
+	WORD $0x4e231c21  // and.16b	v1, v1, v3
+	WORD $0x4e248421  // add.16b	v1, v1, v4
 	WORD $0x9280000c  // mov	x12, #-1
 	WORD $0x9aca218c  // lsl	x12, x12, x10
 	WORD $0x11000529  // add	w9, w9, #1
 Lloh4:
-	WORD $0x10ffe4ad  // adr	x13, lCPI0_0 $-876(%rip)
+	WORD $0x10ffe6ad  // adr	x13, lCPI0_0 $-812(%rip)
 Lloh5:
 	WORD $0x3dc001a2  // ldr	q2, [x13, lCPI0_0@PAGEOFF] $0(%rip)
 Lloh6:
-	WORD $0x10ffe4ed  // adr	x13, lCPI0_1 $-868(%rip)
+	WORD $0x10ffe6ed  // adr	x13, lCPI0_1 $-804(%rip)
 Lloh7:
 	WORD $0x3dc001a3  // ldr	q3, [x13, lCPI0_1@PAGEOFF] $0(%rip)
 LBB0_44:
 	WORD $0xad401564  // ldp	q4, q5, [x11]
-	WORD $0x6e248c24  // cmeq.16b	v4, v1, v4
-	WORD $0x6e258c05  // cmeq.16b	v5, v0, v5
+	WORD $0x6e248c04  // cmeq.16b	v4, v0, v4
+	WORD $0x6e258c25  // cmeq.16b	v5, v1, v5
 	WORD $0x4e221c84  // and.16b	v4, v4, v2
 	WORD $0x4e030084  // tbl.16b	v4, { v4 }, v3
 	WORD $0x4e71b884  // addv.8h	h4, v4
@@ -319,7 +301,7 @@ LBB0_44:
 	WORD $0x33103dcd  // bfi	w13, w14, #16, #16
 	WORD $0x2a0c01ad  // orr	w13, w13, w12
 	WORD $0x310005bf  // cmn	w13, #1
-	WORD $0x54fffc40  // b.eq	LBB0_42 $-120(%rip)
+	WORD $0x54000100  // b.eq	LBB0_47 $32(%rip)
 	WORD $0x8b09016b  // add	x11, x11, x9
 	WORD $0x71000508  // subs	w8, w8, #1
 	WORD $0x54fffde1  // b.ne	LBB0_44 $-68(%rip)
@@ -328,6 +310,12 @@ LBB0_46:
 	WORD $0xa940fbfd  // ldp	fp, lr, [sp, #8]
 	WORD $0x910083ff  // add	sp, sp, #32
 	WORD $0xd65f03c0  // ret
+LBB0_47:
+	WORD $0x8b0a0168  // add	x8, x11, x10
+	WORD $0x39400100  // ldrb	w0, [x8]
+	WORD $0xa940fbfd  // ldp	fp, lr, [sp, #8]
+	WORD $0x910083ff  // add	sp, sp, #32
+	WORD $0xd65f03c0  // ret
 
 TEXT ·__lookup_small_key(SB), NOSPLIT, $0-32
 	NO_LOCAL_POINTERS
diff --git a/internal/optcaching/fcache.go b/internal/optcaching/fcache.go
index 40b3e08..0100282 100644
--- a/internal/optcaching/fcache.go
+++ b/internal/optcaching/fcache.go
@@ -32,7 +32,7 @@ const _PaddingSize =  32
 
 type FieldLookup interface {
 	Set(fields []resolver.FieldMeta)
-	Get(name string) int
+	Get(name string, caseSensitive bool) int
 }
 
 func isAscii(s string) bool {
@@ -93,13 +93,15 @@ func (self *SmallFieldMap) Set(fields []resolver.FieldMeta) {
 	}
 }
 
-func (self *SmallFieldMap) Get(name string) int {
+func (self *SmallFieldMap) Get(name string, caseSensitive bool) int {
 	for i, k := range self.keys {
 		if len(k) == len(name) && k == name {
 			return i
 		}
 	}
-
+	if caseSensitive {
+		return -1
+	}
 	name = strings.ToLower(name)
 	for i, k := range self.lowerKeys {
 		if len(k) == len(name) && k == name {
@@ -153,16 +155,20 @@ const _HdrSlot = 33
 const _HdrSize = _HdrSlot * 5
 
 // use native SIMD to accelerate it
-func (self *NormalFieldMap) Get(name string) int {
+func (self *NormalFieldMap) Get(name string, caseSensitive bool) int {
 	// small keys use native C
 	if len(name) <= 32 {
-		 _ = native.LookupSmallKey
-		return native.LookupSmallKey(&name, &self.keys, self.lowOffset);
+		_ = native.LookupSmallKey
+		lowOffset := self.lowOffset
+		if caseSensitive {
+			lowOffset = -1
+		}
+		return native.LookupSmallKey(&name, &self.keys, lowOffset);
 	}
-	return self.getLongKey(name)
+	return self.getLongKey(name, caseSensitive)
 }
 
-func (self *NormalFieldMap) getLongKey(name string) int {
+func (self *NormalFieldMap) getLongKey(name string, caseSensitive bool) int {
 	for _, k := range self.longKeys {
 		if len(k.key) != len(name) {
 			continue;
@@ -172,6 +178,10 @@ func (self *NormalFieldMap) getLongKey(name string) int {
 		}
 	}
 
+	if caseSensitive {
+		return -1
+	}
+
 	lower := strings.ToLower(name)
 	for _, k := range self.longKeys {
 		if len(k.key) != len(name) {
@@ -329,11 +339,13 @@ type FallbackFieldMap struct {
 	 }
  }
  
- func (self *FallbackFieldMap) Get(name string) int {
+ func (self *FallbackFieldMap) Get(name string, caseSensitive bool) int {
 	 if i, ok := self.inner[name]; ok {
 		 return i
-	 } else {
+	 } else if !caseSensitive {
 		 return self.getCaseInsensitive(name)
+	 } else {
+		return -1
 	 }
  }
  
diff --git a/native/lookup_small_key.c b/native/lookup_small_key.c
index fcf41b4..07eff79 100644
--- a/native/lookup_small_key.c
+++ b/native/lookup_small_key.c
@@ -130,6 +130,10 @@ long lookup_small_key(GoString* key, GoSlice* table, long lower_off) {
     }
 
 case_sensitve:
+    if (lower_off == -1) {
+        return -_NOT_FOUND;
+    }
+    
     offset = lower_off + *(uint32_t*)(meta + 1);
     p = table->buf + offset;
     to_lower(lower, (uint8_t*)(key->buf), 32);



Test patch:
diff --git a/decoder/decoder_native_test.go b/decoder/decoder_native_test.go
index 9b435ca..62e85b5 100644
--- a/decoder/decoder_native_test.go
+++ b/decoder/decoder_native_test.go
@@ -33,6 +33,67 @@ 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)
+        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 {
diff --git a/decoder/decoder_test.go b/decoder/decoder_test.go
index c58ed97..6da7558 100644
--- a/decoder/decoder_test.go
+++ b/decoder/decoder_test.go
@@ -26,7 +26,6 @@ import (
 	"time"
 
 	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
 func TestMain(m *testing.M) {
@@ -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)