Merge pull request #65034 from caesarxuchao/json-case-sensitive

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Make kubernetes json serializer case sensitive

This PR imported the latest jsoniterator library so that case sensitivity during unmarhsaling is optional. The PR also set Kubernetes json serializer to be case sensitive.

Kubernetes json serializer had been case sensitive for 1.1-1.7 as we were using ugorji. This PR restores the behavior.

Fix #64612.

```release-notes
Kubernetes json deserializer is now case-sensitive as it was before 1.8.
If your config files contains fields with wrong case, the config files will be now invalid.
```
pull/8/head
Kubernetes Submit Queue 2018-06-14 15:41:26 -07:00 committed by GitHub
commit a2de1398f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 151 additions and 73 deletions

4
Godeps/Godeps.json generated
View File

@ -2029,8 +2029,8 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Comment": "1.1.3-16-g2ddf6d7", "Comment": "1.1.3-22-gf2b4162",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/jteeuwen/go-bindata", "ImportPath": "github.com/jteeuwen/go-bindata",

View File

@ -58,7 +58,6 @@ go_library(
"//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library", "//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", "//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library",
"//pkg/util/pointer:go_default_library", "//pkg/util/pointer:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"//vendor/github.com/ugorji/go/codec:go_default_library", "//vendor/github.com/ugorji/go/codec:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",

View File

@ -23,7 +23,6 @@ import (
"strconv" "strconv"
"strings" "strings"
jsoniter "github.com/json-iterator/go"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -31,8 +30,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer"
) )
var json = jsoniter.ConfigCompatibleWithStandardLibrary
type configMutationFunc func(map[string]interface{}) error type configMutationFunc func(map[string]interface{}) error
// These migrations are a stop-gap until we get a properly-versioned configuration file for MasterConfiguration. // These migrations are a stop-gap until we get a properly-versioned configuration file for MasterConfiguration.

View File

@ -64,7 +64,7 @@ KubeProxy:
minSyncPeriod: 0s minSyncPeriod: 0s
syncPeriod: 30s syncPeriod: 30s
ipvs: ipvs:
ExcludeCIDRs: null excludeCIDRs: null
minSyncPeriod: 0s minSyncPeriod: 0s
scheduler: "" scheduler: ""
syncPeriod: 30s syncPeriod: 30s

View File

@ -53,7 +53,7 @@ kubeProxy:
minSyncPeriod: 0s minSyncPeriod: 0s
syncPeriod: 30s syncPeriod: 30s
ipvs: ipvs:
ExcludeCIDRs: null excludeCIDRs: null
minSyncPeriod: 0s minSyncPeriod: 0s
scheduler: "" scheduler: ""
syncPeriod: 30s syncPeriod: 30s

View File

@ -50,7 +50,7 @@ kubeProxy:
minSyncPeriod: 0s minSyncPeriod: 0s
syncPeriod: 30s syncPeriod: 30s
ipvs: ipvs:
ExcludeCIDRs: null excludeCIDRs: null
minSyncPeriod: 0s minSyncPeriod: 0s
scheduler: "" scheduler: ""
syncPeriod: 30s syncPeriod: 30s

View File

@ -54,7 +54,7 @@ kubeProxy:
minSyncPeriod: 0s minSyncPeriod: 0s
syncPeriod: 30s syncPeriod: 30s
ipvs: ipvs:
ExcludeCIDRs: null excludeCIDRs: null
minSyncPeriod: 0s minSyncPeriod: 0s
scheduler: "" scheduler: ""
syncPeriod: 30s syncPeriod: 30s

View File

@ -49,7 +49,7 @@ kubeProxy:
minSyncPeriod: 0s minSyncPeriod: 0s
syncPeriod: 30s syncPeriod: 30s
ipvs: ipvs:
ExcludeCIDRs: null excludeCIDRs: null
minSyncPeriod: 0s minSyncPeriod: 0s
scheduler: "" scheduler: ""
syncPeriod: 30s syncPeriod: 30s

View File

@ -49,7 +49,7 @@ kubeProxy:
minSyncPeriod: 0s minSyncPeriod: 0s
syncPeriod: 30s syncPeriod: 30s
ipvs: ipvs:
ExcludeCIDRs: null excludeCIDRs: null
minSyncPeriod: 0s minSyncPeriod: 0s
scheduler: "" scheduler: ""
syncPeriod: 30s syncPeriod: 30s

