diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD index 7be13ed4715..3b7408f96b5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/BUILD @@ -22,6 +22,7 @@ go_library( deps = [ "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/json-iterator/go:go_default_library", + "//vendor/github.com/modern-go/reflect2: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/serializer/recognizer:go_default_library", diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go index 068d3f708e3..7b26567efa8 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go @@ -24,6 +24,7 @@ import ( "github.com/ghodss/yaml" jsoniter "github.com/json-iterator/go" + "github.com/modern-go/reflect2" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -68,43 +69,60 @@ type Serializer struct { var _ runtime.Serializer = &Serializer{} var _ recognizer.RecognizingDecoder = &Serializer{} -func init() { - // Force jsoniter to decode number to interface{} via ints, if possible. - decodeNumberAsInt64IfPossible := func(ptr unsafe.Pointer, iter *jsoniter.Iterator) { - switch iter.WhatIsNext() { - case jsoniter.NumberValue: - var number json.Number - iter.ReadVal(&number) - i64, err := strconv.ParseInt(string(number), 10, 64) - if err == nil { - *(*interface{})(ptr) = i64 - return - } - f64, err := strconv.ParseFloat(string(number), 64) - if err == nil { - *(*interface{})(ptr) = f64 - return - } - // Not much we can do here. - default: - *(*interface{})(ptr) = iter.Read() - } +type customNumberExtension struct { + jsoniter.DummyExtension +} + +func (cne *customNumberExtension) CreateDecoder(typ reflect2.Type) jsoniter.ValDecoder { + if typ.String() == "interface {}" { + return customNumberDecoder{} + } + return nil +} + +type customNumberDecoder struct { +} + +func (customNumberDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) { + switch iter.WhatIsNext() { + case jsoniter.NumberValue: + var number jsoniter.Number + iter.ReadVal(&number) + i64, err := strconv.ParseInt(string(number), 10, 64) + if err == nil { + *(*interface{})(ptr) = i64 + return + } + f64, err := strconv.ParseFloat(string(number), 64) + if err == nil { + *(*interface{})(ptr) = f64 + return + } + iter.ReportError("DecodeNumber", err.Error()) + default: + *(*interface{})(ptr) = iter.Read() } - 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{ + config := jsoniter.Config{ EscapeHTML: true, SortMapKeys: true, ValidateJsonRawMessage: true, CaseSensitive: true, }.Froze() + // Force jsoniter to decode number to interface{} via int64/float64, if possible. + config.RegisterExtension(&customNumberExtension{}) + return config } +// Private copy of jsoniter to try to shield against possible mutations +// from outside. Still does not protect from package level jsoniter.Register*() functions - someone calling them +// in some other library will mess with every usage of the jsoniter library in the whole program. +// See https://github.com/json-iterator/go/issues/265 var caseSensitiveJsonIterator = CaseSensitiveJsonIterator() // gvkWithDefaults returns group kind and version defaulting from provided default diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go index b3d629a063c..b05f8802f47 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json_test.go @@ -29,10 +29,11 @@ import ( ) type testDecodable struct { - Other string - Value int `json:"value"` - Spec DecodableSpec `json:"spec"` - gvk schema.GroupVersionKind + Other string + Value int `json:"value"` + Spec DecodableSpec `json:"spec"` + Interface interface{} `json:"interface"` + gvk schema.GroupVersionKind } // DecodableSpec has 15 fields. json-iterator treats struct with more than 10 @@ -242,6 +243,15 @@ func TestDecode(t *testing.T) { }, }, }, + // Error on invalid number + { + data: []byte(`{"kind":"Test","apiVersion":"other/blah","interface":1e1000}`), + creater: &mockCreater{obj: &testDecodable{}}, + expectedGVK: &schema.GroupVersionKind{Kind: "Test", Group: "other", Version: "blah"}, + errFn: func(err error) bool { + return strings.Contains(err.Error(), `json_test.testDecodable.Interface: DecodeNumber: strconv.ParseFloat: parsing "1e1000": value out of range`) + }, + }, // Unmarshalling is case-sensitive { // "VaLue" should have been "value"