2018-09-05 20:46:10 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package json
import (
"encoding/json"
"io"
"strconv"
"unsafe"
jsoniter "github.com/json-iterator/go"
2020-05-04 14:59:48 +00:00
"github.com/modern-go/reflect2"
"sigs.k8s.io/yaml"
2018-09-05 20:46:10 +00:00
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
"k8s.io/apimachinery/pkg/util/framer"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
)
// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer
// is not nil, the object has the group, version, and kind fields set.
2020-05-04 14:59:48 +00:00
// Deprecated: use NewSerializerWithOptions instead.
2018-09-05 20:46:10 +00:00
func NewSerializer ( meta MetaFactory , creater runtime . ObjectCreater , typer runtime . ObjectTyper , pretty bool ) * Serializer {
2020-05-04 14:59:48 +00:00
return NewSerializerWithOptions ( meta , creater , typer , SerializerOptions { false , pretty , false } )
2018-09-05 20:46:10 +00:00
}
// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer
// is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that
// matches JSON, and will error if constructs are used that do not serialize to JSON.
2020-05-04 14:59:48 +00:00
// Deprecated: use NewSerializerWithOptions instead.
2018-09-05 20:46:10 +00:00
func NewYAMLSerializer ( meta MetaFactory , creater runtime . ObjectCreater , typer runtime . ObjectTyper ) * Serializer {
2020-05-04 14:59:48 +00:00
return NewSerializerWithOptions ( meta , creater , typer , SerializerOptions { true , false , false } )
}
// NewSerializerWithOptions creates a JSON/YAML serializer that handles encoding versioned objects into the proper JSON/YAML
// form. If typer is not nil, the object has the group, version, and kind fields set. Options are copied into the Serializer
// and are immutable.
func NewSerializerWithOptions ( meta MetaFactory , creater runtime . ObjectCreater , typer runtime . ObjectTyper , options SerializerOptions ) * Serializer {
2018-09-05 20:46:10 +00:00
return & Serializer {
meta : meta ,
creater : creater ,
typer : typer ,
2020-05-04 14:59:48 +00:00
options : options ,
2018-09-05 20:46:10 +00:00
}
}
2020-05-04 14:59:48 +00:00
// SerializerOptions holds the options which are used to configure a JSON/YAML serializer.
// example:
// (1) To configure a JSON serializer, set `Yaml` to `false`.
// (2) To configure a YAML serializer, set `Yaml` to `true`.
// (3) To configure a strict serializer that can return strictDecodingError, set `Strict` to `true`.
type SerializerOptions struct {
// Yaml: configures the Serializer to work with JSON(false) or YAML(true).
// When `Yaml` is enabled, this serializer only supports the subset of YAML that
// matches JSON, and will error if constructs are used that do not serialize to JSON.
Yaml bool
// Pretty: configures a JSON enabled Serializer(`Yaml: false`) to produce human-readable output.
// This option is silently ignored when `Yaml` is `true`.
Pretty bool
// Strict: configures the Serializer to return strictDecodingError's when duplicate fields are present decoding JSON or YAML.
// Note that enabling this option is not as performant as the non-strict variant, and should not be used in fast paths.
Strict bool
}
2018-09-05 20:46:10 +00:00
type Serializer struct {
meta MetaFactory
2020-05-04 14:59:48 +00:00
options SerializerOptions
2018-09-05 20:46:10 +00:00
creater runtime . ObjectCreater
typer runtime . ObjectTyper
}
// Serializer implements Serializer
var _ runtime . Serializer = & Serializer { }
var _ recognizer . RecognizingDecoder = & Serializer { }
2020-05-04 14:59:48 +00:00
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 :
// init depth, if needed
if iter . Attachment == nil {
iter . Attachment = int ( 1 )
}
// remember current depth
originalAttachment := iter . Attachment
// increment depth before descending
if i , ok := iter . Attachment . ( int ) ; ok {
iter . Attachment = i + 1
if i > 10000 {
iter . ReportError ( "parse" , "exceeded max depth" )
2018-09-05 20:46:10 +00:00
return
}
}
2020-05-04 14:59:48 +00:00
* ( * interface { } ) ( ptr ) = iter . Read ( )
// restore current depth
iter . Attachment = originalAttachment
2018-09-05 20:46:10 +00:00
}
}
// 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 {
2020-05-04 14:59:48 +00:00
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
}
// StrictCaseSensitiveJsonIterator returns a jsoniterator API that's configured to be
// case-sensitive, but also disallows unknown fields when unmarshalling. It is compatible with
// the encoding/json standard library.
func StrictCaseSensitiveJsonIterator ( ) jsoniter . API {
config := jsoniter . Config {
2018-09-05 20:46:10 +00:00
EscapeHTML : true ,
SortMapKeys : true ,
ValidateJsonRawMessage : true ,
CaseSensitive : true ,
2020-05-04 14:59:48 +00:00
DisallowUnknownFields : true ,
2018-09-05 20:46:10 +00:00
} . Froze ( )
2020-05-04 14:59:48 +00:00
// Force jsoniter to decode number to interface{} via int64/float64, if possible.
config . RegisterExtension ( & customNumberExtension { } )
return config
2018-09-05 20:46:10 +00:00
}
2020-05-04 14:59:48 +00:00
// Private copies 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
2018-09-05 20:46:10 +00:00
var caseSensitiveJsonIterator = CaseSensitiveJsonIterator ( )
2020-05-04 14:59:48 +00:00
var strictCaseSensitiveJsonIterator = StrictCaseSensitiveJsonIterator ( )
2018-09-05 20:46:10 +00:00
// gvkWithDefaults returns group kind and version defaulting from provided default
func gvkWithDefaults ( actual , defaultGVK schema . GroupVersionKind ) schema . GroupVersionKind {
if len ( actual . Kind ) == 0 {
actual . Kind = defaultGVK . Kind
}
if len ( actual . Version ) == 0 && len ( actual . Group ) == 0 {
actual . Group = defaultGVK . Group
actual . Version = defaultGVK . Version
}
if len ( actual . Version ) == 0 && actual . Group == defaultGVK . Group {
actual . Version = defaultGVK . Version
}
return actual
}
// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then
// load that data into an object matching the desired schema kind or the provided into.
// If into is *runtime.Unknown, the raw data will be extracted and no decoding will be performed.
// If into is not registered with the typer, then the object will be straight decoded using normal JSON/YAML unmarshalling.
// If into is provided and the original data is not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk.
// If into is nil or data's gvk different from into's gvk, it will generate a new Object with ObjectCreater.New(gvk)
// On success or most errors, the method will return the calculated schema kind.
// The gvk calculate priority will be originalData > default gvk > into
func ( s * Serializer ) Decode ( originalData [ ] byte , gvk * schema . GroupVersionKind , into runtime . Object ) ( runtime . Object , * schema . GroupVersionKind , error ) {
if versioned , ok := into . ( * runtime . VersionedObjects ) ; ok {
into = versioned . Last ( )
obj , actual , err := s . Decode ( originalData , gvk , into )
if err != nil {
return nil , actual , err
}
versioned . Objects = [ ] runtime . Object { obj }
return versioned , actual , nil
}
data := originalData
2020-05-04 14:59:48 +00:00
if s . options . Yaml {
2018-09-05 20:46:10 +00:00
altered , err := yaml . YAMLToJSON ( data )
if err != nil {
return nil , nil , err
}
data = altered
}
actual , err := s . meta . Interpret ( data )
if err != nil {
return nil , nil , err
}
if gvk != nil {
* actual = gvkWithDefaults ( * actual , * gvk )
}
if unk , ok := into . ( * runtime . Unknown ) ; ok && unk != nil {
unk . Raw = originalData
unk . ContentType = runtime . ContentTypeJSON
unk . GetObjectKind ( ) . SetGroupVersionKind ( * actual )
return unk , actual , nil
}
if into != nil {
_ , isUnstructured := into . ( runtime . Unstructured )
types , _ , err := s . typer . ObjectKinds ( into )
switch {
case runtime . IsNotRegisteredError ( err ) , isUnstructured :
if err := caseSensitiveJsonIterator . Unmarshal ( data , into ) ; err != nil {
return nil , actual , err
}
return into , actual , nil
case err != nil :
return nil , actual , err
default :
* actual = gvkWithDefaults ( * actual , types [ 0 ] )
}
}
if len ( actual . Kind ) == 0 {
return nil , actual , runtime . NewMissingKindErr ( string ( originalData ) )
}
if len ( actual . Version ) == 0 {
return nil , actual , runtime . NewMissingVersionErr ( string ( originalData ) )
}
// use the target if necessary
obj , err := runtime . UseOrCreateObject ( s . typer , s . creater , * actual , into )
if err != nil {
return nil , actual , err
}
if err := caseSensitiveJsonIterator . Unmarshal ( data , obj ) ; err != nil {
return nil , actual , err
}
2020-05-04 14:59:48 +00:00
// If the deserializer is non-strict, return successfully here.
if ! s . options . Strict {
return obj , actual , nil
}
// In strict mode pass the data trough the YAMLToJSONStrict converter.
// This is done to catch duplicate fields regardless of encoding (JSON or YAML). For JSON data,
// the output would equal the input, unless there is a parsing error such as duplicate fields.
// As we know this was successful in the non-strict case, the only error that may be returned here
// is because of the newly-added strictness. hence we know we can return the typed strictDecoderError
// the actual error is that the object contains duplicate fields.
altered , err := yaml . YAMLToJSONStrict ( originalData )
if err != nil {
return nil , actual , runtime . NewStrictDecodingError ( err . Error ( ) , string ( originalData ) )
}
// As performance is not an issue for now for the strict deserializer (one has regardless to do
// the unmarshal twice), we take the sanitized, altered data that is guaranteed to have no duplicated
// fields, and unmarshal this into a copy of the already-populated obj. Any error that occurs here is
// due to that a matching field doesn't exist in the object. hence we can return a typed strictDecoderError,
// the actual error is that the object contains unknown field.
strictObj := obj . DeepCopyObject ( )
if err := strictCaseSensitiveJsonIterator . Unmarshal ( altered , strictObj ) ; err != nil {
return nil , actual , runtime . NewStrictDecodingError ( err . Error ( ) , string ( originalData ) )
}
// Always return the same object as the non-strict serializer to avoid any deviations.
2018-09-05 20:46:10 +00:00
return obj , actual , nil
}
// Encode serializes the provided object to the given writer.
func ( s * Serializer ) Encode ( obj runtime . Object , w io . Writer ) error {
2020-05-04 14:59:48 +00:00
if s . options . Yaml {
2018-09-05 20:46:10 +00:00
json , err := caseSensitiveJsonIterator . Marshal ( obj )
if err != nil {
return err
}
data , err := yaml . JSONToYAML ( json )
if err != nil {
return err
}
_ , err = w . Write ( data )
return err
}
2020-05-04 14:59:48 +00:00
if s . options . Pretty {
2018-09-05 20:46:10 +00:00
data , err := caseSensitiveJsonIterator . MarshalIndent ( obj , "" , " " )
if err != nil {
return err
}
_ , err = w . Write ( data )
return err
}
encoder := json . NewEncoder ( w )
return encoder . Encode ( obj )
}
// RecognizesData implements the RecognizingDecoder interface.
func ( s * Serializer ) RecognizesData ( peek io . Reader ) ( ok , unknown bool , err error ) {
2020-05-04 14:59:48 +00:00
if s . options . Yaml {
2018-09-05 20:46:10 +00:00
// we could potentially look for '---'
return false , true , nil
}
_ , _ , ok = utilyaml . GuessJSONStream ( peek , 2048 )
return ok , false , nil
}
// Framer is the default JSON framing behavior, with newlines delimiting individual objects.
var Framer = jsonFramer { }
type jsonFramer struct { }
// NewFrameWriter implements stream framing for this serializer
func ( jsonFramer ) NewFrameWriter ( w io . Writer ) io . Writer {
// we can write JSON objects directly to the writer, because they are self-framing
return w
}
// NewFrameReader implements stream framing for this serializer
func ( jsonFramer ) NewFrameReader ( r io . ReadCloser ) io . ReadCloser {
// we need to extract the JSON chunks of data to pass to Decode()
return framer . NewJSONFramedReader ( r )
}
2020-05-04 14:59:48 +00:00
// YAMLFramer is the default JSON framing behavior, with newlines delimiting individual objects.
2018-09-05 20:46:10 +00:00
var YAMLFramer = yamlFramer { }
type yamlFramer struct { }
// NewFrameWriter implements stream framing for this serializer
func ( yamlFramer ) NewFrameWriter ( w io . Writer ) io . Writer {
return yamlFrameWriter { w }
}
// NewFrameReader implements stream framing for this serializer
func ( yamlFramer ) NewFrameReader ( r io . ReadCloser ) io . ReadCloser {
// extract the YAML document chunks directly
return utilyaml . NewDocumentDecoder ( r )
}
type yamlFrameWriter struct {
w io . Writer
}
// Write separates each document with the YAML document separator (`---` followed by line
// break). Writers must write well formed YAML documents (include a final line break).
func ( w yamlFrameWriter ) Write ( data [ ] byte ) ( n int , err error ) {
if _ , err := w . w . Write ( [ ] byte ( "---\n" ) ) ; err != nil {
return 0 , err
}
return w . w . Write ( data )
}