View File

@ -96,6 +96,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library", "//vendor/k8s.io/apimachinery/pkg/types:go_default_library",

View File

@ -37,6 +37,7 @@ import (
"k8s.io/apimachinery/pkg/conversion" "k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
k8s_json "k8s.io/apimachinery/pkg/runtime/serializer/json"
"k8s.io/apimachinery/pkg/runtime/serializer/streaming" "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
@ -560,9 +561,10 @@ func BenchmarkDecodeIntoJSONCodecGenConfigFast(b *testing.B) {
b.StopTimer() b.StopTimer()
} }
// BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary // BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary provides a
// provides a baseline for JSON decode performance // baseline for JSON decode performance with
// with jsoniter.ConfigCompatibleWithStandardLibrary // jsoniter.ConfigCompatibleWithStandardLibrary, but with case sensitivity set
// to true
func BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary(b *testing.B) { func BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary(b *testing.B) {
kcodec := testapi.Default.Codec() kcodec := testapi.Default.Codec()
items := benchmarkItems(b) items := benchmarkItems(b)
@ -577,9 +579,10 @@ func BenchmarkDecodeIntoJSONCodecGenConfigCompatibleWithStandardLibrary(b *testi
} }
b.ResetTimer() b.ResetTimer()
iter := k8s_json.CaseSensitiveJsonIterator()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
obj := v1.Pod{} obj := v1.Pod{}
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(encoded[i%width], &obj); err != nil { if err := iter.Unmarshal(encoded[i%width], &obj); err != nil {
b.Fatal(err) b.Fatal(err)
} }
} }

View File

@ -65,7 +65,7 @@ type KubeProxyIPVSConfiguration struct {
Scheduler string `json:"scheduler"` Scheduler string `json:"scheduler"`
// excludeCIDRs is a list of CIDR's which the ipvs proxier should not touch // excludeCIDRs is a list of CIDR's which the ipvs proxier should not touch
// when cleaning up ipvs services. // when cleaning up ipvs services.
ExcludeCIDRs []string ExcludeCIDRs []string `json:"excludeCIDRs"`
} }
// KubeProxyConntrackConfiguration contains conntrack settings for // KubeProxyConntrackConfiguration contains conntrack settings for

View File

@ -36,7 +36,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/modern-go/concurrent", "ImportPath": "github.com/modern-go/concurrent",

View File

@ -532,7 +532,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",

View File

@ -92,7 +92,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/modern-go/concurrent", "ImportPath": "github.com/modern-go/concurrent",

View File

@ -21,9 +21,9 @@ go_test(
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
], ],
) )

View File

@ -21,7 +21,7 @@ import (
"reflect" "reflect"
"testing" "testing"
jsoniter "github.com/json-iterator/go" k8s_json "k8s.io/apimachinery/pkg/runtime/serializer/json"
) )
type GroupVersionHolder struct { type GroupVersionHolder struct {
@ -47,7 +47,8 @@ func TestGroupVersionUnmarshalJSON(t *testing.T) {
t.Errorf("JSON codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV) t.Errorf("JSON codec failed to unmarshal input '%s': expected %+v, got %+v", c.input, c.expect, result.GV)
} }
// test the json-iterator codec // test the json-iterator codec
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(c.input, &result); err != nil { iter := k8s_json.CaseSensitiveJsonIterator()
if err := iter.Unmarshal(c.input, &result); err != nil {
t.Errorf("json-iterator codec failed to unmarshal input '%v': %v", c.input, err) t.Errorf("json-iterator codec failed to unmarshal input '%v': %v", c.input, err)
} }
if !reflect.DeepEqual(result.GV, c.expect) { if !reflect.DeepEqual(result.GV, c.expect) {

View File

@ -21,7 +21,7 @@ import (
"reflect" "reflect"
"testing" "testing"
jsoniter "github.com/json-iterator/go" k8s_json "k8s.io/apimachinery/pkg/runtime/serializer/json"
) )
func TestVerbsUgorjiMarshalJSON(t *testing.T) { func TestVerbsUgorjiMarshalJSON(t *testing.T) {
@ -45,7 +45,7 @@ func TestVerbsUgorjiMarshalJSON(t *testing.T) {
} }
} }
func TestVerbsUgorjiUnmarshalJSON(t *testing.T) { func TestVerbsUJsonIterUnmarshalJSON(t *testing.T) {
cases := []struct { cases := []struct {
input string input string
result APIResource result APIResource
@ -56,9 +56,10 @@ func TestVerbsUgorjiUnmarshalJSON(t *testing.T) {
{`{"verbs":["delete"]}`, APIResource{Verbs: Verbs([]string{"delete"})}}, {`{"verbs":["delete"]}`, APIResource{Verbs: Verbs([]string{"delete"})}},
} }
iter := k8s_json.CaseSensitiveJsonIterator()
for i, c := range cases { for i, c := range cases {
var result APIResource var result APIResource
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal([]byte(c.input), &result); err != nil { if err := iter.Unmarshal([]byte(c.input), &result); err != nil {
t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err) t.Errorf("[%d] Failed to unmarshal input '%v': %v", i, c.input, err)
} }
if !reflect.DeepEqual(result, c.result) { if !reflect.DeepEqual(result, c.result) {

View File

@ -93,6 +93,20 @@ func init() {
jsoniter.RegisterTypeDecoderFunc("interface {}", decodeNumberAsInt64IfPossible) jsoniter.RegisterTypeDecoderFunc("interface {}", decodeNumberAsInt64IfPossible)
} }
// CaseSensitiveJsonIterator returns a jsoniterator API that's configured to be
// case-sensitive when unmarshalling, and otherwise compatible with
// the encoding/json standard library.
func CaseSensitiveJsonIterator() jsoniter.API {
return jsoniter.Config{
EscapeHTML: true,
SortMapKeys: true,
ValidateJsonRawMessage: true,
CaseSensitive: true,
}.Froze()
}
var caseSensitiveJsonIterator = CaseSensitiveJsonIterator()
// gvkWithDefaults returns group kind and version defaulting from provided default // gvkWithDefaults returns group kind and version defaulting from provided default
func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind { func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
if len(actual.Kind) == 0 { if len(actual.Kind) == 0 {
@ -157,7 +171,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i
types, _, err := s.typer.ObjectKinds(into) types, _, err := s.typer.ObjectKinds(into)
switch { switch {
case runtime.IsNotRegisteredError(err), isUnstructured: case runtime.IsNotRegisteredError(err), isUnstructured:
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, into); err != nil { if err := caseSensitiveJsonIterator.Unmarshal(data, into); err != nil {
return nil, actual, err return nil, actual, err
} }
return into, actual, nil return into, actual, nil
@ -181,7 +195,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i
return nil, actual, err return nil, actual, err
} }
if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, obj); err != nil { if err := caseSensitiveJsonIterator.Unmarshal(data, obj); err != nil {
return nil, actual, err return nil, actual, err
} }
return obj, actual, nil return obj, actual, nil
@ -190,7 +204,7 @@ func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, i
// Encode serializes the provided object to the given writer. // Encode serializes the provided object to the given writer.
func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
if s.yaml { if s.yaml {
json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj) json, err := caseSensitiveJsonIterator.Marshal(obj)
if err != nil { if err != nil {
return err return err
} }
@ -203,7 +217,7 @@ func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
} }
if s.pretty { if s.pretty {
data, err := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(obj, "", " ") data, err := caseSensitiveJsonIterator.MarshalIndent(obj, "", " ")
if err != nil { if err != nil {
return err return err
} }

View File

@ -30,10 +30,31 @@ import (
type testDecodable struct { type testDecodable struct {
Other string Other string
Value int `json:"value"` Value int `json:"value"`
Spec DecodableSpec `json:"spec"`
gvk schema.GroupVersionKind gvk schema.GroupVersionKind
} }
// DecodableSpec has 15 fields. json-iterator treats struct with more than 10
// fields differently from struct that has less than 10 fields.
type DecodableSpec struct {
A int `json:"A"`
B int `json:"B"`
C int `json:"C"`
D int `json:"D"`
E int `json:"E"`
F int `json:"F"`
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"`
N int `json:"n"`
O int `json:"o"`
}
func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d } func (d *testDecodable) GetObjectKind() schema.ObjectKind { return d }
func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk } func (d *testDecodable) SetGroupVersionKind(gvk schema.GroupVersionKind) { d.gvk = gvk }
func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk } func (d *testDecodable) GroupVersionKind() schema.GroupVersionKind { return d.gvk }
@ -221,6 +242,28 @@ func TestDecode(t *testing.T) {
}, },
}, },
}, },
// Unmarshalling is case-sensitive
{
// "VaLue" should have been "value"
data: []byte(`{"kind":"Test","apiVersion":"other/blah","VaLue":1,"Other":"test"}`),
into: &testDecodable{},
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind(schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &testDecodable{
Other: "test",
},
},
// Unmarshalling is case-sensitive for big struct.
{
// "b" should have been "B", "I" should have been "i"
data: []byte(`{"kind":"Test","apiVersion":"other/blah","spec": {"A": 1, "b": 2, "h": 3, "I": 4}}`),
into: &testDecodable{},
typer: &mockTyper{err: runtime.NewNotRegisteredErrForKind(schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"})},
expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"},
expectedObject: &testDecodable{
Spec: DecodableSpec{A: 1, H: 3},
},
},
} }
for i, test := range testCases { for i, test := range testCases {

View File

@ -504,7 +504,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",

View File

@ -156,7 +156,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/modern-go/concurrent", "ImportPath": "github.com/modern-go/concurrent",

View File

@ -216,7 +216,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",

View File

@ -76,7 +76,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/modern-go/concurrent", "ImportPath": "github.com/modern-go/concurrent",

View File

@ -208,7 +208,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/mailru/easyjson/buffer", "ImportPath": "github.com/mailru/easyjson/buffer",

View File

@ -92,7 +92,7 @@
}, },
{ {
"ImportPath": "github.com/json-iterator/go", "ImportPath": "github.com/json-iterator/go",
"Rev": "2ddf6d758266fcb080a4f9e054b9f292c85e6798" "Rev": "f2b4162afba35581b6d4a50d3b8f34e33c144682"
}, },
{ {
"ImportPath": "github.com/modern-go/concurrent", "ImportPath": "github.com/modern-go/concurrent",

View File

@ -1,6 +1,12 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/json-iterator/go"
packages = ["."]
revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
version = "1.1.3"
[[projects]] [[projects]]
name = "github.com/modern-go/concurrent" name = "github.com/modern-go/concurrent"
packages = ["."] packages = ["."]
@ -16,6 +22,6 @@
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "ac7003b5a981716353a43055ab7d4c5357403cb30a60de2dbdeb446c1544beaa" inputs-digest = "56a0b9e9e61d2bc8af5e1b68537401b7f4d60805eda3d107058f3171aa5cf793"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -2,12 +2,13 @@ package jsoniter
import ( import (
"encoding/json" "encoding/json"
"github.com/modern-go/concurrent"
"github.com/modern-go/reflect2"
"io" "io"
"reflect" "reflect"
"sync" "sync"
"unsafe" "unsafe"
"github.com/modern-go/concurrent"
"github.com/modern-go/reflect2"
) )
// Config customize how the API should behave. // Config customize how the API should behave.
@ -23,6 +24,7 @@ type Config struct {
OnlyTaggedField bool OnlyTaggedField bool
ValidateJsonRawMessage bool ValidateJsonRawMessage bool
ObjectFieldMustBeSimpleString bool ObjectFieldMustBeSimpleString bool
CaseSensitive bool
} }
// API the public interface of this package. // API the public interface of this package.
@ -75,6 +77,7 @@ type frozenConfig struct {
extensions []Extension extensions []Extension
streamPool *sync.Pool streamPool *sync.Pool
iteratorPool *sync.Pool iteratorPool *sync.Pool
caseSensitive bool
} }
func (cfg *frozenConfig) initCache() { func (cfg *frozenConfig) initCache() {
@ -128,6 +131,7 @@ func (cfg Config) Froze() API {
objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString, objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString,
onlyTaggedField: cfg.OnlyTaggedField, onlyTaggedField: cfg.OnlyTaggedField,
disallowUnknownFields: cfg.DisallowUnknownFields, disallowUnknownFields: cfg.DisallowUnknownFields,
caseSensitive: cfg.CaseSensitive,
} }
api.streamPool = &sync.Pool{ api.streamPool = &sync.Pool{
New: func() interface{} { New: func() interface{} {

View File

@ -60,7 +60,7 @@ func (iter *Iterator) readFieldHash() int64 {
if b == '\\' { if b == '\\' {
iter.head = i iter.head = i
for _, b := range iter.readStringSlowPath() { for _, b := range iter.readStringSlowPath() {
if 'A' <= b && b <= 'Z' { if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
b += 'a' - 'A' b += 'a' - 'A'
} }
hash ^= int64(b) hash ^= int64(b)
@ -82,7 +82,7 @@ func (iter *Iterator) readFieldHash() int64 {
} }
return hash return hash
} }
if 'A' <= b && b <= 'Z' { if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
b += 'a' - 'A' b += 'a' - 'A'
} }
hash ^= int64(b) hash ^= int64(b)
@ -95,10 +95,14 @@ func (iter *Iterator) readFieldHash() int64 {
} }
} }
func calcHash(str string) int64 { func calcHash(str string, caseSensitive bool) int64 {
hash := int64(0x811c9dc5) hash := int64(0x811c9dc5)
for _, b := range str { for _, b := range str {
hash ^= int64(unicode.ToLower(b)) if caseSensitive {
hash ^= int64(b)
} else {
hash ^= int64(unicode.ToLower(b))
}
hash *= 0x1000193 hash *= 0x1000193
} }
return int64(hash) return int64(hash)

View File

@ -2,9 +2,10 @@ package jsoniter
import ( import (
"fmt" "fmt"
"github.com/modern-go/reflect2"
"reflect" "reflect"
"unsafe" "unsafe"
"github.com/modern-go/reflect2"
) )
// ValDecoder is an internal type registered to cache as needed. // ValDecoder is an internal type registered to cache as needed.
@ -40,6 +41,14 @@ type ctx struct {
decoders map[reflect2.Type]ValDecoder decoders map[reflect2.Type]ValDecoder
} }
func (b *ctx) caseSensitive() bool {
if b.frozenConfig == nil {
// default is case-insensitive
return false
}
return b.frozenConfig.caseSensitive
}
func (b *ctx) append(prefix string) *ctx { func (b *ctx) append(prefix string) *ctx {
return &ctx{ return &ctx{
frozenConfig: b.frozenConfig, frozenConfig: b.frozenConfig,

View File

@ -1,7 +1,6 @@
package jsoniter package jsoniter
import ( import (
"bytes"
"encoding/base64" "encoding/base64"
"reflect" "reflect"
"strconv" "strconv"
@ -418,20 +417,11 @@ func (codec *base64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
} }
switch iter.WhatIsNext() { switch iter.WhatIsNext() {
case StringValue: case StringValue:
encoding := base64.StdEncoding src := iter.ReadString()
src := iter.SkipAndReturnBytes() dst, err := base64.StdEncoding.DecodeString(src)
// New line characters (\r and \n) are ignored.
// Refer to https://golang.org/pkg/encoding/base64/#Encoding.Decode
src = bytes.Replace(src, []byte(`\r`), []byte{}, -1)
src = bytes.Replace(src, []byte(`\n`), []byte{}, -1)
src = src[1 : len(src)-1]
decodedLen := encoding.DecodedLen(len(src))
dst := make([]byte, decodedLen)
len, err := encoding.Decode(dst, src)
if err != nil { if err != nil {
iter.ReportError("decode base64", err.Error()) iter.ReportError("decode base64", err.Error())
} else { } else {
dst = dst[:len]
codec.sliceType.UnsafeSet(ptr, unsafe.Pointer(&dst)) codec.sliceType.UnsafeSet(ptr, unsafe.Pointer(&dst))
} }
case ArrayValue: case ArrayValue:

View File

@ -2,10 +2,11 @@ package jsoniter
import ( import (
"fmt" "fmt"
"github.com/modern-go/reflect2"
"io" "io"
"strings" "strings"
"unsafe" "unsafe"
"github.com/modern-go/reflect2"
) )
func decoderOfStruct(ctx *ctx, typ reflect2.Type) ValDecoder { func decoderOfStruct(ctx *ctx, typ reflect2.Type) ValDecoder {
@ -31,11 +32,15 @@ func decoderOfStruct(ctx *ctx, typ reflect2.Type) ValDecoder {
for k, binding := range bindings { for k, binding := range bindings {
fields[k] = binding.Decoder.(*structFieldDecoder) fields[k] = binding.Decoder.(*structFieldDecoder)
} }
for k, binding := range bindings {
if _, found := fields[strings.ToLower(k)]; !found { if !ctx.caseSensitive() {
fields[strings.ToLower(k)] = binding.Decoder.(*structFieldDecoder) for k, binding := range bindings {
if _, found := fields[strings.ToLower(k)]; !found {
fields[strings.ToLower(k)] = binding.Decoder.(*structFieldDecoder)
}
} }
} }
return createStructDecoder(ctx, typ, fields) return createStructDecoder(ctx, typ, fields)
} }
@ -46,12 +51,13 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
knownHash := map[int64]struct{}{ knownHash := map[int64]struct{}{
0: {}, 0: {},
} }
switch len(fields) { switch len(fields) {
case 0: case 0:
return &skipObjectDecoder{typ} return &skipObjectDecoder{typ}
case 1: case 1:
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -65,7 +71,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder1 *structFieldDecoder var fieldDecoder1 *structFieldDecoder
var fieldDecoder2 *structFieldDecoder var fieldDecoder2 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -88,7 +94,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder2 *structFieldDecoder var fieldDecoder2 *structFieldDecoder
var fieldDecoder3 *structFieldDecoder var fieldDecoder3 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -119,7 +125,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder3 *structFieldDecoder var fieldDecoder3 *structFieldDecoder
var fieldDecoder4 *structFieldDecoder var fieldDecoder4 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -156,7 +162,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder4 *structFieldDecoder var fieldDecoder4 *structFieldDecoder
var fieldDecoder5 *structFieldDecoder var fieldDecoder5 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -199,7 +205,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder5 *structFieldDecoder var fieldDecoder5 *structFieldDecoder
var fieldDecoder6 *structFieldDecoder var fieldDecoder6 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -248,7 +254,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder6 *structFieldDecoder var fieldDecoder6 *structFieldDecoder
var fieldDecoder7 *structFieldDecoder var fieldDecoder7 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -303,7 +309,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder7 *structFieldDecoder var fieldDecoder7 *structFieldDecoder
var fieldDecoder8 *structFieldDecoder var fieldDecoder8 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -364,7 +370,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder8 *structFieldDecoder var fieldDecoder8 *structFieldDecoder
var fieldDecoder9 *structFieldDecoder var fieldDecoder9 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -431,7 +437,7 @@ func createStructDecoder(ctx *ctx, typ reflect2.Type, fields map[string]*structF
var fieldDecoder9 *structFieldDecoder var fieldDecoder9 *structFieldDecoder
var fieldDecoder10 *structFieldDecoder var fieldDecoder10 *structFieldDecoder
for fieldName, fieldDecoder := range fields { for fieldName, fieldDecoder := range fields {
fieldHash := calcHash(fieldName) fieldHash := calcHash(fieldName, ctx.caseSensitive())
_, known := knownHash[fieldHash] _, known := knownHash[fieldHash]
if known { if known {
return &generalStructDecoder{typ, fields, false} return &generalStructDecoder{typ, fields, false}
@ -513,13 +519,13 @@ func (decoder *generalStructDecoder) decodeOneField(ptr unsafe.Pointer, iter *It
fieldBytes := iter.ReadStringAsSlice() fieldBytes := iter.ReadStringAsSlice()
field = *(*string)(unsafe.Pointer(&fieldBytes)) field = *(*string)(unsafe.Pointer(&fieldBytes))
fieldDecoder = decoder.fields[field] fieldDecoder = decoder.fields[field]
if fieldDecoder == nil { if fieldDecoder == nil && !iter.cfg.caseSensitive {
fieldDecoder = decoder.fields[strings.ToLower(field)] fieldDecoder = decoder.fields[strings.ToLower(field)]
} }
} else { } else {
field = iter.ReadString() field = iter.ReadString()
fieldDecoder = decoder.fields[field] fieldDecoder = decoder.fields[field]
if fieldDecoder == nil { if fieldDecoder == nil && !iter.cfg.caseSensitive {
fieldDecoder = decoder.fields[strings.ToLower(field)] fieldDecoder = decoder.fields[strings.ToLower(field)]
} }
} }