| // Copyright 2020 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build goexperiment.jsonv2 | |
| package json | |
| import ( | |
| "bytes" | |
| "encoding" | |
| "encoding/base32" | |
| "encoding/base64" | |
| "encoding/hex" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "math" | |
| "net" | |
| "net/netip" | |
| "reflect" | |
| "strconv" | |
| "strings" | |
| "testing" | |
| "time" | |
| "encoding/json/internal" | |
| "encoding/json/internal/jsonflags" | |
| "encoding/json/internal/jsonopts" | |
| "encoding/json/internal/jsontest" | |
| "encoding/json/internal/jsonwire" | |
| "encoding/json/jsontext" | |
| ) | |
| func newNonStringNameError(offset int64, pointer jsontext.Pointer) error { | |
| return &jsontext.SyntacticError{ByteOffset: offset, JSONPointer: pointer, Err: jsontext.ErrNonStringName} | |
| } | |
| func newInvalidCharacterError(prefix, where string, offset int64, pointer jsontext.Pointer) error { | |
| return &jsontext.SyntacticError{ByteOffset: offset, JSONPointer: pointer, Err: jsonwire.NewInvalidCharacterError(prefix, where)} | |
| } | |
| func newInvalidUTF8Error(offset int64, pointer jsontext.Pointer) error { | |
| return &jsontext.SyntacticError{ByteOffset: offset, JSONPointer: pointer, Err: jsonwire.ErrInvalidUTF8} | |
| } | |
| func newParseTimeError(layout, value, layoutElem, valueElem, message string) error { | |
| return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message} | |
| } | |
| func EM(err error) *SemanticError { | |
| return &SemanticError{action: "marshal", Err: err} | |
| } | |
| func EU(err error) *SemanticError { | |
| return &SemanticError{action: "unmarshal", Err: err} | |
| } | |
| func (e *SemanticError) withVal(val string) *SemanticError { | |
| e.JSONValue = jsontext.Value(val) | |
| return e | |
| } | |
| func (e *SemanticError) withPos(prefix string, pointer jsontext.Pointer) *SemanticError { | |
| e.ByteOffset = int64(len(prefix)) | |
| e.JSONPointer = pointer | |
| return e | |
| } | |
| func (e *SemanticError) withType(k jsontext.Kind, t reflect.Type) *SemanticError { | |
| e.JSONKind = k | |
| e.GoType = t | |
| return e | |
| } | |
| var ( | |
| errInvalidFormatFlag = errors.New(`invalid format flag "invalid"`) | |
| errSomeError = errors.New("some error") | |
| errMustNotCall = errors.New("must not call") | |
| ) | |
| func T[T any]() reflect.Type { return reflect.TypeFor[T]() } | |
| type ( | |
| jsonObject = map[string]any | |
| jsonArray = []any | |
| namedAny any | |
| namedBool bool | |
| namedString string | |
| NamedString string | |
| namedBytes []byte | |
| namedInt64 int64 | |
| namedUint64 uint64 | |
| namedFloat64 float64 | |
| namedByte byte | |
| netipAddr = netip.Addr | |
| recursiveMap map[string]recursiveMap | |
| recursiveSlice []recursiveSlice | |
| recursivePointer struct{ P *recursivePointer } | |
| structEmpty struct{} | |
| structConflicting struct { | |
| A string `json:"conflict"` | |
| B string `json:"conflict"` | |
| } | |
| structNoneExported struct { | |
| unexported string | |
| } | |
| structUnexportedIgnored struct { | |
| ignored string `json:"-"` | |
| } | |
| structMalformedTag struct { | |
| Malformed string `json:"\""` | |
| } | |
| structUnexportedTag struct { | |
| unexported string `json:"name"` | |
| } | |
| structExportedEmbedded struct { | |
| NamedString | |
| } | |
| structExportedEmbeddedTag struct { | |
| NamedString `json:"name"` | |
| } | |
| structUnexportedEmbedded struct { | |
| namedString | |
| } | |
| structUnexportedEmbeddedTag struct { | |
| namedString `json:"name"` | |
| } | |
| structUnexportedEmbeddedMethodTag struct { | |
| // netipAddr cannot be marshaled since the MarshalText method | |
| // cannot be called on an unexported field. | |
| netipAddr `json:"name"` | |
| // Bogus MarshalText and AppendText methods are declared on | |
| // structUnexportedEmbeddedMethodTag to prevent it from | |
| // implementing those method interfaces. | |
| } | |
| structUnexportedEmbeddedStruct struct { | |
| structOmitZeroAll | |
| FizzBuzz int | |
| structNestedAddr | |
| } | |
| structUnexportedEmbeddedStructPointer struct { | |
| *structOmitZeroAll | |
| FizzBuzz int | |
| *structNestedAddr | |
| } | |
| structNestedAddr struct { | |
| Addr netip.Addr | |
| } | |
| structIgnoredUnexportedEmbedded struct { | |
| namedString `json:"-"` | |
| } | |
| structNoCase struct { | |
| Aaa string `json:",case:strict"` | |
| AA_A string | |
| AaA string `json:",case:ignore"` | |
| AAa string `json:",case:ignore"` | |
| AAA string | |
| } | |
| structScalars struct { | |
| unexported bool | |
| Ignored bool `json:"-"` | |
| Bool bool | |
| String string | |
| Bytes []byte | |
| Int int64 | |
| Uint uint64 | |
| Float float64 | |
| } | |
| structSlices struct { | |
| unexported bool | |
| Ignored bool `json:"-"` | |
| SliceBool []bool | |
| SliceString []string | |
| SliceBytes [][]byte | |
| SliceInt []int64 | |
| SliceUint []uint64 | |
| SliceFloat []float64 | |
| } | |
| structMaps struct { | |
| unexported bool | |
| Ignored bool `json:"-"` | |
| MapBool map[string]bool | |
| MapString map[string]string | |
| MapBytes map[string][]byte | |
| MapInt map[string]int64 | |
| MapUint map[string]uint64 | |
| MapFloat map[string]float64 | |
| } | |
| structAll struct { | |
| Bool bool | |
| String string | |
| Bytes []byte | |
| Int int64 | |
| Uint uint64 | |
| Float float64 | |
| Map map[string]string | |
| StructScalars structScalars | |
| StructMaps structMaps | |
| StructSlices structSlices | |
| Slice []string | |
| Array [1]string | |
| Pointer *structAll | |
| Interface any | |
| } | |
| structOmitZeroAll struct { | |
| Bool bool `json:",omitzero"` | |
| String string `json:",omitzero"` | |
| Bytes []byte `json:",omitzero"` | |
| Int int64 `json:",omitzero"` | |
| Uint uint64 `json:",omitzero"` | |
| Float float64 `json:",omitzero"` | |
| Map map[string]string `json:",omitzero"` | |
| StructScalars structScalars `json:",omitzero"` | |
| StructMaps structMaps `json:",omitzero"` | |
| StructSlices structSlices `json:",omitzero"` | |
| Slice []string `json:",omitzero"` | |
| Array [1]string `json:",omitzero"` | |
| Pointer *structOmitZeroAll `json:",omitzero"` | |
| Interface any `json:",omitzero"` | |
| } | |
| structOmitZeroMethodAll struct { | |
| ValueAlwaysZero valueAlwaysZero `json:",omitzero"` | |
| ValueNeverZero valueNeverZero `json:",omitzero"` | |
| PointerAlwaysZero pointerAlwaysZero `json:",omitzero"` | |
| PointerNeverZero pointerNeverZero `json:",omitzero"` | |
| PointerValueAlwaysZero *valueAlwaysZero `json:",omitzero"` | |
| PointerValueNeverZero *valueNeverZero `json:",omitzero"` | |
| PointerPointerAlwaysZero *pointerAlwaysZero `json:",omitzero"` | |
| PointerPointerNeverZero *pointerNeverZero `json:",omitzero"` | |
| PointerPointerValueAlwaysZero **valueAlwaysZero `json:",omitzero"` | |
| PointerPointerValueNeverZero **valueNeverZero `json:",omitzero"` | |
| PointerPointerPointerAlwaysZero **pointerAlwaysZero `json:",omitzero"` | |
| PointerPointerPointerNeverZero **pointerNeverZero `json:",omitzero"` | |
| } | |
| structOmitZeroMethodInterfaceAll struct { | |
| ValueAlwaysZero isZeroer `json:",omitzero"` | |
| ValueNeverZero isZeroer `json:",omitzero"` | |
| PointerValueAlwaysZero isZeroer `json:",omitzero"` | |
| PointerValueNeverZero isZeroer `json:",omitzero"` | |
| PointerPointerAlwaysZero isZeroer `json:",omitzero"` | |
| PointerPointerNeverZero isZeroer `json:",omitzero"` | |
| } | |
| structOmitEmptyAll struct { | |
| Bool bool `json:",omitempty"` | |
| PointerBool *bool `json:",omitempty"` | |
| String string `json:",omitempty"` | |
| StringEmpty stringMarshalEmpty `json:",omitempty"` | |
| StringNonEmpty stringMarshalNonEmpty `json:",omitempty"` | |
| PointerString *string `json:",omitempty"` | |
| PointerStringEmpty *stringMarshalEmpty `json:",omitempty"` | |
| PointerStringNonEmpty *stringMarshalNonEmpty `json:",omitempty"` | |
| Bytes []byte `json:",omitempty"` | |
| BytesEmpty bytesMarshalEmpty `json:",omitempty"` | |
| BytesNonEmpty bytesMarshalNonEmpty `json:",omitempty"` | |
| PointerBytes *[]byte `json:",omitempty"` | |
| PointerBytesEmpty *bytesMarshalEmpty `json:",omitempty"` | |
| PointerBytesNonEmpty *bytesMarshalNonEmpty `json:",omitempty"` | |
| Float float64 `json:",omitempty"` | |
| PointerFloat *float64 `json:",omitempty"` | |
| Map map[string]string `json:",omitempty"` | |
| MapEmpty mapMarshalEmpty `json:",omitempty"` | |
| MapNonEmpty mapMarshalNonEmpty `json:",omitempty"` | |
| PointerMap *map[string]string `json:",omitempty"` | |
| PointerMapEmpty *mapMarshalEmpty `json:",omitempty"` | |
| PointerMapNonEmpty *mapMarshalNonEmpty `json:",omitempty"` | |
| Slice []string `json:",omitempty"` | |
| SliceEmpty sliceMarshalEmpty `json:",omitempty"` | |
| SliceNonEmpty sliceMarshalNonEmpty `json:",omitempty"` | |
| PointerSlice *[]string `json:",omitempty"` | |
| PointerSliceEmpty *sliceMarshalEmpty `json:",omitempty"` | |
| PointerSliceNonEmpty *sliceMarshalNonEmpty `json:",omitempty"` | |
| Pointer *structOmitZeroEmptyAll `json:",omitempty"` | |
| Interface any `json:",omitempty"` | |
| } | |
| structOmitZeroEmptyAll struct { | |
| Bool bool `json:",omitzero,omitempty"` | |
| String string `json:",omitzero,omitempty"` | |
| Bytes []byte `json:",omitzero,omitempty"` | |
| Int int64 `json:",omitzero,omitempty"` | |
| Uint uint64 `json:",omitzero,omitempty"` | |
| Float float64 `json:",omitzero,omitempty"` | |
| Map map[string]string `json:",omitzero,omitempty"` | |
| Slice []string `json:",omitzero,omitempty"` | |
| Array [1]string `json:",omitzero,omitempty"` | |
| Pointer *structOmitZeroEmptyAll `json:",omitzero,omitempty"` | |
| Interface any `json:",omitzero,omitempty"` | |
| } | |
| structStringifiedLegacy struct { | |
| Bool bool `json:",string"` | |
| String string `json:",string"` | |
| Int int64 `json:",string"` | |
| Uint uint64 `json:",string"` | |
| Float float64 `json:",string"` | |
| PointerBool *bool `json:",string"` | |
| PointerString *string `json:",string"` | |
| PointerInt *int64 `json:",string"` | |
| PointerUint *uint64 `json:",string"` | |
| PointerFloat *float64 `json:",string"` | |
| } | |
| structStringified struct { | |
| Int int64 `json:",string"` | |
| Uint uint64 `json:",string"` | |
| Float float64 `json:",string"` | |
| PointerInt *int64 `json:",string"` | |
| PointerUint *uint64 `json:",string"` | |
| PointerFloat *float64 `json:",string"` | |
| } | |
| structStringifiedBool struct { | |
| Bool bool `json:",string"` | |
| } | |
| structStringifiedString struct { | |
| String string `json:",string"` | |
| } | |
| structStringifiedBytes struct { | |
| Bytes []byte `json:",string"` | |
| } | |
| structStringifiedMap struct { | |
| Map map[string]string `json:",string"` | |
| } | |
| structStringifiedSlice struct { | |
| Slice []string `json:",string"` | |
| } | |
| structStringifiedArray struct { | |
| Array [1]string `json:",string"` | |
| } | |
| structStringifiedStruct struct { | |
| Struct structAll `json:",string"` | |
| } | |
| structStringifiedPointer struct { | |
| Pointer *structAll `json:",string"` | |
| } | |
| structStringifiedPointerPointerInt struct { | |
| Pointer **int `json:",string"` | |
| } | |
| structStringifiedInterface struct { | |
| Interface any `json:",string"` | |
| } | |
| structFormatBytes struct { | |
| Base16 []byte `json:",format:base16"` | |
| Base32 []byte `json:",format:base32"` | |
| Base32Hex []byte `json:",format:base32hex"` | |
| Base64 []byte `json:",format:base64"` | |
| Base64URL []byte `json:",format:base64url"` | |
| Array []byte `json:",format:array"` | |
| } | |
| structFormatArrayBytes struct { | |
| Base16 [4]byte `json:",format:base16"` | |
| Base32 [4]byte `json:",format:base32"` | |
| Base32Hex [4]byte `json:",format:base32hex"` | |
| Base64 [4]byte `json:",format:base64"` | |
| Base64URL [4]byte `json:",format:base64url"` | |
| Array [4]byte `json:",format:array"` | |
| Default [4]byte | |
| } | |
| structFormatFloats struct { | |
| NonFinite float64 `json:",format:nonfinite"` | |
| PointerNonFinite *float64 `json:",format:nonfinite"` | |
| } | |
| structFormatMaps struct { | |
| EmitNull map[string]string `json:",format:emitnull"` | |
| PointerEmitNull *map[string]string `json:",format:emitnull"` | |
| EmitEmpty map[string]string `json:",format:emitempty"` | |
| PointerEmitEmpty *map[string]string `json:",format:emitempty"` | |
| EmitDefault map[string]string | |
| PointerEmitDefault *map[string]string | |
| } | |
| structFormatSlices struct { | |
| EmitNull []string `json:",format:emitnull"` | |
| PointerEmitNull *[]string `json:",format:emitnull"` | |
| EmitEmpty []string `json:",format:emitempty"` | |
| PointerEmitEmpty *[]string `json:",format:emitempty"` | |
| EmitDefault []string | |
| PointerEmitDefault *[]string | |
| } | |
| structFormatInvalid struct { | |
| Bool bool `json:",omitzero,format:invalid"` | |
| String string `json:",omitzero,format:invalid"` | |
| Bytes []byte `json:",omitzero,format:invalid"` | |
| Int int64 `json:",omitzero,format:invalid"` | |
| Uint uint64 `json:",omitzero,format:invalid"` | |
| Float float64 `json:",omitzero,format:invalid"` | |
| Map map[string]string `json:",omitzero,format:invalid"` | |
| Struct structAll `json:",omitzero,format:invalid"` | |
| Slice []string `json:",omitzero,format:invalid"` | |
| Array [1]string `json:",omitzero,format:invalid"` | |
| Interface any `json:",omitzero,format:invalid"` | |
| } | |
| structDurationFormat struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:units"` | |
| D3 time.Duration `json:",format:sec"` | |
| D4 time.Duration `json:",string,format:sec"` | |
| D5 time.Duration `json:",format:milli"` | |
| D6 time.Duration `json:",string,format:milli"` | |
| D7 time.Duration `json:",format:micro"` | |
| D8 time.Duration `json:",string,format:micro"` | |
| D9 time.Duration `json:",format:nano"` | |
| D10 time.Duration `json:",string,format:nano"` | |
| D11 time.Duration `json:",format:iso8601"` | |
| } | |
| structTimeFormat struct { | |
| T1 time.Time | |
| T2 time.Time `json:",format:ANSIC"` | |
| T3 time.Time `json:",format:UnixDate"` | |
| T4 time.Time `json:",format:RubyDate"` | |
| T5 time.Time `json:",format:RFC822"` | |
| T6 time.Time `json:",format:RFC822Z"` | |
| T7 time.Time `json:",format:RFC850"` | |
| T8 time.Time `json:",format:RFC1123"` | |
| T9 time.Time `json:",format:RFC1123Z"` | |
| T10 time.Time `json:",format:RFC3339"` | |
| T11 time.Time `json:",format:RFC3339Nano"` | |
| T12 time.Time `json:",format:Kitchen"` | |
| T13 time.Time `json:",format:Stamp"` | |
| T14 time.Time `json:",format:StampMilli"` | |
| T15 time.Time `json:",format:StampMicro"` | |
| T16 time.Time `json:",format:StampNano"` | |
| T17 time.Time `json:",format:DateTime"` | |
| T18 time.Time `json:",format:DateOnly"` | |
| T19 time.Time `json:",format:TimeOnly"` | |
| T20 time.Time `json:",format:'2006-01-02'"` | |
| T21 time.Time `json:",format:'\"weird\"2006'"` | |
| T22 time.Time `json:",format:unix"` | |
| T23 time.Time `json:",string,format:unix"` | |
| T24 time.Time `json:",format:unixmilli"` | |
| T25 time.Time `json:",string,format:unixmilli"` | |
| T26 time.Time `json:",format:unixmicro"` | |
| T27 time.Time `json:",string,format:unixmicro"` | |
| T28 time.Time `json:",format:unixnano"` | |
| T29 time.Time `json:",string,format:unixnano"` | |
| } | |
| structTimeFormatStringInvalid struct { | |
| T time.Time `json:",string,format:RFC3339"` | |
| } | |
| structEmbedded struct { | |
| X structEmbeddedL1 `json:",embed"` | |
| *StructEmbed2 // implicit embed | |
| } | |
| structEmbeddedL1 struct { | |
| X *structEmbeddedL2 `json:",embed"` | |
| StructEmbed1 `json:",embed"` | |
| } | |
| structEmbeddedL2 struct{ A, B, C string } | |
| StructEmbed1 struct{ C, D, E string } | |
| StructEmbed2 struct{ E, F, G string } | |
| structEmbedTextValue struct { | |
| A int `json:",omitzero"` | |
| X jsontext.Value `json:",embed"` | |
| B int `json:",omitzero"` | |
| } | |
| structEmbedPointerTextValue struct { | |
| A int `json:",omitzero"` | |
| X *jsontext.Value `json:",embed"` | |
| B int `json:",omitzero"` | |
| } | |
| structEmbedPointerEmbedTextValue struct { | |
| X *struct { | |
| A int | |
| X jsontext.Value `json:",embed"` | |
| } `json:",embed"` | |
| } | |
| structEmbedEmbedPointerTextValue struct { | |
| X struct { | |
| X *jsontext.Value `json:",embed"` | |
| } `json:",embed"` | |
| } | |
| structEmbedMapStringAny struct { | |
| A int `json:",omitzero"` | |
| X jsonObject `json:",embed"` | |
| B int `json:",omitzero"` | |
| } | |
| structEmbedPointerMapStringAny struct { | |
| A int `json:",omitzero"` | |
| X *jsonObject `json:",embed"` | |
| B int `json:",omitzero"` | |
| } | |
| structEmbedPointerEmbedMapStringAny struct { | |
| X *struct { | |
| A int | |
| X jsonObject `json:",embed"` | |
| } `json:",embed"` | |
| } | |
| structEmbedEmbedPointerMapStringAny struct { | |
| X struct { | |
| X *jsonObject `json:",embed"` | |
| } `json:",embed"` | |
| } | |
| structEmbedMapStringInt struct { | |
| X map[string]int `json:",embed"` | |
| } | |
| structEmbedMapNamedStringInt struct { | |
| X map[namedString]int `json:",embed"` | |
| } | |
| structEmbedMapNamedStringAny struct { | |
| A int `json:",omitzero"` | |
| X map[namedString]any `json:",embed"` | |
| B int `json:",omitzero"` | |
| } | |
| structNoCaseEmbedTextValue struct { | |
| AAA string `json:",omitempty,case:strict"` | |
| AA_b string `json:",omitempty"` | |
| AaA string `json:",omitempty,case:ignore"` | |
| AAa string `json:",omitempty,case:ignore"` | |
| Aaa string `json:",omitempty"` | |
| X jsontext.Value `json:",embed"` | |
| } | |
| structNoCaseEmbedMapStringAny struct { | |
| AAA string `json:",omitempty"` | |
| AaA string `json:",omitempty,case:ignore"` | |
| AAa string `json:",omitempty,case:ignore"` | |
| Aaa string `json:",omitempty"` | |
| X jsonObject `json:",embed"` | |
| } | |
| allMethods struct { | |
| method string // the method that was called | |
| value []byte // the raw value to provide or store | |
| } | |
| allMethodsExceptJSONv2 struct { | |
| allMethods | |
| MarshalJSONTo struct{} // cancel out MarshalJSONTo method with collision | |
| UnmarshalJSONFrom struct{} // cancel out UnmarshalJSONFrom method with collision | |
| } | |
| allMethodsExceptJSONv1 struct { | |
| allMethods | |
| MarshalJSON struct{} // cancel out MarshalJSON method with collision | |
| UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision | |
| } | |
| allMethodsExceptText struct { | |
| allMethods | |
| MarshalText struct{} // cancel out MarshalText method with collision | |
| UnmarshalText struct{} // cancel out UnmarshalText method with collision | |
| } | |
| onlyMethodJSONv2 struct { | |
| allMethods | |
| MarshalJSON struct{} // cancel out MarshalJSON method with collision | |
| UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision | |
| MarshalText struct{} // cancel out MarshalText method with collision | |
| UnmarshalText struct{} // cancel out UnmarshalText method with collision | |
| } | |
| onlyMethodJSONv1 struct { | |
| allMethods | |
| MarshalJSONTo struct{} // cancel out MarshalJSONTo method with collision | |
| UnmarshalJSONFrom struct{} // cancel out UnmarshalJSONFrom method with collision | |
| MarshalText struct{} // cancel out MarshalText method with collision | |
| UnmarshalText struct{} // cancel out UnmarshalText method with collision | |
| } | |
| onlyMethodText struct { | |
| allMethods | |
| MarshalJSONTo struct{} // cancel out MarshalJSONTo method with collision | |
| UnmarshalJSONFrom struct{} // cancel out UnmarshalJSONFrom method with collision | |
| MarshalJSON struct{} // cancel out MarshalJSON method with collision | |
| UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision | |
| } | |
| unsupportedMethodJSONv2 map[string]int | |
| structMethodJSONv2 struct{ value string } | |
| structMethodJSONv1 struct{ value string } | |
| structMethodText struct{ value string } | |
| marshalJSONv2Func func(*jsontext.Encoder) error | |
| marshalJSONv1Func func() ([]byte, error) | |
| appendTextFunc func([]byte) ([]byte, error) | |
| marshalTextFunc func() ([]byte, error) | |
| unmarshalJSONv2Func func(*jsontext.Decoder) error | |
| unmarshalJSONv1Func func([]byte) error | |
| unmarshalTextFunc func([]byte) error | |
| nocaseString string | |
| stringMarshalEmpty string | |
| stringMarshalNonEmpty string | |
| bytesMarshalEmpty []byte | |
| bytesMarshalNonEmpty []byte | |
| mapMarshalEmpty map[string]string | |
| mapMarshalNonEmpty map[string]string | |
| sliceMarshalEmpty []string | |
| sliceMarshalNonEmpty []string | |
| valueAlwaysZero string | |
| valueNeverZero string | |
| pointerAlwaysZero string | |
| pointerNeverZero string | |
| valueStringer struct{} | |
| pointerStringer struct{} | |
| cyclicA struct { | |
| B1 cyclicB `json:",embed"` | |
| B2 cyclicB `json:",embed"` | |
| } | |
| cyclicB struct { | |
| F int | |
| A *cyclicA `json:",embed"` | |
| } | |
| ) | |
| func (structUnexportedEmbeddedMethodTag) MarshalText() {} | |
| func (structUnexportedEmbeddedMethodTag) AppendText() {} | |
| func (p *allMethods) MarshalJSONTo(enc *jsontext.Encoder) error { | |
| if got, want := "MarshalJSONTo", p.method; got != want { | |
| return fmt.Errorf("called wrong method: got %v, want %v", got, want) | |
| } | |
| return enc.WriteValue(p.value) | |
| } | |
| func (p *allMethods) MarshalJSON() ([]byte, error) { | |
| if got, want := "MarshalJSON", p.method; got != want { | |
| return nil, fmt.Errorf("called wrong method: got %v, want %v", got, want) | |
| } | |
| return p.value, nil | |
| } | |
| func (p *allMethods) MarshalText() ([]byte, error) { | |
| if got, want := "MarshalText", p.method; got != want { | |
| return nil, fmt.Errorf("called wrong method: got %v, want %v", got, want) | |
| } | |
| return p.value, nil | |
| } | |
| func (p *allMethods) UnmarshalJSONFrom(dec *jsontext.Decoder) error { | |
| p.method = "UnmarshalJSONFrom" | |
| val, err := dec.ReadValue() | |
| p.value = val | |
| return err | |
| } | |
| func (p *allMethods) UnmarshalJSON(val []byte) error { | |
| p.method = "UnmarshalJSON" | |
| p.value = val | |
| return nil | |
| } | |
| func (p *allMethods) UnmarshalText(val []byte) error { | |
| p.method = "UnmarshalText" | |
| p.value = val | |
| return nil | |
| } | |
| func (s *unsupportedMethodJSONv2) MarshalJSONTo(enc *jsontext.Encoder) error { | |
| (*s)["called"] += 1 | |
| return errors.ErrUnsupported | |
| } | |
| func (s *unsupportedMethodJSONv2) UnmarshalJSONFrom(dec *jsontext.Decoder) error { | |
| (*s)["called"] += 1 | |
| return errors.ErrUnsupported | |
| } | |
| func (s structMethodJSONv2) MarshalJSONTo(enc *jsontext.Encoder) error { | |
| return enc.WriteToken(jsontext.String(s.value)) | |
| } | |
| func (s *structMethodJSONv2) UnmarshalJSONFrom(dec *jsontext.Decoder) error { | |
| tok, err := dec.ReadToken() | |
| if err != nil { | |
| return err | |
| } | |
| if k := tok.Kind(); k != '"' { | |
| return EU(nil).withType(k, T[structMethodJSONv2]()) | |
| } | |
| s.value = tok.String() | |
| return nil | |
| } | |
| func (s structMethodJSONv1) MarshalJSON() ([]byte, error) { | |
| return jsontext.AppendQuote(nil, s.value) | |
| } | |
| func (s *structMethodJSONv1) UnmarshalJSON(b []byte) error { | |
| if k := jsontext.Value(b).Kind(); k != '"' { | |
| return EU(nil).withType(k, T[structMethodJSONv1]()) | |
| } | |
| b, _ = jsontext.AppendUnquote(nil, b) | |
| s.value = string(b) | |
| return nil | |
| } | |
| func (s structMethodText) MarshalText() ([]byte, error) { | |
| return []byte(s.value), nil | |
| } | |
| func (s *structMethodText) UnmarshalText(b []byte) error { | |
| s.value = string(b) | |
| return nil | |
| } | |
| func (f marshalJSONv2Func) MarshalJSONTo(enc *jsontext.Encoder) error { | |
| return f(enc) | |
| } | |
| func (f marshalJSONv1Func) MarshalJSON() ([]byte, error) { | |
| return f() | |
| } | |
| func (f appendTextFunc) AppendText(b []byte) ([]byte, error) { | |
| return f(b) | |
| } | |
| func (f marshalTextFunc) MarshalText() ([]byte, error) { | |
| return f() | |
| } | |
| func (f unmarshalJSONv2Func) UnmarshalJSONFrom(dec *jsontext.Decoder) error { | |
| return f(dec) | |
| } | |
| func (f unmarshalJSONv1Func) UnmarshalJSON(b []byte) error { | |
| return f(b) | |
| } | |
| func (f unmarshalTextFunc) UnmarshalText(b []byte) error { | |
| return f(b) | |
| } | |
| func (k nocaseString) MarshalText() ([]byte, error) { | |
| return []byte(strings.ToLower(string(k))), nil | |
| } | |
| func (k *nocaseString) UnmarshalText(b []byte) error { | |
| *k = nocaseString(strings.ToLower(string(b))) | |
| return nil | |
| } | |
| func (stringMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`""`), nil } | |
| func (stringMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`"value"`), nil } | |
| func (bytesMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`[]`), nil } | |
| func (bytesMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`["value"]`), nil } | |
| func (mapMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`{}`), nil } | |
| func (mapMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`{"key":"value"}`), nil } | |
| func (sliceMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`[]`), nil } | |
| func (sliceMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`["value"]`), nil } | |
| func (valueAlwaysZero) IsZero() bool { return true } | |
| func (valueNeverZero) IsZero() bool { return false } | |
| func (*pointerAlwaysZero) IsZero() bool { return true } | |
| func (*pointerNeverZero) IsZero() bool { return false } | |
| func (valueStringer) String() string { return "" } | |
| func (*pointerStringer) String() string { return "" } | |
| func addr[T any](v T) *T { | |
| return &v | |
| } | |
| func mustParseTime(layout, value string) time.Time { | |
| t, err := time.Parse(layout, value) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return t | |
| } | |
| // invalidFormatOption is an option where the Format field is set, | |
| // but jsonflags.FormatTag has been cleared. In such a case, | |
| // the Format field should be ignored. | |
| var invalidFormatOption = &jsonopts.Struct{ | |
| ArshalValues: jsonopts.ArshalValues{Format: "invalid"}, | |
| } | |
| func TestMarshal(t *testing.T) { | |
| tests := []struct { | |
| name jsontest.CaseName | |
| opts []Options | |
| in any | |
| want string | |
| wantErr error | |
| canonicalize bool // canonicalize the output before comparing? | |
| useWriter bool // call MarshalWrite instead of Marshal | |
| }{{ | |
| name: jsontest.Name("Nil"), | |
| in: nil, | |
| want: `null`, | |
| }, { | |
| name: jsontest.Name("Bools"), | |
| in: []bool{false, true}, | |
| want: `[false,true]`, | |
| }, { | |
| name: jsontest.Name("Bools/Named"), | |
| in: []namedBool{false, true}, | |
| want: `[false,true]`, | |
| }, { | |
| name: jsontest.Name("Bools/NotStringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: []bool{false, true}, | |
| want: `[false,true]`, | |
| }, { | |
| name: jsontest.Name("Bools/StringifiedBool/False"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: false, | |
| want: `"false"`, | |
| }, { | |
| name: jsontest.Name("Bools/StringifiedBool/True"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: true, | |
| want: `"true"`, | |
| }, { | |
| name: jsontest.Name("Bools/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Strings"), | |
| in: []string{"", "hello", "世界"}, | |
| want: `["","hello","世界"]`, | |
| }, { | |
| name: jsontest.Name("Strings/Named"), | |
| in: []namedString{"", "hello", "世界"}, | |
| want: `["","hello","世界"]`, | |
| }, { | |
| name: jsontest.Name("Strings/StringifiedString"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: "hello", | |
| want: `"\"hello\""`, | |
| }, { | |
| name: jsontest.Name("Strings/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: "string", | |
| want: `"string"`, | |
| }, { | |
| name: jsontest.Name("Bytes"), | |
| in: [][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}, | |
| want: `["","","AQ==","AQI=","AQID"]`, | |
| }, { | |
| name: jsontest.Name("Bytes/FormatNilSliceAsNull"), | |
| opts: []Options{FormatNilSliceAsNull(true)}, | |
| in: [][]byte{nil, {}}, | |
| want: `[null,""]`, | |
| }, { | |
| name: jsontest.Name("Bytes/Large"), | |
| in: []byte("the quick brown fox jumped over the lazy dog and ate the homework that I spent so much time on."), | |
| want: `"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2cgYW5kIGF0ZSB0aGUgaG9tZXdvcmsgdGhhdCBJIHNwZW50IHNvIG11Y2ggdGltZSBvbi4="`, | |
| }, { | |
| name: jsontest.Name("Bytes/Named"), | |
| in: []namedBytes{nil, {}, {1}, {1, 2}, {1, 2, 3}}, | |
| want: `["","","AQ==","AQI=","AQID"]`, | |
| }, { | |
| name: jsontest.Name("Bytes/NotStringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: [][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}, | |
| want: `["","","AQ==","AQI=","AQID"]`, | |
| }, { | |
| // NOTE: []namedByte is not assignable to []byte, | |
| // so the following should be treated as a slice of uints. | |
| name: jsontest.Name("Bytes/Invariant"), | |
| in: [][]namedByte{nil, {}, {1}, {1, 2}, {1, 2, 3}}, | |
| want: `[[],[],[1],[1,2],[1,2,3]]`, | |
| }, { | |
| // NOTE: This differs in behavior from v1, | |
| // but keeps the representation of slices and arrays more consistent. | |
| name: jsontest.Name("Bytes/ByteArray"), | |
| in: [5]byte{'h', 'e', 'l', 'l', 'o'}, | |
| want: `"aGVsbG8="`, | |
| }, { | |
| // NOTE: []namedByte is not assignable to []byte, | |
| // so the following should be treated as an array of uints. | |
| name: jsontest.Name("Bytes/NamedByteArray"), | |
| in: [5]namedByte{'h', 'e', 'l', 'l', 'o'}, | |
| want: `[104,101,108,108,111]`, | |
| }, { | |
| name: jsontest.Name("Bytes/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: []byte("hello"), | |
| want: `"aGVsbG8="`, | |
| }, { | |
| name: jsontest.Name("Ints"), | |
| in: []any{ | |
| int(0), int8(math.MinInt8), int16(math.MinInt16), int32(math.MinInt32), int64(math.MinInt64), namedInt64(-6464), | |
| }, | |
| want: `[0,-128,-32768,-2147483648,-9223372036854775808,-6464]`, | |
| }, { | |
| name: jsontest.Name("Ints/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: []any{ | |
| int(0), int8(math.MinInt8), int16(math.MinInt16), int32(math.MinInt32), int64(math.MinInt64), namedInt64(-6464), | |
| }, | |
| want: `["0","-128","-32768","-2147483648","-9223372036854775808","-6464"]`, | |
| }, { | |
| name: jsontest.Name("Ints/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: int(0), | |
| want: `0`, | |
| }, { | |
| name: jsontest.Name("Uints"), | |
| in: []any{ | |
| uint(0), uint8(math.MaxUint8), uint16(math.MaxUint16), uint32(math.MaxUint32), uint64(math.MaxUint64), namedUint64(6464), uintptr(1234), | |
| }, | |
| want: `[0,255,65535,4294967295,18446744073709551615,6464,1234]`, | |
| }, { | |
| name: jsontest.Name("Uints/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: []any{ | |
| uint(0), uint8(math.MaxUint8), uint16(math.MaxUint16), uint32(math.MaxUint32), uint64(math.MaxUint64), namedUint64(6464), | |
| }, | |
| want: `["0","255","65535","4294967295","18446744073709551615","6464"]`, | |
| }, { | |
| name: jsontest.Name("Uints/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: uint(0), | |
| want: `0`, | |
| }, { | |
| name: jsontest.Name("Floats"), | |
| in: []any{ | |
| float32(math.MaxFloat32), float64(math.MaxFloat64), namedFloat64(64.64), | |
| }, | |
| want: `[3.4028235e+38,1.7976931348623157e+308,64.64]`, | |
| }, { | |
| name: jsontest.Name("Floats/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: []any{ | |
| float32(math.MaxFloat32), float64(math.MaxFloat64), namedFloat64(64.64), | |
| }, | |
| want: `["3.4028235e+38","1.7976931348623157e+308","64.64"]`, | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/NaN"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: math.NaN(), | |
| wantErr: EM(fmt.Errorf("unsupported value: %v", math.NaN())).withType(0, float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/PositiveInfinity"), | |
| in: math.Inf(+1), | |
| wantErr: EM(fmt.Errorf("unsupported value: %v", math.Inf(+1))).withType(0, float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/NegativeInfinity"), | |
| in: math.Inf(-1), | |
| wantErr: EM(fmt.Errorf("unsupported value: %v", math.Inf(-1))).withType(0, float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: float64(0), | |
| want: `0`, | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Bool"), | |
| in: map[bool]string{false: "value"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, boolType), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/NamedBool"), | |
| in: map[namedBool]string{false: "value"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[namedBool]()), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Array"), | |
| in: map[[1]string]string{{"key"}: "value"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Channel"), | |
| in: map[chan string]string{make(chan string): "value"}, | |
| want: `{`, | |
| wantErr: EM(nil).withPos(`{`, "").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Int"), | |
| in: map[int64]string{math.MinInt64: "MinInt64", 0: "Zero", math.MaxInt64: "MaxInt64"}, | |
| canonicalize: true, | |
| want: `{"-9223372036854775808":"MinInt64","0":"Zero","9223372036854775807":"MaxInt64"}`, | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/PointerInt"), | |
| in: map[*int64]string{addr(int64(math.MinInt64)): "MinInt64", addr(int64(0)): "Zero", addr(int64(math.MaxInt64)): "MaxInt64"}, | |
| canonicalize: true, | |
| want: `{"-9223372036854775808":"MinInt64","0":"Zero","9223372036854775807":"MaxInt64"}`, | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/PointerInt"), | |
| in: map[*int64]string{addr(int64(0)): "0", addr(int64(0)): "0"}, | |
| canonicalize: true, | |
| want: `{"0":"0"`, | |
| wantErr: newDuplicateNameError("", []byte(`"0"`), len64(`{"0":"0",`)), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/NamedInt"), | |
| in: map[namedInt64]string{math.MinInt64: "MinInt64", 0: "Zero", math.MaxInt64: "MaxInt64"}, | |
| canonicalize: true, | |
| want: `{"-9223372036854775808":"MinInt64","0":"Zero","9223372036854775807":"MaxInt64"}`, | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Uint"), | |
| in: map[uint64]string{0: "Zero", math.MaxUint64: "MaxUint64"}, | |
| canonicalize: true, | |
| want: `{"0":"Zero","18446744073709551615":"MaxUint64"}`, | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/NamedUint"), | |
| in: map[namedUint64]string{0: "Zero", math.MaxUint64: "MaxUint64"}, | |
| canonicalize: true, | |
| want: `{"0":"Zero","18446744073709551615":"MaxUint64"}`, | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Float"), | |
| in: map[float64]string{3.14159: "value"}, | |
| want: `{"3.14159":"value"}`, | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Float/NaN"), | |
| in: map[float64]string{math.NaN(): "NaN", math.NaN(): "NaN"}, | |
| want: `{`, | |
| wantErr: EM(errors.New("unsupported value: NaN")).withPos(`{`, "").withType(0, float64Type), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Interface"), | |
| in: map[any]any{ | |
| "key": "key", | |
| namedInt64(-64): int32(-32), | |
| namedUint64(+64): uint32(+32), | |
| namedFloat64(64.64): float32(32.32), | |
| }, | |
| canonicalize: true, | |
| want: `{"-64":-32,"64":32,"64.64":32.32,"key":"key"}`, | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/String/AllowInvalidUTF8+AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, | |
| in: map[string]string{"\x80": "", "\x81": ""}, | |
| want: `{"�":"","�":""}`, | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/String/AllowInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: map[string]string{"\x80": "", "\x81": ""}, | |
| want: `{"�":""`, | |
| wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":"",`)), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/NoCaseString/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| in: map[nocaseString]string{"hello": "", "HELLO": ""}, | |
| want: `{"hello":"","hello":""}`, | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/NoCaseString"), | |
| in: map[nocaseString]string{"hello": "", "HELLO": ""}, | |
| want: `{"hello":""`, | |
| wantErr: EM(newDuplicateNameError("", []byte(`"hello"`), len64(`{"hello":"",`))).withPos(`{"hello":"",`, "").withType(0, T[nocaseString]()), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/NaNs/Deterministic+AllowDuplicateNames"), | |
| opts: []Options{ | |
| WithMarshalers( | |
| MarshalFunc(func(v float64) ([]byte, error) { return []byte(`"NaN"`), nil }), | |
| ), | |
| Deterministic(true), | |
| jsontext.AllowDuplicateNames(true), | |
| }, | |
| in: map[float64]string{math.NaN(): "NaN", math.NaN(): "NaN"}, | |
| want: `{"NaN":"NaN","NaN":"NaN"}`, | |
| }, { | |
| name: jsontest.Name("Maps/InvalidValue/Channel"), | |
| in: map[string]chan string{ | |
| "key": nil, | |
| }, | |
| want: `{"key"`, | |
| wantErr: EM(nil).withPos(`{"key":`, "/key").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Maps/String/Deterministic"), | |
| opts: []Options{Deterministic(true)}, | |
| in: map[string]int{"a": 0, "b": 1, "c": 2}, | |
| want: `{"a":0,"b":1,"c":2}`, | |
| }, { | |
| name: jsontest.Name("Maps/String/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), | |
| opts: []Options{ | |
| Deterministic(true), | |
| jsontext.AllowInvalidUTF8(true), | |
| jsontext.AllowDuplicateNames(false), | |
| }, | |
| in: map[string]int{"\xff": 0, "\xfe": 1}, | |
| want: `{"�":1`, | |
| wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":1,`)), | |
| }, { | |
| name: jsontest.Name("Maps/String/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), | |
| opts: []Options{ | |
| Deterministic(true), | |
| jsontext.AllowInvalidUTF8(true), | |
| jsontext.AllowDuplicateNames(true), | |
| }, | |
| in: map[string]int{"\xff": 0, "\xfe": 1}, | |
| want: `{"�":1,"�":0}`, | |
| }, { | |
| name: jsontest.Name("Maps/String/Deterministic+MarshalFuncs"), | |
| opts: []Options{ | |
| Deterministic(true), | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v string) error { | |
| if p := enc.StackPointer(); p != "/X" { | |
| return fmt.Errorf("invalid stack pointer: got %s, want /X", p) | |
| } | |
| switch v { | |
| case "a": | |
| return enc.WriteToken(jsontext.String("b")) | |
| case "b": | |
| return enc.WriteToken(jsontext.String("a")) | |
| default: | |
| return fmt.Errorf("invalid value: %q", v) | |
| } | |
| })), | |
| }, | |
| in: map[namedString]map[string]int{"X": {"a": -1, "b": 1}}, | |
| want: `{"X":{"a":1,"b":-1}}`, | |
| }, { | |
| name: jsontest.Name("Maps/String/Deterministic+MarshalFuncs+RejectDuplicateNames"), | |
| opts: []Options{ | |
| Deterministic(true), | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v string) error { | |
| if p := enc.StackPointer(); p != "/X" { | |
| return fmt.Errorf("invalid stack pointer: got %s, want /X", p) | |
| } | |
| switch v { | |
| case "a", "b": | |
| return enc.WriteToken(jsontext.String("x")) | |
| default: | |
| return fmt.Errorf("invalid value: %q", v) | |
| } | |
| })), | |
| jsontext.AllowDuplicateNames(false), | |
| }, | |
| in: map[namedString]map[string]int{"X": {"a": 1, "b": 1}}, | |
| want: `{"X":{"x":1`, | |
| wantErr: newDuplicateNameError("/X/x", nil, len64(`{"X":{"x":1,`)), | |
| }, { | |
| name: jsontest.Name("Maps/String/Deterministic+MarshalFuncs+AllowDuplicateNames"), | |
| opts: []Options{ | |
| Deterministic(true), | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v string) error { | |
| if p := enc.StackPointer(); p != "/X" { | |
| return fmt.Errorf("invalid stack pointer: got %s, want /0", p) | |
| } | |
| switch v { | |
| case "a", "b": | |
| return enc.WriteToken(jsontext.String("x")) | |
| default: | |
| return fmt.Errorf("invalid value: %q", v) | |
| } | |
| })), | |
| jsontext.AllowDuplicateNames(true), | |
| }, | |
| in: map[namedString]map[string]int{"X": {"a": 1, "b": 1}}, | |
| // NOTE: Since the names are identical, the exact values may be | |
| // non-deterministic since sort cannot distinguish between members. | |
| want: `{"X":{"x":1,"x":1}}`, | |
| }, { | |
| name: jsontest.Name("Maps/RecursiveMap"), | |
| in: recursiveMap{ | |
| "fizz": { | |
| "foo": {}, | |
| "bar": nil, | |
| }, | |
| "buzz": nil, | |
| }, | |
| canonicalize: true, | |
| want: `{"buzz":{},"fizz":{"bar":{},"foo":{}}}`, | |
| }, { | |
| name: jsontest.Name("Maps/CyclicMap"), | |
| in: func() recursiveMap { | |
| m := recursiveMap{"k": nil} | |
| m["k"] = m | |
| return m | |
| }(), | |
| want: strings.Repeat(`{"k":`, startDetectingCyclesAfter) + `{"k"`, | |
| wantErr: EM(internal.ErrCycle).withPos(strings.Repeat(`{"k":`, startDetectingCyclesAfter+1), jsontext.Pointer(strings.Repeat("/k", startDetectingCyclesAfter+1))).withType(0, T[recursiveMap]()), | |
| }, { | |
| name: jsontest.Name("Maps/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: map[string]string{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/Empty"), | |
| in: structEmpty{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedIgnored"), | |
| in: structUnexportedIgnored{ignored: "ignored"}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/IgnoredUnexportedEmbedded"), | |
| in: structIgnoredUnexportedEmbedded{namedString: "ignored"}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/NoCase"), | |
| in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}, | |
| want: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/MatchCaseInsensitiveNames"), | |
| opts: []Options{MatchCaseInsensitiveNames(true)}, | |
| in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}, | |
| want: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), | |
| opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, | |
| in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}, | |
| want: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, | |
| }, { | |
| name: jsontest.Name("Structs/Normal"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structAll{ | |
| Bool: true, | |
| String: "hello", | |
| Bytes: []byte{1, 2, 3}, | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| Map: map[string]string{"key": "value"}, | |
| StructScalars: structScalars{ | |
| Bool: true, | |
| String: "hello", | |
| Bytes: []byte{1, 2, 3}, | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| }, | |
| StructMaps: structMaps{ | |
| MapBool: map[string]bool{"": true}, | |
| MapString: map[string]string{"": "hello"}, | |
| MapBytes: map[string][]byte{"": {1, 2, 3}}, | |
| MapInt: map[string]int64{"": -64}, | |
| MapUint: map[string]uint64{"": +64}, | |
| MapFloat: map[string]float64{"": 3.14159}, | |
| }, | |
| StructSlices: structSlices{ | |
| SliceBool: []bool{true}, | |
| SliceString: []string{"hello"}, | |
| SliceBytes: [][]byte{{1, 2, 3}}, | |
| SliceInt: []int64{-64}, | |
| SliceUint: []uint64{+64}, | |
| SliceFloat: []float64{3.14159}, | |
| }, | |
| Slice: []string{"fizz", "buzz"}, | |
| Array: [1]string{"goodbye"}, | |
| Pointer: new(structAll), | |
| Interface: (*structAll)(nil), | |
| }, | |
| want: `{ | |
| "Bool": true, | |
| "String": "hello", | |
| "Bytes": "AQID", | |
| "Int": -64, | |
| "Uint": 64, | |
| "Float": 3.14159, | |
| "Map": { | |
| "key": "value" | |
| }, | |
| "StructScalars": { | |
| "Bool": true, | |
| "String": "hello", | |
| "Bytes": "AQID", | |
| "Int": -64, | |
| "Uint": 64, | |
| "Float": 3.14159 | |
| }, | |
| "StructMaps": { | |
| "MapBool": { | |
| "": true | |
| }, | |
| "MapString": { | |
| "": "hello" | |
| }, | |
| "MapBytes": { | |
| "": "AQID" | |
| }, | |
| "MapInt": { | |
| "": -64 | |
| }, | |
| "MapUint": { | |
| "": 64 | |
| }, | |
| "MapFloat": { | |
| "": 3.14159 | |
| } | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [ | |
| true | |
| ], | |
| "SliceString": [ | |
| "hello" | |
| ], | |
| "SliceBytes": [ | |
| "AQID" | |
| ], | |
| "SliceInt": [ | |
| -64 | |
| ], | |
| "SliceUint": [ | |
| 64 | |
| ], | |
| "SliceFloat": [ | |
| 3.14159 | |
| ] | |
| }, | |
| "Slice": [ | |
| "fizz", | |
| "buzz" | |
| ], | |
| "Array": [ | |
| "goodbye" | |
| ], | |
| "Pointer": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0, | |
| "Map": {}, | |
| "StructScalars": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {}, | |
| "MapString": {}, | |
| "MapBytes": {}, | |
| "MapInt": {}, | |
| "MapUint": {}, | |
| "MapFloat": {} | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [], | |
| "SliceString": [], | |
| "SliceBytes": [], | |
| "SliceInt": [], | |
| "SliceUint": [], | |
| "SliceFloat": [] | |
| }, | |
| "Slice": [], | |
| "Array": [ | |
| "" | |
| ], | |
| "Pointer": null, | |
| "Interface": null | |
| }, | |
| "Interface": null | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/SpaceAfterColonAndComma"), | |
| opts: []Options{jsontext.SpaceAfterColon(true), jsontext.SpaceAfterComma(true)}, | |
| in: structOmitZeroAll{Int: 1, Uint: 1}, | |
| want: `{"Int": 1, "Uint": 1}`, | |
| }, { | |
| name: jsontest.Name("Structs/SpaceAfterColon"), | |
| opts: []Options{jsontext.SpaceAfterColon(true)}, | |
| in: structOmitZeroAll{Int: 1, Uint: 1}, | |
| want: `{"Int": 1,"Uint": 1}`, | |
| }, { | |
| name: jsontest.Name("Structs/SpaceAfterComma"), | |
| opts: []Options{jsontext.SpaceAfterComma(true)}, | |
| in: structOmitZeroAll{Int: 1, Uint: 1, Slice: []string{"a", "b"}}, | |
| want: `{"Int":1, "Uint":1, "Slice":["a", "b"]}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structStringified{ | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| PointerInt: new(int64(-64)), | |
| PointerUint: new(uint64(+64)), | |
| PointerFloat: new(float64(3.14159)), | |
| }, | |
| want: `{ | |
| "Int": "-64", | |
| "Uint": "64", | |
| "Float": "3.14159", | |
| "PointerInt": "-64", | |
| "PointerUint": "64", | |
| "PointerFloat": "3.14159" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified"), | |
| opts: []Options{jsontext.Multiline(true), jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedLegacy{ | |
| Bool: true, | |
| String: "hello", | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| PointerBool: new(true), | |
| PointerString: new("hello"), | |
| PointerInt: new(int64(-64)), | |
| PointerUint: new(uint64(+64)), | |
| PointerFloat: new(float64(3.14159)), | |
| }, | |
| want: `{ | |
| "Bool": "true", | |
| "String": "\"hello\"", | |
| "Int": "-64", | |
| "Uint": "64", | |
| "Float": "3.14159", | |
| "PointerBool": "true", | |
| "PointerString": "\"hello\"", | |
| "PointerInt": "-64", | |
| "PointerUint": "64", | |
| "PointerFloat": "3.14159" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Bool"), | |
| in: structStringifiedBool{}, | |
| want: `{"Bool"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Bool":`, "/Bool").withType(0, boolType), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Bool"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedBool{}, | |
| want: `{"Bool":false}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/String"), | |
| in: structStringifiedString{}, | |
| want: `{"String"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"String":`, "/String").withType(0, stringType), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/String"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedString{}, | |
| want: `{"String":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Bytes"), | |
| in: structStringifiedBytes{}, | |
| want: `{"Bytes"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Bytes":`, "/Bytes").withType(0, bytesType), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Bytes"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedBytes{}, | |
| want: `{"Bytes":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Map"), | |
| in: structStringifiedMap{}, | |
| want: `{"Map"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Map":`, "/Map").withType(0, reflect.TypeFor[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Map"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedMap{}, | |
| want: `{"Map":{}}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Slice"), | |
| in: structStringifiedSlice{}, | |
| want: `{"Slice"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Slice":`, "/Slice").withType(0, reflect.TypeFor[[]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Slice"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedSlice{}, | |
| want: `{"Slice":[]}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Array"), | |
| in: structStringifiedArray{}, | |
| want: `{"Array"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Array":`, "/Array").withType(0, reflect.TypeFor[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Array"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedArray{}, | |
| want: `{"Array":[""]}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Struct"), | |
| in: structStringifiedStruct{}, | |
| want: `{"Struct"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Struct":`, "/Struct").withType(0, reflect.TypeFor[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Struct"), | |
| opts: []Options{jsontext.Multiline(true), jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedStruct{}, | |
| want: `{ | |
| "Struct": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0, | |
| "Map": {}, | |
| "StructScalars": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {}, | |
| "MapString": {}, | |
| "MapBytes": {}, | |
| "MapInt": {}, | |
| "MapUint": {}, | |
| "MapFloat": {} | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [], | |
| "SliceString": [], | |
| "SliceBytes": [], | |
| "SliceInt": [], | |
| "SliceUint": [], | |
| "SliceFloat": [] | |
| }, | |
| "Slice": [], | |
| "Array": [ | |
| "" | |
| ], | |
| "Pointer": null, | |
| "Interface": null | |
| } | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Pointer"), | |
| in: structStringifiedPointer{}, | |
| want: `{"Pointer":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Pointer"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedPointer{}, | |
| want: `{"Pointer":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/PointerPointerInt"), | |
| in: structStringifiedPointerPointerInt{Pointer: new(new(5))}, | |
| want: `{"Pointer":"5"}`, | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Interface"), | |
| in: structStringifiedInterface{Interface: 1000}, | |
| want: `{"Interface"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Interface":`, "/Interface").withType(0, anyType), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Interface"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedInterface{Interface: 1000}, | |
| want: `{"Interface":"1000"}`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Bytes"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| want: `{"Bytes"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Bytes":`, "/Bytes").withType(0, bytesType), | |
| in: structStringifiedBytes{}, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Bytes"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedBytes{}, | |
| want: `{"Bytes":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Map"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedMap{}, | |
| want: `{"Map"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Map":`, "/Map").withType(0, reflect.TypeFor[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Map"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedMap{}, | |
| want: `{"Map":{}}`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Slice"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedSlice{}, | |
| want: `{"Slice"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Slice":`, "/Slice").withType(0, reflect.TypeFor[[]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Slice"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedSlice{}, | |
| want: `{"Slice":[]}`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Array"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedArray{}, | |
| want: `{"Array"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Array":`, "/Array").withType(0, reflect.TypeFor[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Array"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedArray{}, | |
| want: `{"Array":[""]}`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Struct"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedStruct{}, | |
| want: `{"Struct"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Struct":`, "/Struct").withType(0, reflect.TypeFor[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Struct"), | |
| opts: []Options{jsontext.Multiline(true), jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedStruct{}, | |
| want: `{ | |
| "Struct": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0, | |
| "Map": {}, | |
| "StructScalars": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {}, | |
| "MapString": {}, | |
| "MapBytes": {}, | |
| "MapInt": {}, | |
| "MapUint": {}, | |
| "MapFloat": {} | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [], | |
| "SliceString": [], | |
| "SliceBytes": [], | |
| "SliceInt": [], | |
| "SliceUint": [], | |
| "SliceFloat": [] | |
| }, | |
| "Slice": [], | |
| "Array": [ | |
| "" | |
| ], | |
| "Pointer": null, | |
| "Interface": null | |
| } | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Pointer"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedPointer{Pointer: new(structAll)}, | |
| want: `{"Pointer"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Pointer":`, "/Pointer").withType(0, reflect.TypeFor[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Pointer"), | |
| opts: []Options{jsontext.Multiline(true), jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedPointer{Pointer: new(structAll)}, | |
| want: `{ | |
| "Pointer": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0, | |
| "Map": {}, | |
| "StructScalars": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {}, | |
| "MapString": {}, | |
| "MapBytes": {}, | |
| "MapInt": {}, | |
| "MapUint": {}, | |
| "MapFloat": {} | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [], | |
| "SliceString": [], | |
| "SliceBytes": [], | |
| "SliceInt": [], | |
| "SliceUint": [], | |
| "SliceFloat": [] | |
| }, | |
| "Slice": [], | |
| "Array": [ | |
| "" | |
| ], | |
| "Pointer": null, | |
| "Interface": null | |
| } | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/PointerPointerInt"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedPointerPointerInt{Pointer: new(new(5))}, | |
| want: `{"Pointer"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Pointer":`, "/Pointer").withType(0, reflect.TypeFor[**int]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/PointerPointerInt"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedPointerPointerInt{Pointer: new(new(5))}, | |
| want: `{"Pointer":5}`, | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Interface"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| in: structStringifiedInterface{}, | |
| want: `{"Interface"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"Interface":`, "/Interface").withType(0, anyType), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Interface"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structStringifiedInterface{Interface: 1000}, | |
| want: `{"Interface":1000}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZero/Zero"), | |
| in: structOmitZeroAll{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroOption/Zero"), | |
| opts: []Options{OmitZeroStructFields(true)}, | |
| in: structAll{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZero/NonZero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitZeroAll{ | |
| Bool: true, // not omitted since true is non-zero | |
| String: " ", // not omitted since non-empty string is non-zero | |
| Bytes: []byte{}, // not omitted since allocated slice is non-zero | |
| Int: 1, // not omitted since 1 is non-zero | |
| Uint: 1, // not omitted since 1 is non-zero | |
| Float: math.SmallestNonzeroFloat64, // not omitted since still slightly above zero | |
| Map: map[string]string{}, // not omitted since allocated map is non-zero | |
| StructScalars: structScalars{unexported: true}, // not omitted since unexported is non-zero | |
| StructSlices: structSlices{Ignored: true}, // not omitted since Ignored is non-zero | |
| StructMaps: structMaps{MapBool: map[string]bool{}}, // not omitted since MapBool is non-zero | |
| Slice: []string{}, // not omitted since allocated slice is non-zero | |
| Array: [1]string{" "}, // not omitted since single array element is non-zero | |
| Pointer: new(structOmitZeroAll), // not omitted since pointer is non-zero (even if all fields of the struct value are zero) | |
| Interface: (*structOmitZeroAll)(nil), // not omitted since interface value is non-zero (even if interface value is a nil pointer) | |
| }, | |
| want: `{ | |
| "Bool": true, | |
| "String": " ", | |
| "Bytes": "", | |
| "Int": 1, | |
| "Uint": 1, | |
| "Float": 5e-324, | |
| "Map": {}, | |
| "StructScalars": { | |
| "Bool": false, | |
| "String": "", | |
| "Bytes": "", | |
| "Int": 0, | |
| "Uint": 0, | |
| "Float": 0 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {}, | |
| "MapString": {}, | |
| "MapBytes": {}, | |
| "MapInt": {}, | |
| "MapUint": {}, | |
| "MapFloat": {} | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [], | |
| "SliceString": [], | |
| "SliceBytes": [], | |
| "SliceInt": [], | |
| "SliceUint": [], | |
| "SliceFloat": [] | |
| }, | |
| "Slice": [], | |
| "Array": [ | |
| " " | |
| ], | |
| "Pointer": {}, | |
| "Interface": null | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroOption/NonZero"), | |
| opts: []Options{OmitZeroStructFields(true), jsontext.Multiline(true)}, | |
| in: structAll{ | |
| Bool: true, | |
| String: " ", | |
| Bytes: []byte{}, | |
| Int: 1, | |
| Uint: 1, | |
| Float: math.SmallestNonzeroFloat64, | |
| Map: map[string]string{}, | |
| StructScalars: structScalars{unexported: true}, | |
| StructSlices: structSlices{Ignored: true}, | |
| StructMaps: structMaps{MapBool: map[string]bool{}}, | |
| Slice: []string{}, | |
| Array: [1]string{" "}, | |
| Pointer: new(structAll), | |
| Interface: (*structAll)(nil), | |
| }, | |
| want: `{ | |
| "Bool": true, | |
| "String": " ", | |
| "Bytes": "", | |
| "Int": 1, | |
| "Uint": 1, | |
| "Float": 5e-324, | |
| "Map": {}, | |
| "StructScalars": {}, | |
| "StructMaps": { | |
| "MapBool": {} | |
| }, | |
| "StructSlices": {}, | |
| "Slice": [], | |
| "Array": [ | |
| " " | |
| ], | |
| "Pointer": {}, | |
| "Interface": null | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroMethod/Zero"), | |
| in: structOmitZeroMethodAll{}, | |
| want: `{"ValueNeverZero":"","PointerNeverZero":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroMethod/NonZero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitZeroMethodAll{ | |
| ValueAlwaysZero: valueAlwaysZero("nonzero"), | |
| ValueNeverZero: valueNeverZero("nonzero"), | |
| PointerAlwaysZero: pointerAlwaysZero("nonzero"), | |
| PointerNeverZero: pointerNeverZero("nonzero"), | |
| PointerValueAlwaysZero: addr(valueAlwaysZero("nonzero")), | |
| PointerValueNeverZero: addr(valueNeverZero("nonzero")), | |
| PointerPointerAlwaysZero: addr(pointerAlwaysZero("nonzero")), | |
| PointerPointerNeverZero: addr(pointerNeverZero("nonzero")), | |
| PointerPointerValueAlwaysZero: addr(addr(valueAlwaysZero("nonzero"))), // marshaled since **valueAlwaysZero does not implement IsZero | |
| PointerPointerValueNeverZero: addr(addr(valueNeverZero("nonzero"))), | |
| PointerPointerPointerAlwaysZero: addr(addr(pointerAlwaysZero("nonzero"))), // marshaled since **pointerAlwaysZero does not implement IsZero | |
| PointerPointerPointerNeverZero: addr(addr(pointerNeverZero("nonzero"))), | |
| }, | |
| want: `{ | |
| "ValueNeverZero": "nonzero", | |
| "PointerNeverZero": "nonzero", | |
| "PointerValueNeverZero": "nonzero", | |
| "PointerPointerNeverZero": "nonzero", | |
| "PointerPointerValueAlwaysZero": "nonzero", | |
| "PointerPointerValueNeverZero": "nonzero", | |
| "PointerPointerPointerAlwaysZero": "nonzero", | |
| "PointerPointerPointerNeverZero": "nonzero" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroMethod/Interface/Zero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitZeroMethodInterfaceAll{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroMethod/Interface/PartialZero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitZeroMethodInterfaceAll{ | |
| ValueAlwaysZero: valueAlwaysZero(""), | |
| ValueNeverZero: valueNeverZero(""), | |
| PointerValueAlwaysZero: (*valueAlwaysZero)(nil), | |
| PointerValueNeverZero: (*valueNeverZero)(nil), // nil pointer, so method not called | |
| PointerPointerAlwaysZero: (*pointerAlwaysZero)(nil), | |
| PointerPointerNeverZero: (*pointerNeverZero)(nil), // nil pointer, so method not called | |
| }, | |
| want: `{ | |
| "ValueNeverZero": "" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroMethod/Interface/NonZero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitZeroMethodInterfaceAll{ | |
| ValueAlwaysZero: valueAlwaysZero("nonzero"), | |
| ValueNeverZero: valueNeverZero("nonzero"), | |
| PointerValueAlwaysZero: addr(valueAlwaysZero("nonzero")), | |
| PointerValueNeverZero: addr(valueNeverZero("nonzero")), | |
| PointerPointerAlwaysZero: addr(pointerAlwaysZero("nonzero")), | |
| PointerPointerNeverZero: addr(pointerNeverZero("nonzero")), | |
| }, | |
| want: `{ | |
| "ValueNeverZero": "nonzero", | |
| "PointerValueNeverZero": "nonzero", | |
| "PointerPointerNeverZero": "nonzero" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/Zero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitEmptyAll{}, | |
| want: `{ | |
| "Bool": false, | |
| "StringNonEmpty": "value", | |
| "BytesNonEmpty": [ | |
| "value" | |
| ], | |
| "Float": 0, | |
| "MapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "SliceNonEmpty": [ | |
| "value" | |
| ] | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/EmptyNonZero"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitEmptyAll{ | |
| String: string(""), | |
| StringEmpty: stringMarshalEmpty(""), | |
| StringNonEmpty: stringMarshalNonEmpty(""), | |
| PointerString: addr(string("")), | |
| PointerStringEmpty: addr(stringMarshalEmpty("")), | |
| PointerStringNonEmpty: addr(stringMarshalNonEmpty("")), | |
| Bytes: []byte(""), | |
| BytesEmpty: bytesMarshalEmpty([]byte("")), | |
| BytesNonEmpty: bytesMarshalNonEmpty([]byte("")), | |
| PointerBytes: addr([]byte("")), | |
| PointerBytesEmpty: addr(bytesMarshalEmpty([]byte(""))), | |
| PointerBytesNonEmpty: addr(bytesMarshalNonEmpty([]byte(""))), | |
| Map: map[string]string{}, | |
| MapEmpty: mapMarshalEmpty{}, | |
| MapNonEmpty: mapMarshalNonEmpty{}, | |
| PointerMap: addr(map[string]string{}), | |
| PointerMapEmpty: addr(mapMarshalEmpty{}), | |
| PointerMapNonEmpty: addr(mapMarshalNonEmpty{}), | |
| Slice: []string{}, | |
| SliceEmpty: sliceMarshalEmpty{}, | |
| SliceNonEmpty: sliceMarshalNonEmpty{}, | |
| PointerSlice: addr([]string{}), | |
| PointerSliceEmpty: addr(sliceMarshalEmpty{}), | |
| PointerSliceNonEmpty: addr(sliceMarshalNonEmpty{}), | |
| Pointer: &structOmitZeroEmptyAll{}, | |
| Interface: []string{}, | |
| }, | |
| want: `{ | |
| "Bool": false, | |
| "StringNonEmpty": "value", | |
| "PointerStringNonEmpty": "value", | |
| "BytesNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerBytesNonEmpty": [ | |
| "value" | |
| ], | |
| "Float": 0, | |
| "MapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "PointerMapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "SliceNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerSliceNonEmpty": [ | |
| "value" | |
| ] | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/NonEmpty"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structOmitEmptyAll{ | |
| Bool: true, | |
| PointerBool: addr(true), | |
| String: string("value"), | |
| StringEmpty: stringMarshalEmpty("value"), | |
| StringNonEmpty: stringMarshalNonEmpty("value"), | |
| PointerString: addr(string("value")), | |
| PointerStringEmpty: addr(stringMarshalEmpty("value")), | |
| PointerStringNonEmpty: addr(stringMarshalNonEmpty("value")), | |
| Bytes: []byte("value"), | |
| BytesEmpty: bytesMarshalEmpty([]byte("value")), | |
| BytesNonEmpty: bytesMarshalNonEmpty([]byte("value")), | |
| PointerBytes: addr([]byte("value")), | |
| PointerBytesEmpty: addr(bytesMarshalEmpty([]byte("value"))), | |
| PointerBytesNonEmpty: addr(bytesMarshalNonEmpty([]byte("value"))), | |
| Float: math.Copysign(0, -1), | |
| PointerFloat: addr(math.Copysign(0, -1)), | |
| Map: map[string]string{"": ""}, | |
| MapEmpty: mapMarshalEmpty{"key": "value"}, | |
| MapNonEmpty: mapMarshalNonEmpty{"key": "value"}, | |
| PointerMap: addr(map[string]string{"": ""}), | |
| PointerMapEmpty: addr(mapMarshalEmpty{"key": "value"}), | |
| PointerMapNonEmpty: addr(mapMarshalNonEmpty{"key": "value"}), | |
| Slice: []string{""}, | |
| SliceEmpty: sliceMarshalEmpty{"value"}, | |
| SliceNonEmpty: sliceMarshalNonEmpty{"value"}, | |
| PointerSlice: addr([]string{""}), | |
| PointerSliceEmpty: addr(sliceMarshalEmpty{"value"}), | |
| PointerSliceNonEmpty: addr(sliceMarshalNonEmpty{"value"}), | |
| Pointer: &structOmitZeroEmptyAll{Float: math.SmallestNonzeroFloat64}, | |
| Interface: []string{""}, | |
| }, | |
| want: `{ | |
| "Bool": true, | |
| "PointerBool": true, | |
| "String": "value", | |
| "StringNonEmpty": "value", | |
| "PointerString": "value", | |
| "PointerStringNonEmpty": "value", | |
| "Bytes": "dmFsdWU=", | |
| "BytesNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerBytes": "dmFsdWU=", | |
| "PointerBytesNonEmpty": [ | |
| "value" | |
| ], | |
| "Float": -0, | |
| "PointerFloat": -0, | |
| "Map": { | |
| "": "" | |
| }, | |
| "MapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "PointerMap": { | |
| "": "" | |
| }, | |
| "PointerMapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "Slice": [ | |
| "" | |
| ], | |
| "SliceNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerSlice": [ | |
| "" | |
| ], | |
| "PointerSliceNonEmpty": [ | |
| "value" | |
| ], | |
| "Pointer": { | |
| "Float": 5e-324 | |
| }, | |
| "Interface": [ | |
| "" | |
| ] | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/Legacy/Zero"), | |
| opts: []Options{jsonflags.OmitEmptyWithLegacySemantics | 1}, | |
| in: structOmitEmptyAll{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/Legacy/NonEmpty"), | |
| opts: []Options{jsontext.Multiline(true), jsonflags.OmitEmptyWithLegacySemantics | 1}, | |
| in: structOmitEmptyAll{ | |
| Bool: true, | |
| PointerBool: addr(true), | |
| String: string("value"), | |
| StringEmpty: stringMarshalEmpty("value"), | |
| StringNonEmpty: stringMarshalNonEmpty("value"), | |
| PointerString: addr(string("value")), | |
| PointerStringEmpty: addr(stringMarshalEmpty("value")), | |
| PointerStringNonEmpty: addr(stringMarshalNonEmpty("value")), | |
| Bytes: []byte("value"), | |
| BytesEmpty: bytesMarshalEmpty([]byte("value")), | |
| BytesNonEmpty: bytesMarshalNonEmpty([]byte("value")), | |
| PointerBytes: addr([]byte("value")), | |
| PointerBytesEmpty: addr(bytesMarshalEmpty([]byte("value"))), | |
| PointerBytesNonEmpty: addr(bytesMarshalNonEmpty([]byte("value"))), | |
| Float: math.Copysign(0, -1), | |
| PointerFloat: addr(math.Copysign(0, -1)), | |
| Map: map[string]string{"": ""}, | |
| MapEmpty: mapMarshalEmpty{"key": "value"}, | |
| MapNonEmpty: mapMarshalNonEmpty{"key": "value"}, | |
| PointerMap: addr(map[string]string{"": ""}), | |
| PointerMapEmpty: addr(mapMarshalEmpty{"key": "value"}), | |
| PointerMapNonEmpty: addr(mapMarshalNonEmpty{"key": "value"}), | |
| Slice: []string{""}, | |
| SliceEmpty: sliceMarshalEmpty{"value"}, | |
| SliceNonEmpty: sliceMarshalNonEmpty{"value"}, | |
| PointerSlice: addr([]string{""}), | |
| PointerSliceEmpty: addr(sliceMarshalEmpty{"value"}), | |
| PointerSliceNonEmpty: addr(sliceMarshalNonEmpty{"value"}), | |
| Pointer: &structOmitZeroEmptyAll{Float: math.Copysign(0, -1)}, | |
| Interface: []string{""}, | |
| }, | |
| want: `{ | |
| "Bool": true, | |
| "PointerBool": true, | |
| "String": "value", | |
| "StringEmpty": "", | |
| "StringNonEmpty": "value", | |
| "PointerString": "value", | |
| "PointerStringEmpty": "", | |
| "PointerStringNonEmpty": "value", | |
| "Bytes": "dmFsdWU=", | |
| "BytesEmpty": [], | |
| "BytesNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerBytes": "dmFsdWU=", | |
| "PointerBytesEmpty": [], | |
| "PointerBytesNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerFloat": -0, | |
| "Map": { | |
| "": "" | |
| }, | |
| "MapEmpty": {}, | |
| "MapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "PointerMap": { | |
| "": "" | |
| }, | |
| "PointerMapEmpty": {}, | |
| "PointerMapNonEmpty": { | |
| "key": "value" | |
| }, | |
| "Slice": [ | |
| "" | |
| ], | |
| "SliceEmpty": [], | |
| "SliceNonEmpty": [ | |
| "value" | |
| ], | |
| "PointerSlice": [ | |
| "" | |
| ], | |
| "PointerSliceEmpty": [], | |
| "PointerSliceNonEmpty": [ | |
| "value" | |
| ], | |
| "Pointer": {}, | |
| "Interface": [ | |
| "" | |
| ] | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/NonEmptyString"), | |
| in: struct { | |
| X string `json:",omitempty"` | |
| }{`"`}, | |
| want: `{"X":"\""}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroEmpty/Zero"), | |
| in: structOmitZeroEmptyAll{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroEmpty/Empty"), | |
| in: structOmitZeroEmptyAll{ | |
| Bytes: []byte{}, | |
| Map: map[string]string{}, | |
| Slice: []string{}, | |
| Pointer: &structOmitZeroEmptyAll{}, | |
| Interface: []string{}, | |
| }, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/PathologicalDepth"), | |
| in: func() any { | |
| type X struct { | |
| X *X `json:",omitempty"` | |
| } | |
| var make func(int) *X | |
| make = func(n int) *X { | |
| if n == 0 { | |
| return nil | |
| } | |
| return &X{make(n - 1)} | |
| } | |
| return make(100) | |
| }(), | |
| want: `{}`, | |
| useWriter: true, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/PathologicalBreadth"), | |
| in: func() any { | |
| var fields []reflect.StructField | |
| for i := range 100 { | |
| fields = append(fields, reflect.StructField{ | |
| Name: fmt.Sprintf("X%d", i), | |
| Type: T[stringMarshalEmpty](), | |
| Tag: `json:",omitempty"`, | |
| }) | |
| } | |
| return reflect.New(reflect.StructOf(fields)).Interface() | |
| }(), | |
| want: `{}`, | |
| useWriter: true, | |
| }, { | |
| name: jsontest.Name("Structs/OmitEmpty/PathologicalTree"), | |
| in: func() any { | |
| type X struct { | |
| XL, XR *X `json:",omitempty"` | |
| } | |
| var make func(int) *X | |
| make = func(n int) *X { | |
| if n == 0 { | |
| return nil | |
| } | |
| return &X{make(n - 1), make(n - 1)} | |
| } | |
| return make(8) | |
| }(), | |
| want: `{}`, | |
| useWriter: true, | |
| }, { | |
| name: jsontest.Name("Structs/OmitZeroEmpty/NonEmpty"), | |
| in: structOmitZeroEmptyAll{ | |
| Bytes: []byte("value"), | |
| Map: map[string]string{"": ""}, | |
| Slice: []string{""}, | |
| Pointer: &structOmitZeroEmptyAll{Bool: true}, | |
| Interface: []string{""}, | |
| }, | |
| want: `{"Bytes":"dmFsdWU=","Map":{"":""},"Slice":[""],"Pointer":{"Bool":true},"Interface":[""]}`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Unsupported"), | |
| opts: []Options{jsontext.Multiline(true)}, | |
| in: structFormatBytes{}, | |
| wantErr: EM(errors.New("Go struct field Base16 has unsupported `format` tag option")).withType(0, T[structFormatBytes]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: structFormatBytes{ | |
| Base16: []byte("\x01\x23\x45\x67\x89\xab\xcd\xef"), | |
| Base32: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), | |
| Base32Hex: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), | |
| Base64: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2ۯ\xc3\x1c\xb3\xd3]\xb7㞻\xf3߿"), | |
| Base64URL: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2ۯ\xc3\x1c\xb3\xd3]\xb7㞻\xf3߿"), | |
| Array: []byte{1, 2, 3, 4}, | |
| }, | |
| want: `{ | |
| "Base16": "0123456789abcdef", | |
| "Base32": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", | |
| "Base32Hex": "0123456789ABCDEFGHIJKLMNOPQRSTUV", | |
| "Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", | |
| "Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", | |
| "Array": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4 | |
| ] | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/ArrayBytes"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: structFormatArrayBytes{ | |
| Base16: [4]byte{1, 2, 3, 4}, | |
| Base32: [4]byte{1, 2, 3, 4}, | |
| Base32Hex: [4]byte{1, 2, 3, 4}, | |
| Base64: [4]byte{1, 2, 3, 4}, | |
| Base64URL: [4]byte{1, 2, 3, 4}, | |
| Array: [4]byte{1, 2, 3, 4}, | |
| Default: [4]byte{1, 2, 3, 4}, | |
| }, | |
| want: `{ | |
| "Base16": "01020304", | |
| "Base32": "AEBAGBA=", | |
| "Base32Hex": "0410610=", | |
| "Base64": "AQIDBA==", | |
| "Base64URL": "AQIDBA==", | |
| "Array": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4 | |
| ], | |
| "Default": "AQIDBA==" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/ArrayBytes/Legacy"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true), jsonflags.FormatByteArrayAsArray | jsonflags.FormatBytesWithLegacySemantics | 1}, | |
| in: structFormatArrayBytes{ | |
| Base16: [4]byte{1, 2, 3, 4}, | |
| Base32: [4]byte{1, 2, 3, 4}, | |
| Base32Hex: [4]byte{1, 2, 3, 4}, | |
| Base64: [4]byte{1, 2, 3, 4}, | |
| Base64URL: [4]byte{1, 2, 3, 4}, | |
| Array: [4]byte{1, 2, 3, 4}, | |
| Default: [4]byte{1, 2, 3, 4}, | |
| }, | |
| want: `{ | |
| "Base16": "01020304", | |
| "Base32": "AEBAGBA=", | |
| "Base32Hex": "0410610=", | |
| "Base64": "AQIDBA==", | |
| "Base64URL": "AQIDBA==", | |
| "Array": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4 | |
| ], | |
| "Default": [ | |
| 1, | |
| 2, | |
| 3, | |
| 4 | |
| ] | |
| }`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Array"), | |
| opts: []Options{ | |
| jsonopts.ExperimentalSupportFormatTag(true), | |
| WithMarshalers(MarshalFunc(func(in byte) ([]byte, error) { | |
| if in > 3 { | |
| return []byte("true"), nil | |
| } else { | |
| return []byte("false"), nil | |
| } | |
| })), | |
| }, | |
| in: struct { | |
| Array []byte `json:",format:array"` | |
| }{ | |
| Array: []byte{1, 6, 2, 5, 3, 4}, | |
| }, | |
| want: `{"Array":[false,true,false,true,false,true]}`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Floats"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: []structFormatFloats{ | |
| {NonFinite: math.Pi, PointerNonFinite: addr(math.Pi)}, | |
| {NonFinite: math.NaN(), PointerNonFinite: addr(math.NaN())}, | |
| {NonFinite: math.Inf(-1), PointerNonFinite: addr(math.Inf(-1))}, | |
| {NonFinite: math.Inf(+1), PointerNonFinite: addr(math.Inf(+1))}, | |
| }, | |
| want: `[ | |
| { | |
| "NonFinite": 3.141592653589793, | |
| "PointerNonFinite": 3.141592653589793 | |
| }, | |
| { | |
| "NonFinite": "NaN", | |
| "PointerNonFinite": "NaN" | |
| }, | |
| { | |
| "NonFinite": "-Infinity", | |
| "PointerNonFinite": "-Infinity" | |
| }, | |
| { | |
| "NonFinite": "Infinity", | |
| "PointerNonFinite": "Infinity" | |
| } | |
| ]`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Maps"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: []structFormatMaps{{ | |
| EmitNull: map[string]string(nil), PointerEmitNull: addr(map[string]string(nil)), | |
| EmitEmpty: map[string]string(nil), PointerEmitEmpty: addr(map[string]string(nil)), | |
| EmitDefault: map[string]string(nil), PointerEmitDefault: addr(map[string]string(nil)), | |
| }, { | |
| EmitNull: map[string]string{}, PointerEmitNull: addr(map[string]string{}), | |
| EmitEmpty: map[string]string{}, PointerEmitEmpty: addr(map[string]string{}), | |
| EmitDefault: map[string]string{}, PointerEmitDefault: addr(map[string]string{}), | |
| }, { | |
| EmitNull: map[string]string{"k": "v"}, PointerEmitNull: addr(map[string]string{"k": "v"}), | |
| EmitEmpty: map[string]string{"k": "v"}, PointerEmitEmpty: addr(map[string]string{"k": "v"}), | |
| EmitDefault: map[string]string{"k": "v"}, PointerEmitDefault: addr(map[string]string{"k": "v"}), | |
| }}, | |
| want: `[ | |
| { | |
| "EmitNull": null, | |
| "PointerEmitNull": null, | |
| "EmitEmpty": {}, | |
| "PointerEmitEmpty": {}, | |
| "EmitDefault": {}, | |
| "PointerEmitDefault": {} | |
| }, | |
| { | |
| "EmitNull": {}, | |
| "PointerEmitNull": {}, | |
| "EmitEmpty": {}, | |
| "PointerEmitEmpty": {}, | |
| "EmitDefault": {}, | |
| "PointerEmitDefault": {} | |
| }, | |
| { | |
| "EmitNull": { | |
| "k": "v" | |
| }, | |
| "PointerEmitNull": { | |
| "k": "v" | |
| }, | |
| "EmitEmpty": { | |
| "k": "v" | |
| }, | |
| "PointerEmitEmpty": { | |
| "k": "v" | |
| }, | |
| "EmitDefault": { | |
| "k": "v" | |
| }, | |
| "PointerEmitDefault": { | |
| "k": "v" | |
| } | |
| } | |
| ]`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Maps/FormatNilMapAsNull"), | |
| opts: []Options{ | |
| jsonopts.ExperimentalSupportFormatTag(true), | |
| FormatNilMapAsNull(true), | |
| jsontext.Multiline(true), | |
| }, | |
| in: []structFormatMaps{{ | |
| EmitNull: map[string]string(nil), PointerEmitNull: addr(map[string]string(nil)), | |
| EmitEmpty: map[string]string(nil), PointerEmitEmpty: addr(map[string]string(nil)), | |
| EmitDefault: map[string]string(nil), PointerEmitDefault: addr(map[string]string(nil)), | |
| }, { | |
| EmitNull: map[string]string{}, PointerEmitNull: addr(map[string]string{}), | |
| EmitEmpty: map[string]string{}, PointerEmitEmpty: addr(map[string]string{}), | |
| EmitDefault: map[string]string{}, PointerEmitDefault: addr(map[string]string{}), | |
| }, { | |
| EmitNull: map[string]string{"k": "v"}, PointerEmitNull: addr(map[string]string{"k": "v"}), | |
| EmitEmpty: map[string]string{"k": "v"}, PointerEmitEmpty: addr(map[string]string{"k": "v"}), | |
| EmitDefault: map[string]string{"k": "v"}, PointerEmitDefault: addr(map[string]string{"k": "v"}), | |
| }}, | |
| want: `[ | |
| { | |
| "EmitNull": null, | |
| "PointerEmitNull": null, | |
| "EmitEmpty": {}, | |
| "PointerEmitEmpty": {}, | |
| "EmitDefault": null, | |
| "PointerEmitDefault": null | |
| }, | |
| { | |
| "EmitNull": {}, | |
| "PointerEmitNull": {}, | |
| "EmitEmpty": {}, | |
| "PointerEmitEmpty": {}, | |
| "EmitDefault": {}, | |
| "PointerEmitDefault": {} | |
| }, | |
| { | |
| "EmitNull": { | |
| "k": "v" | |
| }, | |
| "PointerEmitNull": { | |
| "k": "v" | |
| }, | |
| "EmitEmpty": { | |
| "k": "v" | |
| }, | |
| "PointerEmitEmpty": { | |
| "k": "v" | |
| }, | |
| "EmitDefault": { | |
| "k": "v" | |
| }, | |
| "PointerEmitDefault": { | |
| "k": "v" | |
| } | |
| } | |
| ]`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Slices"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: []structFormatSlices{{ | |
| EmitNull: []string(nil), PointerEmitNull: addr([]string(nil)), | |
| EmitEmpty: []string(nil), PointerEmitEmpty: addr([]string(nil)), | |
| EmitDefault: []string(nil), PointerEmitDefault: addr([]string(nil)), | |
| }, { | |
| EmitNull: []string{}, PointerEmitNull: addr([]string{}), | |
| EmitEmpty: []string{}, PointerEmitEmpty: addr([]string{}), | |
| EmitDefault: []string{}, PointerEmitDefault: addr([]string{}), | |
| }, { | |
| EmitNull: []string{"v"}, PointerEmitNull: addr([]string{"v"}), | |
| EmitEmpty: []string{"v"}, PointerEmitEmpty: addr([]string{"v"}), | |
| EmitDefault: []string{"v"}, PointerEmitDefault: addr([]string{"v"}), | |
| }}, | |
| want: `[ | |
| { | |
| "EmitNull": null, | |
| "PointerEmitNull": null, | |
| "EmitEmpty": [], | |
| "PointerEmitEmpty": [], | |
| "EmitDefault": [], | |
| "PointerEmitDefault": [] | |
| }, | |
| { | |
| "EmitNull": [], | |
| "PointerEmitNull": [], | |
| "EmitEmpty": [], | |
| "PointerEmitEmpty": [], | |
| "EmitDefault": [], | |
| "PointerEmitDefault": [] | |
| }, | |
| { | |
| "EmitNull": [ | |
| "v" | |
| ], | |
| "PointerEmitNull": [ | |
| "v" | |
| ], | |
| "EmitEmpty": [ | |
| "v" | |
| ], | |
| "PointerEmitEmpty": [ | |
| "v" | |
| ], | |
| "EmitDefault": [ | |
| "v" | |
| ], | |
| "PointerEmitDefault": [ | |
| "v" | |
| ] | |
| } | |
| ]`, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Bool"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Bool: true}, | |
| want: `{"Bool"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, boolType), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/String"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{String: "string"}, | |
| want: `{"String"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"String":`, "/String").withType(0, stringType), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Bytes"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Bytes: []byte("bytes")}, | |
| want: `{"Bytes"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Bytes":`, "/Bytes").withType(0, bytesType), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Int"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Int: 1}, | |
| want: `{"Int"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Int":`, "/Int").withType(0, T[int64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Uint"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Uint: 1}, | |
| want: `{"Uint"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Uint":`, "/Uint").withType(0, T[uint64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Float"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Float: 1}, | |
| want: `{"Float"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Float":`, "/Float").withType(0, T[float64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Map"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Map: map[string]string{}}, | |
| want: `{"Map"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Map":`, "/Map").withType(0, T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Struct"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Struct: structAll{Bool: true}}, | |
| want: `{"Struct"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Struct":`, "/Struct").withType(0, T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Slice"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Slice: []string{}}, | |
| want: `{"Slice"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Slice":`, "/Slice").withType(0, T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Array"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Array: [1]string{"string"}}, | |
| want: `{"Array"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Array":`, "/Array").withType(0, T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Interface"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structFormatInvalid{Interface: "anything"}, | |
| want: `{"Interface"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Interface":`, "/Interface").withType(0, T[any]()), | |
| }, { | |
| name: jsontest.Name("Structs/Embed/Zero"), | |
| in: structEmbedded{}, | |
| want: `{"D":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/Embed/Alloc"), | |
| in: structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{}, | |
| StructEmbed1: StructEmbed1{}, | |
| }, | |
| StructEmbed2: &StructEmbed2{}, | |
| }, | |
| want: `{"A":"","B":"","D":"","E":"","F":"","G":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/Embed/NonZero"), | |
| in: structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{A: "A1", B: "B1", C: "C1"}, | |
| StructEmbed1: StructEmbed1{C: "C2", D: "D2", E: "E2"}, | |
| }, | |
| StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, | |
| }, | |
| want: `{"A":"A1","B":"B1","D":"D2","E":"E3","F":"F3","G":"G3"}`, | |
| }, { | |
| name: jsontest.Name("Structs/Embed/DualCycle"), | |
| in: cyclicA{ | |
| B1: cyclicB{F: 1}, // B1.F ignored since it conflicts with B2.F | |
| B2: cyclicB{F: 2}, // B2.F ignored since it conflicts with B1.F | |
| }, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Nil"), | |
| in: structEmbedTextValue{X: jsontext.Value(nil)}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Empty"), | |
| in: structEmbedTextValue{X: jsontext.Value("")}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/NonEmptyN1"), | |
| in: structEmbedTextValue{X: jsontext.Value(` { "fizz" : "buzz" } `)}, | |
| want: `{"fizz":"buzz"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/NonEmptyN2"), | |
| in: structEmbedTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "foo" : "bar" } `)}, | |
| want: `{"fizz":"buzz","foo":"bar"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/NonEmptyWithOthers"), | |
| in: structEmbedTextValue{ | |
| A: 1, | |
| X: jsontext.Value(` { "fizz" : "buzz" , "foo" : "bar" } `), | |
| B: 2, | |
| }, | |
| // NOTE: Embedded fallback fields are always serialized last. | |
| want: `{"A":1,"B":2,"fizz":"buzz","foo":"bar"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/RejectDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, | |
| in: structEmbedTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "fizz" : "buzz" } `)}, | |
| want: `{"fizz":"buzz"`, | |
| wantErr: newDuplicateNameError("/fizz", nil, len64(`{"fizz":"buzz"`)), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| in: structEmbedTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "fizz" : "buzz" } `)}, | |
| want: `{"fizz":"buzz","fizz":"buzz"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/RejectInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(false)}, | |
| in: structEmbedTextValue{X: jsontext.Value(`{"` + "\xde\xad\xbe\xef" + `":"value"}`)}, | |
| want: `{`, | |
| wantErr: newInvalidUTF8Error(len64(`{"`+"\xde\xad"), ""), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/AllowInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: structEmbedTextValue{X: jsontext.Value(`{"` + "\xde\xad\xbe\xef" + `":"value"}`)}, | |
| want: `{"ޭ��":"value"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/InvalidWhitespace"), | |
| in: structEmbedTextValue{X: jsontext.Value("\n\r\t ")}, | |
| want: `{`, | |
| wantErr: EM(io.ErrUnexpectedEOF).withPos(`{`, "").withType(0, T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/InvalidObject"), | |
| in: structEmbedTextValue{X: jsontext.Value(` true `)}, | |
| want: `{`, | |
| wantErr: EM(errRawEmbedNotObject).withPos(`{`, "").withType(0, T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/InvalidObjectName"), | |
| in: structEmbedTextValue{X: jsontext.Value(` { true : false } `)}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(" { "), "")).withPos(`{`, "").withType(0, T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/InvalidEndObject"), | |
| in: structEmbedTextValue{X: jsontext.Value(` { "name" : false , } `)}, | |
| want: `{"name":false`, | |
| wantErr: EM(newInvalidCharacterError(",", "at start of value", len64(` { "name" : false `), "")).withPos(`{"name":false,`, "").withType(0, T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/InvalidDualObject"), | |
| in: structEmbedTextValue{X: jsontext.Value(`{}{}`)}, | |
| want: `{`, | |
| wantErr: EM(newInvalidCharacterError("{", "after top-level value", len64(`{}`), "")).withPos(`{`, "").withType(0, T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Nested/Nil"), | |
| in: structEmbedPointerEmbedTextValue{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/Nil"), | |
| in: structEmbedPointerTextValue{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/NonEmpty"), | |
| in: structEmbedPointerTextValue{X: addr(jsontext.Value(` { "fizz" : "buzz" } `))}, | |
| want: `{"fizz":"buzz"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/Nested/Nil"), | |
| in: structEmbedEmbedPointerTextValue{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Nil"), | |
| in: structEmbedMapStringAny{X: nil}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Empty"), | |
| in: structEmbedMapStringAny{X: make(jsonObject)}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/NonEmptyN1"), | |
| in: structEmbedMapStringAny{X: jsonObject{"fizz": nil}}, | |
| want: `{"fizz":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/NonEmptyN2"), | |
| in: structEmbedMapStringAny{X: jsonObject{"fizz": time.Time{}, "buzz": math.Pi}}, | |
| want: `{"buzz":3.141592653589793,"fizz":"0001-01-01T00:00:00Z"}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/NonEmptyWithOthers"), | |
| in: structEmbedMapStringAny{ | |
| A: 1, | |
| X: jsonObject{"fizz": nil}, | |
| B: 2, | |
| }, | |
| // NOTE: Embedded fallback fields are always serialized last. | |
| want: `{"A":1,"B":2,"fizz":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/RejectInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(false)}, | |
| in: structEmbedMapStringAny{X: jsonObject{"\xde\xad\xbe\xef": nil}}, | |
| want: `{`, | |
| wantErr: EM(jsonwire.ErrInvalidUTF8).withPos(`{`, "").withType(0, stringType), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/AllowInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: structEmbedMapStringAny{X: jsonObject{"\xde\xad\xbe\xef": nil}}, | |
| want: `{"ޭ��":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/InvalidValue"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: structEmbedMapStringAny{X: jsonObject{"name": make(chan string)}}, | |
| want: `{"name"`, | |
| wantErr: EM(nil).withPos(`{"name":`, "/name").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Nested/Nil"), | |
| in: structEmbedPointerEmbedMapStringAny{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MarshalFunc"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { | |
| return []byte(fmt.Sprintf(`"%v"`, v)), nil | |
| })), | |
| }, | |
| in: structEmbedMapStringAny{X: jsonObject{"fizz": 3.14159}}, | |
| want: `{"fizz":"3.14159"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/Nil"), | |
| in: structEmbedPointerMapStringAny{X: nil}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/NonEmpty"), | |
| in: structEmbedPointerMapStringAny{X: addr(jsonObject{"name": "value"})}, | |
| want: `{"name":"value"}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/Nested/Nil"), | |
| in: structEmbedEmbedPointerMapStringAny{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt"), | |
| in: structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }, | |
| want: `{"one":1,"two":2,"zero":0}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/Deterministic"), | |
| opts: []Options{Deterministic(true)}, | |
| in: structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }, | |
| want: `{"one":1,"two":2,"zero":0}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), | |
| opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(false)}, | |
| in: structEmbedMapStringInt{ | |
| X: map[string]int{"\xff": 0, "\xfe": 1}, | |
| }, | |
| want: `{"�":1`, | |
| wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":1`)), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), | |
| opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, | |
| in: structEmbedMapStringInt{ | |
| X: map[string]int{"\xff": 0, "\xfe": 1}, | |
| }, | |
| want: `{"�":1,"�":0}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/StringifiedNumbers"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }, | |
| want: `{"one":"1","two":"2","zero":"0"}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/MarshalFunc"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| // Marshalers do not affect the string key of embedded maps. | |
| MarshalFunc(func(v string) ([]byte, error) { | |
| return []byte(fmt.Sprintf(`"%q"`, strings.ToUpper(v))), nil | |
| }), | |
| MarshalFunc(func(v int) ([]byte, error) { | |
| return []byte(fmt.Sprintf(`"%v"`, v)), nil | |
| }), | |
| )), | |
| }, | |
| in: structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }, | |
| want: `{"one":"1","two":"2","zero":"0"}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt"), | |
| in: structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, | |
| }, | |
| want: `{"one":1,"two":2,"zero":0}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt/Deterministic"), | |
| opts: []Options{Deterministic(true)}, | |
| in: structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, | |
| }, | |
| want: `{"one":1,"two":2,"zero":0}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/Nil"), | |
| in: structEmbedMapNamedStringAny{X: nil}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/Empty"), | |
| in: structEmbedMapNamedStringAny{X: make(map[namedString]any)}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/NonEmptyN1"), | |
| in: structEmbedMapNamedStringAny{X: map[namedString]any{"fizz": nil}}, | |
| want: `{"fizz":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/NonEmptyN2"), | |
| in: structEmbedMapNamedStringAny{X: map[namedString]any{"fizz": time.Time{}, "buzz": math.Pi}}, | |
| want: `{"buzz":3.141592653589793,"fizz":"0001-01-01T00:00:00Z"}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/NonEmptyWithOthers"), | |
| in: structEmbedMapNamedStringAny{ | |
| A: 1, | |
| X: map[namedString]any{"fizz": nil}, | |
| B: 2, | |
| }, | |
| // NOTE: Embedded fallback fields are always serialized last. | |
| want: `{"A":1,"B":2,"fizz":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/RejectInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(false)}, | |
| in: structEmbedMapNamedStringAny{X: map[namedString]any{"\xde\xad\xbe\xef": nil}}, | |
| want: `{`, | |
| wantErr: EM(jsonwire.ErrInvalidUTF8).withPos(`{`, "").withType(0, T[namedString]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/AllowInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: structEmbedMapNamedStringAny{X: map[namedString]any{"\xde\xad\xbe\xef": nil}}, | |
| want: `{"ޭ��":null}`, | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/InvalidValue"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: structEmbedMapNamedStringAny{X: map[namedString]any{"name": make(chan string)}}, | |
| want: `{"name"`, | |
| wantErr: EM(nil).withPos(`{"name":`, "/name").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MarshalFunc"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { | |
| return []byte(fmt.Sprintf(`"%v"`, v)), nil | |
| })), | |
| }, | |
| in: structEmbedMapNamedStringAny{X: map[namedString]any{"fizz": 3.14159}}, | |
| want: `{"fizz":"3.14159"}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/Other"), | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"dupe":"","dupe":""}`), | |
| }, | |
| want: `{"dupe":""`, | |
| wantErr: newDuplicateNameError("", []byte(`"dupe"`), len64(`{"dupe":""`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/Other/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"dupe": "", "dupe": ""}`), | |
| }, | |
| want: `{"dupe":"","dupe":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/ExactDifferent"), | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"Aaa": "", "AaA": "", "AAa": "", "AAA": ""}`), | |
| }, | |
| want: `{"Aaa":"","AaA":"","AAa":"","AAA":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/ExactConflict"), | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"Aaa": "", "Aaa": ""}`), | |
| }, | |
| want: `{"Aaa":""`, | |
| wantErr: newDuplicateNameError("", []byte(`"Aaa"`), len64(`{"Aaa":""`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/ExactConflict/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"Aaa": "", "Aaa": ""}`), | |
| }, | |
| want: `{"Aaa":"","Aaa":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/NoCaseConflict"), | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"Aaa": "", "AaA": "", "aaa": ""}`), | |
| }, | |
| want: `{"Aaa":"","AaA":""`, | |
| wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"Aaa":"","AaA":""`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/NoCaseConflict/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| in: structNoCaseEmbedTextValue{ | |
| X: jsontext.Value(`{"Aaa": "", "AaA": "", "aaa": ""}`), | |
| }, | |
| want: `{"Aaa":"","AaA":"","aaa":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/ExactDifferentWithField"), | |
| in: structNoCaseEmbedTextValue{ | |
| AAA: "x", | |
| AaA: "x", | |
| X: jsontext.Value(`{"Aaa": ""}`), | |
| }, | |
| want: `{"AAA":"x","AaA":"x","Aaa":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/ExactConflictWithField"), | |
| in: structNoCaseEmbedTextValue{ | |
| AAA: "x", | |
| AaA: "x", | |
| X: jsontext.Value(`{"AAA": ""}`), | |
| }, | |
| want: `{"AAA":"x","AaA":"x"`, | |
| wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"x","AaA":"x"`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedTextValue/NoCaseConflictWithField"), | |
| in: structNoCaseEmbedTextValue{ | |
| AAA: "x", | |
| AaA: "x", | |
| X: jsontext.Value(`{"aaa": ""}`), | |
| }, | |
| want: `{"AAA":"x","AaA":"x"`, | |
| wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"AAA":"x","AaA":"x"`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/MatchCaseInsensitiveDelimiter"), | |
| in: structNoCaseEmbedTextValue{ | |
| AaA: "x", | |
| X: jsontext.Value(`{"aa_a": ""}`), | |
| }, | |
| want: `{"AaA":"x"`, | |
| wantErr: newDuplicateNameError("", []byte(`"aa_a"`), len64(`{"AaA":"x"`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/MatchCaseSensitiveDelimiter"), | |
| opts: []Options{jsonflags.MatchCaseSensitiveDelimiter | 1}, | |
| in: structNoCaseEmbedTextValue{ | |
| AaA: "x", | |
| X: jsontext.Value(`{"aa_a": ""}`), | |
| }, | |
| want: `{"AaA":"x","aa_a":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), | |
| opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, | |
| in: structNoCaseEmbedTextValue{ | |
| AaA: "x", | |
| X: jsontext.Value(`{"aa_a": ""}`), | |
| }, | |
| want: `{"AaA":"x","aa_a":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), | |
| opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, | |
| in: structNoCaseEmbedTextValue{ | |
| AA_b: "x", | |
| X: jsontext.Value(`{"aa_b": ""}`), | |
| }, | |
| want: `{"AA_b":"x"`, | |
| wantErr: newDuplicateNameError("", []byte(`"aa_b"`), len64(`{"AA_b":"x"`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedMapStringAny/ExactDifferent"), | |
| in: structNoCaseEmbedMapStringAny{ | |
| X: jsonObject{"Aaa": "", "AaA": "", "AAa": "", "AAA": ""}, | |
| }, | |
| want: `{"AAA":"","AAa":"","AaA":"","Aaa":""}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedMapStringAny/ExactDifferentWithField"), | |
| in: structNoCaseEmbedMapStringAny{ | |
| AAA: "x", | |
| AaA: "x", | |
| X: jsonObject{"Aaa": ""}, | |
| }, | |
| want: `{"AAA":"x","AaA":"x","Aaa":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedMapStringAny/ExactConflictWithField"), | |
| in: structNoCaseEmbedMapStringAny{ | |
| AAA: "x", | |
| AaA: "x", | |
| X: jsonObject{"AAA": ""}, | |
| }, | |
| want: `{"AAA":"x","AaA":"x"`, | |
| wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"x","AaA":"x"`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCaseEmbedMapStringAny/NoCaseConflictWithField"), | |
| in: structNoCaseEmbedMapStringAny{ | |
| AAA: "x", | |
| AaA: "x", | |
| X: jsonObject{"aaa": ""}, | |
| }, | |
| want: `{"AAA":"x","AaA":"x"`, | |
| wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"AAA":"x","AaA":"x"`)), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/Conflicting"), | |
| in: structConflicting{}, | |
| want: ``, | |
| wantErr: EM(errors.New("Go struct fields A and B conflict over JSON object name \"conflict\"")).withType(0, T[structConflicting]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/NoneExported"), | |
| in: structNoneExported{}, | |
| want: ``, | |
| wantErr: EM(errNoExportedFields).withType(0, T[structNoneExported]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/MalformedTag"), | |
| in: structMalformedTag{}, | |
| want: ``, | |
| wantErr: EM(errors.New("Go struct field Malformed has malformed `json` tag: invalid character '\"' at start of option (expecting Unicode letter)")).withType(0, T[structMalformedTag]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/UnexportedTag"), | |
| in: structUnexportedTag{}, | |
| want: ``, | |
| wantErr: EM(errors.New("unexported Go struct field unexported cannot have non-ignored `json:\"name\"` tag")).withType(0, T[structUnexportedTag]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/ExportedEmbedded"), | |
| in: structExportedEmbedded{"hello"}, | |
| want: ``, | |
| wantErr: EM(errors.New("embedded Go struct field NamedString of non-struct type must be explicitly given a JSON name")).withType(0, T[structExportedEmbedded]()), | |
| }, { | |
| name: jsontest.Name("Structs/Valid/ExportedEmbedded"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structExportedEmbedded{"hello"}, | |
| want: `{"NamedString":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Structs/Valid/ExportedEmbeddedTag"), | |
| in: structExportedEmbeddedTag{"hello"}, | |
| want: `{"name":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/UnexportedEmbedded"), | |
| in: structUnexportedEmbedded{}, | |
| want: ``, | |
| wantErr: EM(errors.New("embedded Go struct field namedString of non-struct type must be explicitly given a JSON name")).withType(0, T[structUnexportedEmbedded]()), | |
| }, { | |
| name: jsontest.Name("Structs/Valid/UnexportedEmbedded"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structUnexportedEmbedded{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/UnexportedEmbeddedTag"), | |
| in: structUnexportedEmbeddedTag{}, | |
| wantErr: EM(errors.New("Go struct field namedString is not exported")).withType(0, T[structUnexportedEmbeddedTag]()), | |
| }, { | |
| name: jsontest.Name("Structs/Valid/UnexportedEmbeddedTag"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structUnexportedEmbeddedTag{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/UnexportedEmbeddedMethodTag"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| in: structUnexportedEmbeddedMethodTag{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStruct/Zero"), | |
| in: structUnexportedEmbeddedStruct{}, | |
| want: `{"FizzBuzz":0,"Addr":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStruct/NonZero"), | |
| in: structUnexportedEmbeddedStruct{structOmitZeroAll{Bool: true}, 5, structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}, | |
| want: `{"Bool":true,"FizzBuzz":5,"Addr":"192.168.0.1"}`, | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), | |
| in: structUnexportedEmbeddedStructPointer{}, | |
| want: `{"FizzBuzz":0}`, | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Zero"), | |
| in: structUnexportedEmbeddedStructPointer{&structOmitZeroAll{}, 0, &structNestedAddr{}}, | |
| want: `{"FizzBuzz":0,"Addr":""}`, | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/NonZero"), | |
| in: structUnexportedEmbeddedStructPointer{&structOmitZeroAll{Bool: true}, 5, &structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}, | |
| want: `{"Bool":true,"FizzBuzz":5,"Addr":"192.168.0.1"}`, | |
| }, { | |
| name: jsontest.Name("Structs/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: struct{}{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Slices/Interface"), | |
| in: []any{ | |
| false, true, | |
| "hello", []byte("world"), | |
| int32(-32), namedInt64(-64), | |
| uint32(+32), namedUint64(+64), | |
| float32(32.32), namedFloat64(64.64), | |
| }, | |
| want: `[false,true,"hello","d29ybGQ=",-32,-64,32,64,32.32,64.64]`, | |
| }, { | |
| name: jsontest.Name("Slices/Invalid/Channel"), | |
| in: [](chan string){nil}, | |
| want: `[`, | |
| wantErr: EM(nil).withPos(`[`, "/0").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Slices/RecursiveSlice"), | |
| in: recursiveSlice{ | |
| nil, | |
| {}, | |
| {nil}, | |
| {nil, {}}, | |
| }, | |
| want: `[[],[],[[]],[[],[]]]`, | |
| }, { | |
| name: jsontest.Name("Slices/CyclicSlice"), | |
| in: func() recursiveSlice { | |
| s := recursiveSlice{{}} | |
| s[0] = s | |
| return s | |
| }(), | |
| want: strings.Repeat(`[`, startDetectingCyclesAfter) + `[`, | |
| wantErr: EM(internal.ErrCycle).withPos(strings.Repeat("[", startDetectingCyclesAfter+1), jsontext.Pointer(strings.Repeat("/0", startDetectingCyclesAfter+1))).withType(0, T[recursiveSlice]()), | |
| }, { | |
| name: jsontest.Name("Slices/NonCyclicSlice"), | |
| in: func() []any { | |
| v := []any{nil, nil} | |
| v[1] = v[:1] | |
| for i := 1000; i > 0; i-- { | |
| v = []any{v} | |
| } | |
| return v | |
| }(), | |
| want: strings.Repeat(`[`, startDetectingCyclesAfter) + `[null,[null]]` + strings.Repeat(`]`, startDetectingCyclesAfter), | |
| }, { | |
| name: jsontest.Name("Slices/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: []string{"hello", "goodbye"}, | |
| want: `["hello","goodbye"]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Empty"), | |
| in: [0]struct{}{}, | |
| want: `[]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Bool"), | |
| in: [2]bool{false, true}, | |
| want: `[false,true]`, | |
| }, { | |
| name: jsontest.Name("Arrays/String"), | |
| in: [2]string{"hello", "goodbye"}, | |
| want: `["hello","goodbye"]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Bytes"), | |
| in: [2][]byte{[]byte("hello"), []byte("goodbye")}, | |
| want: `["aGVsbG8=","Z29vZGJ5ZQ=="]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Int"), | |
| in: [2]int64{math.MinInt64, math.MaxInt64}, | |
| want: `[-9223372036854775808,9223372036854775807]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Uint"), | |
| in: [2]uint64{0, math.MaxUint64}, | |
| want: `[0,18446744073709551615]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Float"), | |
| in: [2]float64{-math.MaxFloat64, +math.MaxFloat64}, | |
| want: `[-1.7976931348623157e+308,1.7976931348623157e+308]`, | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Channel"), | |
| in: new([1]chan string), | |
| want: `[`, | |
| wantErr: EM(nil).withPos(`[`, "/0").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: [2]string{"hello", "goodbye"}, | |
| want: `["hello","goodbye"]`, | |
| }, { | |
| name: jsontest.Name("Pointers/NilL0"), | |
| in: (*int)(nil), | |
| want: `null`, | |
| }, { | |
| name: jsontest.Name("Pointers/NilL1"), | |
| in: new(*int), | |
| want: `null`, | |
| }, { | |
| name: jsontest.Name("Pointers/Bool"), | |
| in: addr(addr(bool(true))), | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Pointers/String"), | |
| in: addr(addr(string("string"))), | |
| want: `"string"`, | |
| }, { | |
| name: jsontest.Name("Pointers/Bytes"), | |
| in: addr(addr([]byte("bytes"))), | |
| want: `"Ynl0ZXM="`, | |
| }, { | |
| name: jsontest.Name("Pointers/Int"), | |
| in: addr(addr(int(-100))), | |
| want: `-100`, | |
| }, { | |
| name: jsontest.Name("Pointers/Uint"), | |
| in: addr(addr(uint(100))), | |
| want: `100`, | |
| }, { | |
| name: jsontest.Name("Pointers/Float"), | |
| in: addr(addr(float64(3.14159))), | |
| want: `3.14159`, | |
| }, { | |
| name: jsontest.Name("Pointers/CyclicPointer"), | |
| in: func() *recursivePointer { | |
| p := new(recursivePointer) | |
| p.P = p | |
| return p | |
| }(), | |
| want: strings.Repeat(`{"P":`, startDetectingCyclesAfter) + `{"P"`, | |
| wantErr: EM(internal.ErrCycle).withPos(strings.Repeat(`{"P":`, startDetectingCyclesAfter+1), jsontext.Pointer(strings.Repeat("/P", startDetectingCyclesAfter+1))).withType(0, T[*recursivePointer]()), | |
| }, { | |
| name: jsontest.Name("Pointers/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: addr(addr(bool(true))), | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Nil/Empty"), | |
| in: [1]any{nil}, | |
| want: `[null]`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Nil/NonEmpty"), | |
| in: [1]io.Reader{nil}, | |
| want: `[null]`, | |
| }, { | |
| name: jsontest.Name("Interfaces/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: [1]io.Reader{nil}, | |
| want: `[null]`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any"), | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}, [8]byte{}}}, | |
| want: `{"X":[null,false,"",0,{},[],"AAAAAAAAAAA="]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Named"), | |
| in: struct{ X namedAny }{[]namedAny{nil, false, "", 0.0, map[string]namedAny{}, []namedAny{}, [8]byte{}}}, | |
| want: `{"X":[null,false,"",0,{},[],"AAAAAAAAAAA="]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| in: struct{ X any }{0.0}, | |
| want: `{"X":"0"}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/Any"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v any) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/Bool"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v bool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, | |
| want: `{"X":[null,"called","",0,{},[]]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/String"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v string) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, | |
| want: `{"X":[null,false,"called",0,{},[]]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/Float64"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, | |
| want: `{"X":[null,false,"","called",{},[]]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/MapStringAny"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v map[string]any) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, | |
| want: `{"X":[null,false,"",0,"called",[]]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/SliceAny"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v []any) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, | |
| want: `{"X":"called"}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/MarshalFunc/Bytes"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v [8]byte) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: struct{ X any }{[8]byte{}}, | |
| want: `{"X":"called"}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Float/NaN"), | |
| in: struct{ X any }{math.NaN()}, | |
| want: `{"X"`, | |
| wantErr: EM(fmt.Errorf("unsupported value: %v", math.NaN())).withType(0, reflect.TypeFor[float64]()).withPos(`{"X":`, "/X"), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Nil"), | |
| in: struct{ X any }{map[string]any(nil)}, | |
| want: `{"X":{}}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Nil/FormatNilMapAsNull"), | |
| opts: []Options{FormatNilMapAsNull(true)}, | |
| in: struct{ X any }{map[string]any(nil)}, | |
| want: `{"X":null}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Empty"), | |
| in: struct{ X any }{map[string]any{}}, | |
| want: `{"X":{}}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Empty/Multiline"), | |
| opts: []Options{jsontext.Multiline(true), jsontext.WithIndent("")}, | |
| in: struct{ X any }{map[string]any{}}, | |
| want: "{\n\"X\": {}\n}", | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/NonEmpty"), | |
| in: struct{ X any }{map[string]any{"fizz": "buzz"}}, | |
| want: `{"X":{"fizz":"buzz"}}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Deterministic"), | |
| opts: []Options{Deterministic(true)}, | |
| in: struct{ X any }{map[string]any{"alpha": "", "bravo": ""}}, | |
| want: `{"X":{"alpha":"","bravo":""}}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), | |
| opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(false)}, | |
| in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, | |
| want: `{"X":{"�":""`, | |
| wantErr: newDuplicateNameError("/X", []byte(`"�"`), len64(`{"X":{"�":"",`)), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), | |
| opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, | |
| in: struct{ X any }{map[string]any{"\xff": "alpha", "\xfe": "bravo"}}, | |
| want: `{"X":{"�":"bravo","�":"alpha"}}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/RejectInvalidUTF8"), | |
| in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, | |
| want: `{"X":{`, | |
| wantErr: newInvalidUTF8Error(len64(`{"X":{`), "/X"), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/AllowInvalidUTF8+RejectDuplicateNames"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, | |
| want: `{"X":{"�":""`, | |
| wantErr: newDuplicateNameError("/X", []byte(`"�"`), len64(`{"X":{"�":"",`)), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/AllowInvalidUTF8+AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, | |
| in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, | |
| want: `{"X":{"�":"","�":""}}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/Cyclic"), | |
| in: func() any { | |
| m := map[string]any{} | |
| m[""] = m | |
| return struct{ X any }{m} | |
| }(), | |
| want: `{"X"` + strings.Repeat(`:{""`, startDetectingCyclesAfter), | |
| wantErr: EM(internal.ErrCycle).withPos(`{"X":`+strings.Repeat(`{"":`, startDetectingCyclesAfter), "/X"+jsontext.Pointer(strings.Repeat("/", startDetectingCyclesAfter))).withType(0, T[map[string]any]()), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/Nil"), | |
| in: struct{ X any }{[]any(nil)}, | |
| want: `{"X":[]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/Nil/FormatNilSliceAsNull"), | |
| opts: []Options{FormatNilSliceAsNull(true)}, | |
| in: struct{ X any }{[]any(nil)}, | |
| want: `{"X":null}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/Empty"), | |
| in: struct{ X any }{[]any{}}, | |
| want: `{"X":[]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/Empty/Multiline"), | |
| opts: []Options{jsontext.Multiline(true), jsontext.WithIndent("")}, | |
| in: struct{ X any }{[]any{}}, | |
| want: "{\n\"X\": []\n}", | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/NonEmpty"), | |
| in: struct{ X any }{[]any{"fizz", "buzz"}}, | |
| want: `{"X":["fizz","buzz"]}`, | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/Cyclic"), | |
| in: func() any { | |
| s := make([]any, 1) | |
| s[0] = s | |
| return struct{ X any }{s} | |
| }(), | |
| want: `{"X":` + strings.Repeat(`[`, startDetectingCyclesAfter), | |
| wantErr: EM(internal.ErrCycle).withPos(`{"X":`+strings.Repeat(`[`, startDetectingCyclesAfter), "/X"+jsontext.Pointer(strings.Repeat("/0", startDetectingCyclesAfter))).withType(0, T[[]any]()), | |
| }, { | |
| name: jsontest.Name("Methods/NilPointer"), | |
| in: struct{ X *allMethods }{X: (*allMethods)(nil)}, // method should not be called | |
| want: `{"X":null}`, | |
| }, { | |
| // NOTE: Fixes https://github.com/dominikh/go-tools/issues/975. | |
| name: jsontest.Name("Methods/NilInterface"), | |
| in: struct{ X MarshalerTo }{X: (*allMethods)(nil)}, // method should not be called | |
| want: `{"X":null}`, | |
| }, { | |
| name: jsontest.Name("Methods/AllMethods"), | |
| in: struct{ X *allMethods }{X: &allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/AllMethodsExceptJSONv2"), | |
| in: struct{ X *allMethodsExceptJSONv2 }{X: &allMethodsExceptJSONv2{allMethods: allMethods{method: "MarshalJSON", value: []byte(`"hello"`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/AllMethodsExceptJSONv1"), | |
| in: struct{ X *allMethodsExceptJSONv1 }{X: &allMethodsExceptJSONv1{allMethods: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/AllMethodsExceptText"), | |
| in: struct{ X *allMethodsExceptText }{X: &allMethodsExceptText{allMethods: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/OnlyMethodJSONv2"), | |
| in: struct{ X *onlyMethodJSONv2 }{X: &onlyMethodJSONv2{allMethods: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/OnlyMethodJSONv1"), | |
| in: struct{ X *onlyMethodJSONv1 }{X: &onlyMethodJSONv1{allMethods: allMethods{method: "MarshalJSON", value: []byte(`"hello"`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/OnlyMethodText"), | |
| in: struct{ X *onlyMethodText }{X: &onlyMethodText{allMethods: allMethods{method: "MarshalText", value: []byte(`hello`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| name: jsontest.Name("Methods/IP"), | |
| in: net.IPv4(192, 168, 0, 100), | |
| want: `"192.168.0.100"`, | |
| }, { | |
| name: jsontest.Name("Methods/NetIP"), | |
| in: struct { | |
| Addr netip.Addr | |
| AddrPort netip.AddrPort | |
| Prefix netip.Prefix | |
| }{ | |
| Addr: netip.AddrFrom4([4]byte{1, 2, 3, 4}), | |
| AddrPort: netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 1234), | |
| Prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24), | |
| }, | |
| want: `{"Addr":"1.2.3.4","AddrPort":"1.2.3.4:1234","Prefix":"1.2.3.4/24"}`, | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/46516. | |
| name: jsontest.Name("Methods/Anonymous"), | |
| in: struct{ X struct{ allMethods } }{X: struct{ allMethods }{allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, | |
| want: `{"X":"hello"}`, | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/22967. | |
| name: jsontest.Name("Methods/Addressable"), | |
| in: struct { | |
| V allMethods | |
| M map[string]allMethods | |
| I any | |
| }{ | |
| V: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}, | |
| M: map[string]allMethods{"K": {method: "MarshalJSONTo", value: []byte(`"hello"`)}}, | |
| I: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}, | |
| }, | |
| want: `{"V":"hello","M":{"K":"hello"},"I":"hello"}`, | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/29732. | |
| name: jsontest.Name("Methods/MapKey/JSONv2"), | |
| in: map[structMethodJSONv2]string{{"k1"}: "v1", {"k2"}: "v2"}, | |
| want: `{"k1":"v1","k2":"v2"}`, | |
| canonicalize: true, | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/29732. | |
| name: jsontest.Name("Methods/MapKey/JSONv1"), | |
| in: map[structMethodJSONv1]string{{"k1"}: "v1", {"k2"}: "v2"}, | |
| want: `{"k1":"v1","k2":"v2"}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Methods/MapKey/Text"), | |
| in: map[structMethodText]string{{"k1"}: "v1", {"k2"}: "v2"}, | |
| want: `{"k1":"v1","k2":"v2"}`, | |
| canonicalize: true, | |
| }, { | |
| name: jsontest.Name("Methods/JSONv2/ErrUnsupported"), | |
| opts: []Options{Deterministic(true)}, | |
| in: unsupportedMethodJSONv2{"fizz": 123}, | |
| want: `{"called":1,"fizz":123}`, | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/Error"), | |
| in: marshalJSONv2Func(func(*jsontext.Encoder) error { | |
| return errSomeError | |
| }), | |
| wantErr: EM(errSomeError).withType(0, T[marshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/TooFew"), | |
| in: marshalJSONv2Func(func(*jsontext.Encoder) error { | |
| return nil // do nothing | |
| }), | |
| wantErr: EM(errNonSingularValue).withType(0, T[marshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/TooMany"), | |
| in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { | |
| enc.WriteToken(jsontext.Null) | |
| enc.WriteToken(jsontext.Null) | |
| return nil | |
| }), | |
| want: `nullnull`, | |
| wantErr: EM(errNonSingularValue).withPos(`nullnull`, "").withType(0, T[marshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/ErrUnsupported"), | |
| in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { | |
| return errors.ErrUnsupported | |
| }), | |
| wantErr: EM(nil).withType(0, T[marshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv1/Error"), | |
| in: marshalJSONv1Func(func() ([]byte, error) { | |
| return nil, errSomeError | |
| }), | |
| wantErr: EM(errSomeError).withType(0, T[marshalJSONv1Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv1/Syntax"), | |
| in: marshalJSONv1Func(func() ([]byte, error) { | |
| return []byte("invalid"), nil | |
| }), | |
| wantErr: EM(newInvalidCharacterError("i", "at start of value", 0, "")).withType(0, T[marshalJSONv1Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv1/ErrUnsupported"), | |
| in: marshalJSONv1Func(func() ([]byte, error) { | |
| return nil, errors.ErrUnsupported | |
| }), | |
| wantErr: EM(errors.New("MarshalJSON method may not return errors.ErrUnsupported")).withType(0, T[marshalJSONv1Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/AppendText"), | |
| in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "hello"...), nil }), | |
| want: `"hello"`, | |
| }, { | |
| name: jsontest.Name("Methods/AppendText/Error"), | |
| in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "hello"...), errSomeError }), | |
| wantErr: EM(errSomeError).withType(0, T[appendTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/AppendText/NeedEscape"), | |
| in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, `"`...), nil }), | |
| want: `"\""`, | |
| }, { | |
| name: jsontest.Name("Methods/AppendText/RejectInvalidUTF8"), | |
| in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "\xde\xad\xbe\xef"...), nil }), | |
| wantErr: EM(newInvalidUTF8Error(0, "")).withType(0, T[appendTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/AppendText/AllowInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "\xde\xad\xbe\xef"...), nil }), | |
| want: "\"\xde\xad\ufffd\ufffd\"", | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/Text/Error"), | |
| in: marshalTextFunc(func() ([]byte, error) { | |
| return nil, errSomeError | |
| }), | |
| wantErr: EM(errSomeError).withType(0, T[marshalTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/Text/RejectInvalidUTF8"), | |
| in: marshalTextFunc(func() ([]byte, error) { | |
| return []byte("\xde\xad\xbe\xef"), nil | |
| }), | |
| wantErr: EM(newInvalidUTF8Error(0, "")).withType(0, T[marshalTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/Text/AllowInvalidUTF8"), | |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, | |
| in: marshalTextFunc(func() ([]byte, error) { | |
| return []byte("\xde\xad\xbe\xef"), nil | |
| }), | |
| want: "\"\xde\xad\ufffd\ufffd\"", | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/Text/ErrUnsupported"), | |
| in: marshalTextFunc(func() ([]byte, error) { | |
| return nil, errors.ErrUnsupported | |
| }), | |
| wantErr: EM(wrapErrUnsupported(errors.ErrUnsupported, "MarshalText method")).withType(0, T[marshalTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/MapKey/JSONv2/Syntax"), | |
| in: map[any]string{ | |
| addr(marshalJSONv2Func(func(enc *jsontext.Encoder) error { | |
| return enc.WriteToken(jsontext.Null) | |
| })): "invalid", | |
| }, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[marshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/MapKey/JSONv1/Syntax"), | |
| in: map[any]string{ | |
| addr(marshalJSONv1Func(func() ([]byte, error) { | |
| return []byte(`null`), nil | |
| })): "invalid", | |
| }, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[marshalJSONv1Func]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Bool/Empty"), | |
| opts: []Options{WithMarshalers(nil)}, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/NamedBool/V1/NoMatch"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(namedBool) ([]byte, error) { | |
| return nil, errMustNotCall | |
| })), | |
| }, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/NamedBool/V1/Match"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(namedBool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: namedBool(true), | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/PointerBool/V1/Match"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v *bool) ([]byte, error) { | |
| _ = *v // must be a non-nil pointer | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return enc.WriteToken(jsontext.String("called")) | |
| })), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/NamedBool/V2/NoMatch"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v namedBool) error { | |
| return errMustNotCall | |
| })), | |
| }, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/NamedBool/V2/Match"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v namedBool) error { | |
| return enc.WriteToken(jsontext.String("called")) | |
| })), | |
| }, | |
| in: namedBool(true), | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/PointerBool/V2/Match"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { | |
| _ = *v // must be a non-nil pointer | |
| return enc.WriteToken(jsontext.String("called")) | |
| })), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Bool/Empty1/NoMatch"), | |
| opts: []Options{ | |
| WithMarshalers(new(Marshalers)), | |
| }, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/Bool/Empty2/NoMatch"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers()), | |
| }, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V1/DirectError"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { | |
| return nil, errSomeError | |
| })), | |
| }, | |
| in: true, | |
| wantErr: EM(errSomeError).withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V1/SkipError"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { | |
| return nil, errors.ErrUnsupported | |
| })), | |
| }, | |
| in: true, | |
| wantErr: EM(wrapErrUnsupported(errors.ErrUnsupported, "marshal function of type func(T) ([]byte, error)")).withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V1/InvalidValue"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { | |
| return []byte("invalid"), nil | |
| })), | |
| }, | |
| in: true, | |
| wantErr: EM(newInvalidCharacterError("i", "at start of value", 0, "")).withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2/DirectError"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return errSomeError | |
| })), | |
| }, | |
| in: true, | |
| wantErr: EM(errSomeError).withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2/TooFew"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return nil | |
| })), | |
| }, | |
| in: true, | |
| wantErr: EM(errNonSingularValue).withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2/TooMany"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| enc.WriteValue([]byte(`"hello"`)) | |
| enc.WriteValue([]byte(`"world"`)) | |
| return nil | |
| })), | |
| }, | |
| in: true, | |
| want: `"hello""world"`, | |
| wantErr: EM(errNonSingularValue).withPos(`"hello""world"`, "").withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2/Skipped"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2/ProcessBeforeSkip"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| enc.WriteValue([]byte(`"hello"`)) | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| in: true, | |
| want: `"hello"`, | |
| wantErr: EM(errUnsupportedMutation).withPos(`"hello"`, "").withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Functions/Bool/V2/WrappedUnsupportedError"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return fmt.Errorf("wrap: %w", errors.ErrUnsupported) | |
| })), | |
| }, | |
| in: true, | |
| want: `true`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{"called":"world"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/PointerNoCaseString/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v *nocaseString) ([]byte, error) { | |
| _ = *v // must be a non-nil pointer | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{"called":"world"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/TextMarshaler/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v encoding.TextMarshaler) ([]byte, error) { | |
| _ = *v.(*nocaseString) // must be a non-nil *nocaseString | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{"called":"world"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V1/InvalidValue"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { | |
| return []byte(`null`), nil | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V2/InvalidKind"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { | |
| return []byte(`null`), nil | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/String/V1/DuplicateName"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v string) ([]byte, error) { | |
| return []byte(`"name"`), nil | |
| })), | |
| }, | |
| in: map[string]string{"name1": "value", "name2": "value"}, | |
| want: `{"name":"name"`, | |
| wantErr: EM(newDuplicateNameError("", []byte(`"name"`), len64(`{"name":"name",`))). | |
| withPos(`{"name":"name",`, "").withType(0, T[string]()), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{"called":"world"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/PointerNoCaseString/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *nocaseString) error { | |
| _ = *v // must be a non-nil pointer | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{"called":"world"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/TextMarshaler/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v encoding.TextMarshaler) error { | |
| _ = *v.(*nocaseString) // must be a non-nil *nocaseString | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{"called":"world"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V2/InvalidToken"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { | |
| return enc.WriteToken(jsontext.Null) | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V2/InvalidValue"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { | |
| return enc.WriteValue([]byte(`null`)) | |
| })), | |
| }, | |
| in: map[nocaseString]string{"hello": "world"}, | |
| want: `{`, | |
| wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/NoCaseString/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: map[string]nocaseString{"hello": "world"}, | |
| want: `{"hello":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/PointerNoCaseString/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v *nocaseString) ([]byte, error) { | |
| _ = *v // must be a non-nil pointer | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: map[string]nocaseString{"hello": "world"}, | |
| want: `{"hello":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/TextMarshaler/V1"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v encoding.TextMarshaler) ([]byte, error) { | |
| _ = *v.(*nocaseString) // must be a non-nil *nocaseString | |
| return []byte(`"called"`), nil | |
| })), | |
| }, | |
| in: map[string]nocaseString{"hello": "world"}, | |
| want: `{"hello":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/NoCaseString/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: map[string]nocaseString{"hello": "world"}, | |
| want: `{"hello":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/PointerNoCaseString/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *nocaseString) error { | |
| _ = *v // must be a non-nil pointer | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: map[string]nocaseString{"hello": "world"}, | |
| want: `{"hello":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/TextMarshaler/V2"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v encoding.TextMarshaler) error { | |
| _ = *v.(*nocaseString) // must be a non-nil *nocaseString | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: map[string]nocaseString{"hello": "world"}, | |
| want: `{"hello":"called"}`, | |
| }, { | |
| name: jsontest.Name("Funtions/Struct/Fields"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalFunc(func(v bool) ([]byte, error) { | |
| return []byte(`"called1"`), nil | |
| }), | |
| MarshalFunc(func(v *string) ([]byte, error) { | |
| return []byte(`"called2"`), nil | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v []byte) error { | |
| return enc.WriteValue([]byte(`"called3"`)) | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v *int64) error { | |
| return enc.WriteValue([]byte(`"called4"`)) | |
| }), | |
| )), | |
| }, | |
| in: structScalars{}, | |
| want: `{"Bool":"called1","String":"called2","Bytes":"called3","Int":"called4","Uint":0,"Float":0}`, | |
| }, { | |
| name: jsontest.Name("Functions/Struct/OmitEmpty"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalFunc(func(v bool) ([]byte, error) { | |
| return []byte(`null`), nil | |
| }), | |
| MarshalFunc(func(v string) ([]byte, error) { | |
| return []byte(`"called1"`), nil | |
| }), | |
| MarshalFunc(func(v *stringMarshalNonEmpty) ([]byte, error) { | |
| return []byte(`""`), nil | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v bytesMarshalNonEmpty) error { | |
| return enc.WriteValue([]byte(`{}`)) | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v *float64) error { | |
| return enc.WriteValue([]byte(`[]`)) | |
| }), | |
| MarshalFunc(func(v mapMarshalNonEmpty) ([]byte, error) { | |
| return []byte(`"called2"`), nil | |
| }), | |
| MarshalFunc(func(v []string) ([]byte, error) { | |
| return []byte(`"called3"`), nil | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v *sliceMarshalNonEmpty) error { | |
| return enc.WriteValue([]byte(`"called4"`)) | |
| }), | |
| )), | |
| }, | |
| in: structOmitEmptyAll{}, | |
| want: `{"String":"called1","MapNonEmpty":"called2","Slice":"called3","SliceNonEmpty":"called4"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Struct/OmitZero"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalFunc(func(v bool) ([]byte, error) { | |
| panic("should not be called") | |
| }), | |
| MarshalFunc(func(v *string) ([]byte, error) { | |
| panic("should not be called") | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v []byte) error { | |
| panic("should not be called") | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v *int64) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| in: structOmitZeroAll{}, | |
| want: `{}`, | |
| }, { | |
| name: jsontest.Name("Functions/Struct/Embedded"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalFunc(func(v structEmbeddedL1) ([]byte, error) { | |
| panic("should not be called") | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v *StructEmbed2) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| in: structEmbedded{}, | |
| want: `{"D":""}`, | |
| }, { | |
| name: jsontest.Name("Functions/Slice/Elem"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalFunc(func(v bool) ([]byte, error) { | |
| return []byte(`"` + strconv.FormatBool(v) + `"`), nil | |
| })), | |
| }, | |
| in: []bool{true, false}, | |
| want: `["true","false"]`, | |
| }, { | |
| name: jsontest.Name("Functions/Array/Elem"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { | |
| return enc.WriteValue([]byte(`"` + strconv.FormatBool(*v) + `"`)) | |
| })), | |
| }, | |
| in: [2]bool{true, false}, | |
| want: `["true","false"]`, | |
| }, { | |
| name: jsontest.Name("Functions/Pointer/Nil"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { | |
| panic("should not be called") | |
| })), | |
| }, | |
| in: struct{ X *bool }{nil}, | |
| want: `{"X":null}`, | |
| }, { | |
| name: jsontest.Name("Functions/Pointer/NonNil"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: struct{ X *bool }{addr(false)}, | |
| want: `{"X":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Interface/Nil"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v fmt.Stringer) error { | |
| panic("should not be called") | |
| })), | |
| }, | |
| in: struct{ X fmt.Stringer }{nil}, | |
| want: `{"X":null}`, | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NonNil/MatchInterface"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v fmt.Stringer) error { | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: struct{ X fmt.Stringer }{valueStringer{}}, | |
| want: `{"X":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NonNil/MatchConcrete"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v valueStringer) error { | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: struct{ X fmt.Stringer }{valueStringer{}}, | |
| want: `{"X":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NonNil/MatchPointer"), | |
| opts: []Options{ | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *valueStringer) error { | |
| return enc.WriteValue([]byte(`"called"`)) | |
| })), | |
| }, | |
| in: struct{ X fmt.Stringer }{valueStringer{}}, | |
| want: `{"X":"called"}`, | |
| }, { | |
| name: jsontest.Name("Functions/Interface/Any"), | |
| opts: []Options{ | |
| WithMarshalers(func() *Marshalers { | |
| type P struct { | |
| D int | |
| N int64 | |
| } | |
| type PV struct { | |
| P P | |
| V any | |
| } | |
| var lastChecks []func() error | |
| checkLast := func() error { | |
| for _, fn := range lastChecks { | |
| if err := fn(); err != nil { | |
| return err | |
| } | |
| } | |
| return errors.ErrUnsupported | |
| } | |
| makeValueChecker := func(name string, want []PV) func(e *jsontext.Encoder, v any) error { | |
| checkNext := func(e *jsontext.Encoder, v any) error { | |
| xe := export.Encoder(e) | |
| p := P{len(xe.Tokens.Stack), xe.Tokens.Last.Length()} | |
| rv := reflect.ValueOf(v) | |
| pv := PV{p, v} | |
| switch { | |
| case len(want) == 0: | |
| return fmt.Errorf("%s: %v: got more values than expected", name, p) | |
| case !rv.IsValid() || rv.Kind() != reflect.Pointer || rv.IsNil(): | |
| return fmt.Errorf("%s: %v: got %#v, want non-nil pointer type", name, p, v) | |
| case !reflect.DeepEqual(pv, want[0]): | |
| return fmt.Errorf("%s:\n\tgot %#v\n\twant %#v", name, pv, want[0]) | |
| default: | |
| want = want[1:] | |
| return errors.ErrUnsupported | |
| } | |
| } | |
| lastChecks = append(lastChecks, func() error { | |
| if len(want) > 0 { | |
| return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) | |
| } | |
| return nil | |
| }) | |
| return checkNext | |
| } | |
| makePositionChecker := func(name string, want []P) func(e *jsontext.Encoder, v any) error { | |
| checkNext := func(e *jsontext.Encoder, v any) error { | |
| xe := export.Encoder(e) | |
| p := P{len(xe.Tokens.Stack), xe.Tokens.Last.Length()} | |
| switch { | |
| case len(want) == 0: | |
| return fmt.Errorf("%s: %v: got more values than wanted", name, p) | |
| case p != want[0]: | |
| return fmt.Errorf("%s: got %v, want %v", name, p, want[0]) | |
| default: | |
| want = want[1:] | |
| return errors.ErrUnsupported | |
| } | |
| } | |
| lastChecks = append(lastChecks, func() error { | |
| if len(want) > 0 { | |
| return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) | |
| } | |
| return nil | |
| }) | |
| return checkNext | |
| } | |
| wantAny := []PV{ | |
| {P{0, 0}, addr([]any{ | |
| nil, | |
| valueStringer{}, | |
| (*valueStringer)(nil), | |
| addr(valueStringer{}), | |
| (**valueStringer)(nil), | |
| addr((*valueStringer)(nil)), | |
| addr(addr(valueStringer{})), | |
| pointerStringer{}, | |
| (*pointerStringer)(nil), | |
| addr(pointerStringer{}), | |
| (**pointerStringer)(nil), | |
| addr((*pointerStringer)(nil)), | |
| addr(addr(pointerStringer{})), | |
| "LAST", | |
| })}, | |
| {P{1, 0}, addr(any(nil))}, | |
| {P{1, 1}, addr(any(valueStringer{}))}, | |
| {P{1, 1}, addr(valueStringer{})}, | |
| {P{1, 2}, addr(any((*valueStringer)(nil)))}, | |
| {P{1, 2}, addr((*valueStringer)(nil))}, | |
| {P{1, 3}, addr(any(addr(valueStringer{})))}, | |
| {P{1, 3}, addr(addr(valueStringer{}))}, | |
| {P{1, 3}, addr(valueStringer{})}, | |
| {P{1, 4}, addr(any((**valueStringer)(nil)))}, | |
| {P{1, 4}, addr((**valueStringer)(nil))}, | |
| {P{1, 5}, addr(any(addr((*valueStringer)(nil))))}, | |
| {P{1, 5}, addr(addr((*valueStringer)(nil)))}, | |
| {P{1, 5}, addr((*valueStringer)(nil))}, | |
| {P{1, 6}, addr(any(addr(addr(valueStringer{}))))}, | |
| {P{1, 6}, addr(addr(addr(valueStringer{})))}, | |
| {P{1, 6}, addr(addr(valueStringer{}))}, | |
| {P{1, 6}, addr(valueStringer{})}, | |
| {P{1, 7}, addr(any(pointerStringer{}))}, | |
| {P{1, 7}, addr(pointerStringer{})}, | |
| {P{1, 8}, addr(any((*pointerStringer)(nil)))}, | |
| {P{1, 8}, addr((*pointerStringer)(nil))}, | |
| {P{1, 9}, addr(any(addr(pointerStringer{})))}, | |
| {P{1, 9}, addr(addr(pointerStringer{}))}, | |
| {P{1, 9}, addr(pointerStringer{})}, | |
| {P{1, 10}, addr(any((**pointerStringer)(nil)))}, | |
| {P{1, 10}, addr((**pointerStringer)(nil))}, | |
| {P{1, 11}, addr(any(addr((*pointerStringer)(nil))))}, | |
| {P{1, 11}, addr(addr((*pointerStringer)(nil)))}, | |
| {P{1, 11}, addr((*pointerStringer)(nil))}, | |
| {P{1, 12}, addr(any(addr(addr(pointerStringer{}))))}, | |
| {P{1, 12}, addr(addr(addr(pointerStringer{})))}, | |
| {P{1, 12}, addr(addr(pointerStringer{}))}, | |
| {P{1, 12}, addr(pointerStringer{})}, | |
| {P{1, 13}, addr(any("LAST"))}, | |
| {P{1, 13}, addr("LAST")}, | |
| } | |
| checkAny := makeValueChecker("any", wantAny) | |
| anyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v any) error { | |
| return checkAny(enc, v) | |
| }) | |
| var wantPointerAny []PV | |
| for _, v := range wantAny { | |
| if _, ok := v.V.(*any); ok { | |
| wantPointerAny = append(wantPointerAny, v) | |
| } | |
| } | |
| checkPointerAny := makeValueChecker("*any", wantPointerAny) | |
| pointerAnyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *any) error { | |
| return checkPointerAny(enc, v) | |
| }) | |
| checkNamedAny := makeValueChecker("namedAny", wantAny) | |
| namedAnyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v namedAny) error { | |
| return checkNamedAny(enc, v) | |
| }) | |
| checkPointerNamedAny := makeValueChecker("*namedAny", nil) | |
| pointerNamedAnyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *namedAny) error { | |
| return checkPointerNamedAny(enc, v) | |
| }) | |
| type stringer = fmt.Stringer | |
| var wantStringer []PV | |
| for _, v := range wantAny { | |
| if _, ok := v.V.(stringer); ok { | |
| wantStringer = append(wantStringer, v) | |
| } | |
| } | |
| checkStringer := makeValueChecker("stringer", wantStringer) | |
| stringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v stringer) error { | |
| return checkStringer(enc, v) | |
| }) | |
| checkPointerStringer := makeValueChecker("*stringer", nil) | |
| pointerStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *stringer) error { | |
| return checkPointerStringer(enc, v) | |
| }) | |
| wantValueStringer := []P{{1, 1}, {1, 3}, {1, 6}} | |
| checkValueValueStringer := makePositionChecker("valueStringer", wantValueStringer) | |
| valueValueStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v valueStringer) error { | |
| return checkValueValueStringer(enc, v) | |
| }) | |
| checkPointerValueStringer := makePositionChecker("*valueStringer", wantValueStringer) | |
| pointerValueStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *valueStringer) error { | |
| return checkPointerValueStringer(enc, v) | |
| }) | |
| wantPointerStringer := []P{{1, 7}, {1, 9}, {1, 12}} | |
| checkValuePointerStringer := makePositionChecker("pointerStringer", wantPointerStringer) | |
| valuePointerStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v pointerStringer) error { | |
| return checkValuePointerStringer(enc, v) | |
| }) | |
| checkPointerPointerStringer := makePositionChecker("*pointerStringer", wantPointerStringer) | |
| pointerPointerStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *pointerStringer) error { | |
| return checkPointerPointerStringer(enc, v) | |
| }) | |
| lastMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v string) error { | |
| return checkLast() | |
| }) | |
| return JoinMarshalers( | |
| anyMarshaler, | |
| pointerAnyMarshaler, | |
| namedAnyMarshaler, | |
| pointerNamedAnyMarshaler, // never called | |
| stringerMarshaler, | |
| pointerStringerMarshaler, // never called | |
| valueValueStringerMarshaler, | |
| pointerValueStringerMarshaler, | |
| valuePointerStringerMarshaler, | |
| pointerPointerStringerMarshaler, | |
| lastMarshaler, | |
| ) | |
| }()), | |
| }, | |
| in: []any{ | |
| nil, // nil | |
| valueStringer{}, // T | |
| (*valueStringer)(nil), // *T | |
| addr(valueStringer{}), // *T | |
| (**valueStringer)(nil), // **T | |
| addr((*valueStringer)(nil)), // **T | |
| addr(addr(valueStringer{})), // **T | |
| pointerStringer{}, // T | |
| (*pointerStringer)(nil), // *T | |
| addr(pointerStringer{}), // *T | |
| (**pointerStringer)(nil), // **T | |
| addr((*pointerStringer)(nil)), // **T | |
| addr(addr(pointerStringer{})), // **T | |
| "LAST", | |
| }, | |
| want: `[null,{},null,{},null,null,{},{},null,{},null,null,{},"LAST"]`, | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/V1First"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| }), | |
| MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/V2First"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return enc.WriteToken(jsontext.String("called")) | |
| }), | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/V2Skipped"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { | |
| return errors.ErrUnsupported | |
| }), | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| }), | |
| )), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/NestedFirst"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| JoinMarshalers( | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| }), | |
| ), | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/NestedLast"), | |
| opts: []Options{ | |
| WithMarshalers(JoinMarshalers( | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| return []byte(`"called"`), nil | |
| }), | |
| JoinMarshalers( | |
| MarshalFunc(func(bool) ([]byte, error) { | |
| panic("should not be called") | |
| }), | |
| ), | |
| )), | |
| }, | |
| in: true, | |
| want: `"called"`, | |
| }, { | |
| name: jsontest.Name("Duration/Zero"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{0, 0}, | |
| want: `{"D1":"0s","D2":0}`, | |
| }, { | |
| name: jsontest.Name("Duration/Positive"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{ | |
| 123456789123456789, | |
| 123456789123456789, | |
| }, | |
| want: `{"D1":"34293h33m9.123456789s","D2":123456789123456789}`, | |
| }, { | |
| name: jsontest.Name("Duration/Negative"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{ | |
| -123456789123456789, | |
| -123456789123456789, | |
| }, | |
| want: `{"D1":"-34293h33m9.123456789s","D2":-123456789123456789}`, | |
| }, { | |
| name: jsontest.Name("Duration/Nanos/String"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| D1 time.Duration `json:",string,format:nano"` | |
| D2 time.Duration `json:",string,format:nano"` | |
| D3 time.Duration `json:",string,format:nano"` | |
| }{ | |
| math.MinInt64, | |
| 0, | |
| math.MaxInt64, | |
| }, | |
| want: `{"D1":"-9223372036854775808","D2":"0","D3":"9223372036854775807"}`, | |
| }, { | |
| name: jsontest.Name("Duration/Format/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| D time.Duration `json:",format:invalid"` | |
| }{}, | |
| want: `{"D"`, | |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"D":`, "/D").withType(0, T[time.Duration]()), | |
| }, { | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: jsontest.Name("Duration/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: time.Duration(0), | |
| want: `"0s"`, | |
| }, { */ | |
| name: jsontest.Name("Duration/Format"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: structDurationFormat{ | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| }, | |
| want: `{ | |
| "D1": "12h34m56.078090012s", | |
| "D2": "12h34m56.078090012s", | |
| "D3": 45296.078090012, | |
| "D4": "45296.078090012", | |
| "D5": 45296078.090012, | |
| "D6": "45296078.090012", | |
| "D7": 45296078090.012, | |
| "D8": "45296078090.012", | |
| "D9": 45296078090012, | |
| "D10": "45296078090012", | |
| "D11": "PT12H34M56.078090012S" | |
| }`, | |
| }, { | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: jsontest.Name("Duration/Format/Legacy"), | |
| opts: []Options{jsonflags.FormatDurationAsNano | 1}, | |
| in: structDurationFormat{ | |
| D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| }, | |
| want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0","D11":"PT0S"}`, | |
| }, { */ | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: jsontest.Name("Duration/MapKey"), | |
| in: map[time.Duration]string{time.Second: ""}, | |
| want: `{"1s":""}`, | |
| }, { */ | |
| name: jsontest.Name("Duration/MapKey/Legacy"), | |
| opts: []Options{jsonflags.FormatDurationAsNano | 1}, | |
| in: map[time.Duration]string{time.Second: ""}, | |
| want: `{"1000000000":""}`, | |
| }, { | |
| name: jsontest.Name("Time/Zero"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| T1 time.Time | |
| T2 time.Time `json:",format:RFC822"` | |
| T3 time.Time `json:",format:'2006-01-02'"` | |
| T4 time.Time `json:",omitzero"` | |
| T5 time.Time `json:",omitempty"` | |
| }{ | |
| time.Time{}, | |
| time.Time{}, | |
| time.Time{}, | |
| // This is zero according to time.Time.IsZero, | |
| // but non-zero according to reflect.Value.IsZero. | |
| time.Date(1, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC", 0)), | |
| time.Time{}, | |
| }, | |
| want: `{"T1":"0001-01-01T00:00:00Z","T2":"01 Jan 01 00:00 UTC","T3":"0001-01-01","T5":"0001-01-01T00:00:00Z"}`, | |
| }, { | |
| name: jsontest.Name("Time/Format"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsontext.Multiline(true)}, | |
| in: structTimeFormat{ | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), | |
| }, | |
| want: `{ | |
| "T1": "1234-01-02T03:04:05.000000006Z", | |
| "T2": "Mon Jan 2 03:04:05 1234", | |
| "T3": "Mon Jan 2 03:04:05 UTC 1234", | |
| "T4": "Mon Jan 02 03:04:05 +0000 1234", | |
| "T5": "02 Jan 34 03:04 UTC", | |
| "T6": "02 Jan 34 03:04 +0000", | |
| "T7": "Monday, 02-Jan-34 03:04:05 UTC", | |
| "T8": "Mon, 02 Jan 1234 03:04:05 UTC", | |
| "T9": "Mon, 02 Jan 1234 03:04:05 +0000", | |
| "T10": "1234-01-02T03:04:05Z", | |
| "T11": "1234-01-02T03:04:05.000000006Z", | |
| "T12": "3:04AM", | |
| "T13": "Jan 2 03:04:05", | |
| "T14": "Jan 2 03:04:05.000", | |
| "T15": "Jan 2 03:04:05.000000", | |
| "T16": "Jan 2 03:04:05.000000006", | |
| "T17": "1234-01-02 03:04:05", | |
| "T18": "1234-01-02", | |
| "T19": "03:04:05", | |
| "T20": "1234-01-02", | |
| "T21": "\"weird\"1234", | |
| "T22": -23225777754.999999994, | |
| "T23": "-23225777754.999999994", | |
| "T24": -23225777754999.999994, | |
| "T25": "-23225777754999.999994", | |
| "T26": -23225777754999999.994, | |
| "T27": "-23225777754999999.994", | |
| "T28": -23225777754999999994, | |
| "T29": "-23225777754999999994" | |
| }`, | |
| }, { | |
| name: jsontest.Name("Time/Format/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: struct { | |
| T time.Time `json:",format:UndefinedConstant"` | |
| }{}, | |
| want: `{"T"`, | |
| wantErr: EM(errors.New(`invalid format flag "UndefinedConstant"`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/String/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| in: structTimeFormatStringInvalid{}, | |
| want: `{"T"`, | |
| wantErr: EM(errInvalidStringTag).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/YearOverflow"), | |
| in: struct { | |
| T1 time.Time | |
| T2 time.Time | |
| }{ | |
| time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), | |
| time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC), | |
| }, | |
| want: `{"T1":"9999-12-31T23:59:59Z","T2"`, | |
| wantErr: EM(errors.New(`year outside of range [0,9999]`)).withPos(`{"T1":"9999-12-31T23:59:59Z","T2":`, "/T2").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/YearUnderflow"), | |
| in: struct { | |
| T1 time.Time | |
| T2 time.Time | |
| }{ | |
| time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), | |
| time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), | |
| }, | |
| want: `{"T1":"0000-01-01T00:00:00Z","T2"`, | |
| wantErr: EM(errors.New(`year outside of range [0,9999]`)).withPos(`{"T1":"0000-01-01T00:00:00Z","T2":`, "/T2").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/YearUnderflow"), | |
| in: struct{ T time.Time }{time.Date(-998, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second)}, | |
| want: `{"T"`, | |
| wantErr: EM(errors.New(`year outside of range [0,9999]`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/ZoneExact"), | |
| in: struct{ T time.Time }{time.Date(2020, 1, 1, 0, 0, 0, 0, time.FixedZone("", 23*60*60+59*60))}, | |
| want: `{"T":"2020-01-01T00:00:00+23:59"}`, | |
| }, { | |
| name: jsontest.Name("Time/Format/ZoneHourOverflow"), | |
| in: struct{ T time.Time }{time.Date(2020, 1, 1, 0, 0, 0, 0, time.FixedZone("", 24*60*60))}, | |
| want: `{"T"`, | |
| wantErr: EM(errors.New(`timezone hour outside of range [0,23]`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/ZoneHourOverflow"), | |
| in: struct{ T time.Time }{time.Date(2020, 1, 1, 0, 0, 0, 0, time.FixedZone("", 123*60*60))}, | |
| want: `{"T"`, | |
| wantErr: EM(errors.New(`timezone hour outside of range [0,23]`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| in: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), | |
| want: `"2000-01-01T00:00:00Z"`, | |
| }} | |
| for _, tt := range tests { | |
| t.Run(tt.name.Name, func(t *testing.T) { | |
| var got []byte | |
| var gotErr error | |
| if tt.useWriter { | |
| bb := new(struct{ bytes.Buffer }) // avoid optimizations with bytes.Buffer | |
| gotErr = MarshalWrite(bb, tt.in, tt.opts...) | |
| got = bb.Bytes() | |
| } else { | |
| got, gotErr = Marshal(tt.in, tt.opts...) | |
| } | |
| if tt.canonicalize { | |
| (*jsontext.Value)(&got).Canonicalize() | |
| } | |
| if string(got) != tt.want { | |
| t.Errorf("%s: Marshal output mismatch:\ngot %s\nwant %s", tt.name.Where, got, tt.want) | |
| } | |
| if !reflect.DeepEqual(gotErr, tt.wantErr) { | |
| t.Errorf("%s: Marshal error mismatch:\ngot %v\nwant %v", tt.name.Where, gotErr, tt.wantErr) | |
| } | |
| }) | |
| } | |
| } | |
| func TestUnmarshal(t *testing.T) { | |
| tests := []struct { | |
| name jsontest.CaseName | |
| opts []Options | |
| inBuf string | |
| inVal any | |
| want any | |
| wantErr error | |
| skip bool | |
| }{{ | |
| name: jsontest.Name("Nil"), | |
| inBuf: `null`, | |
| wantErr: EU(internal.ErrNonNilReference), | |
| }, { | |
| name: jsontest.Name("NilPointer"), | |
| inBuf: `null`, | |
| inVal: (*string)(nil), | |
| want: (*string)(nil), | |
| wantErr: EU(internal.ErrNonNilReference).withType(0, T[*string]()), | |
| }, { | |
| name: jsontest.Name("NonPointer"), | |
| inBuf: `null`, | |
| inVal: "unchanged", | |
| want: "unchanged", | |
| wantErr: EU(internal.ErrNonNilReference).withType(0, T[string]()), | |
| }, { | |
| name: jsontest.Name("Bools/TrailingJunk"), | |
| inBuf: `falsetrue`, | |
| inVal: addr(true), | |
| want: addr(false), | |
| wantErr: newInvalidCharacterError("t", "after top-level value", len64(`false`), ""), | |
| }, { | |
| name: jsontest.Name("Bools/Null"), | |
| inBuf: `null`, | |
| inVal: addr(true), | |
| want: addr(false), | |
| }, { | |
| name: jsontest.Name("Bools"), | |
| inBuf: `[null,false,true]`, | |
| inVal: new([]bool), | |
| want: addr([]bool{false, false, true}), | |
| }, { | |
| name: jsontest.Name("Bools/Named"), | |
| inBuf: `[null,false,true]`, | |
| inVal: new([]namedBool), | |
| want: addr([]namedBool{false, false, true}), | |
| }, { | |
| name: jsontest.Name("Bools/Invalid/StringifiedFalse"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"false"`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('"', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/Invalid/StringifiedTrue"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"true"`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('"', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/StringifiedBool/True"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `"true"`, | |
| inVal: addr(false), | |
| want: addr(true), | |
| }, { | |
| name: jsontest.Name("Bools/StringifiedBool/False"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `"false"`, | |
| inVal: addr(true), | |
| want: addr(false), | |
| }, { | |
| name: jsontest.Name("Bools/StringifiedBool/InvalidWhitespace"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `"false "`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"false "`).withType('"', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/StringifiedBool/InvalidBool"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `false`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('f', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/Invalid/Number"), | |
| inBuf: `0`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('0', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/Invalid/String"), | |
| inBuf: `""`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('"', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('{', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr(true), | |
| want: addr(true), | |
| wantErr: EU(nil).withType('[', boolType), | |
| }, { | |
| name: jsontest.Name("Bools/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `false`, | |
| inVal: addr(true), | |
| want: addr(false), | |
| }, { | |
| name: jsontest.Name("Strings/Null"), | |
| inBuf: `null`, | |
| inVal: addr("something"), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Strings"), | |
| inBuf: `[null,"","hello","世界"]`, | |
| inVal: new([]string), | |
| want: addr([]string{"", "", "hello", "世界"}), | |
| }, { | |
| name: jsontest.Name("Strings/Escaped"), | |
| inBuf: `[null,"","\u0068\u0065\u006c\u006c\u006f","\u4e16\u754c"]`, | |
| inVal: new([]string), | |
| want: addr([]string{"", "", "hello", "世界"}), | |
| }, { | |
| name: jsontest.Name("Strings/Named"), | |
| inBuf: `[null,"","hello","世界"]`, | |
| inVal: new([]namedString), | |
| want: addr([]namedString{"", "", "hello", "世界"}), | |
| }, { | |
| name: jsontest.Name("Strings/Invalid/False"), | |
| inBuf: `false`, | |
| inVal: addr("nochange"), | |
| want: addr("nochange"), | |
| wantErr: EU(nil).withType('f', stringType), | |
| }, { | |
| name: jsontest.Name("Strings/Invalid/True"), | |
| inBuf: `true`, | |
| inVal: addr("nochange"), | |
| want: addr("nochange"), | |
| wantErr: EU(nil).withType('t', stringType), | |
| }, { | |
| name: jsontest.Name("Strings/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr("nochange"), | |
| want: addr("nochange"), | |
| wantErr: EU(nil).withType('{', stringType), | |
| }, { | |
| name: jsontest.Name("Strings/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr("nochange"), | |
| want: addr("nochange"), | |
| wantErr: EU(nil).withType('[', stringType), | |
| }, { | |
| name: jsontest.Name("Strings/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `"hello"`, | |
| inVal: addr("goodbye"), | |
| want: addr("hello"), | |
| }, { | |
| name: jsontest.Name("Strings/StringifiedString"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `"\"foo\""`, | |
| inVal: new(string), | |
| want: addr("foo"), | |
| }, { | |
| name: jsontest.Name("Strings/StringifiedString/InvalidWhitespace"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `"\"foo\" "`, | |
| inVal: new(string), | |
| want: new(string), | |
| wantErr: EU(newInvalidCharacterError(" ", "after string value", 0, "")).withType('"', stringType), | |
| }, { | |
| name: jsontest.Name("Strings/StringifiedString/InvalidString"), | |
| opts: []Options{jsonflags.StringTag | jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `""`, | |
| inVal: new(string), | |
| want: new(string), | |
| wantErr: EU(&jsontext.SyntacticError{Err: io.ErrUnexpectedEOF}).withType('"', stringType), | |
| }, { | |
| name: jsontest.Name("Bytes/Null"), | |
| inBuf: `null`, | |
| inVal: addr([]byte("something")), | |
| want: addr([]byte(nil)), | |
| }, { | |
| name: jsontest.Name("Bytes"), | |
| inBuf: `[null,"","AQ==","AQI=","AQID"]`, | |
| inVal: new([][]byte), | |
| want: addr([][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), | |
| }, { | |
| name: jsontest.Name("Bytes/Large"), | |
| inBuf: `"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2cgYW5kIGF0ZSB0aGUgaG9tZXdvcmsgdGhhdCBJIHNwZW50IHNvIG11Y2ggdGltZSBvbi4="`, | |
| inVal: new([]byte), | |
| want: addr([]byte("the quick brown fox jumped over the lazy dog and ate the homework that I spent so much time on.")), | |
| }, { | |
| name: jsontest.Name("Bytes/Reuse"), | |
| inBuf: `"AQID"`, | |
| inVal: addr([]byte("changed")), | |
| want: addr([]byte{1, 2, 3}), | |
| }, { | |
| name: jsontest.Name("Bytes/Escaped"), | |
| inBuf: `[null,"","\u0041\u0051\u003d\u003d","\u0041\u0051\u0049\u003d","\u0041\u0051\u0049\u0044"]`, | |
| inVal: new([][]byte), | |
| want: addr([][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), | |
| }, { | |
| name: jsontest.Name("Bytes/Named"), | |
| inBuf: `[null,"","AQ==","AQI=","AQID"]`, | |
| inVal: new([]namedBytes), | |
| want: addr([]namedBytes{nil, {}, {1}, {1, 2}, {1, 2, 3}}), | |
| }, { | |
| name: jsontest.Name("Bytes/NotStringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `[null,"","AQ==","AQI=","AQID"]`, | |
| inVal: new([][]byte), | |
| want: addr([][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), | |
| }, { | |
| // NOTE: []namedByte is not assignable to []byte, | |
| // so the following should be treated as a slice of uints. | |
| name: jsontest.Name("Bytes/Invariant"), | |
| inBuf: `[null,[],[1],[1,2],[1,2,3]]`, | |
| inVal: new([][]namedByte), | |
| want: addr([][]namedByte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), | |
| }, { | |
| // NOTE: This differs in behavior from v1, | |
| // but keeps the representation of slices and arrays more consistent. | |
| name: jsontest.Name("Bytes/ByteArray"), | |
| inBuf: `"aGVsbG8="`, | |
| inVal: new([5]byte), | |
| want: addr([5]byte{'h', 'e', 'l', 'l', 'o'}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray0/Valid"), | |
| inBuf: `""`, | |
| inVal: new([0]byte), | |
| want: addr([0]byte{}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray0/Invalid"), | |
| inBuf: `"A"`, | |
| inVal: new([0]byte), | |
| want: addr([0]byte{}), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 0), []byte("A")) | |
| return err | |
| }()).withType('"', T[[0]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray0/Overflow"), | |
| inBuf: `"AA=="`, | |
| inVal: new([0]byte), | |
| want: addr([0]byte{}), | |
| wantErr: EU(errors.New("decoded length of 1 mismatches array length of 0")).withType('"', T[[0]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray1/Valid"), | |
| inBuf: `"AQ=="`, | |
| inVal: new([1]byte), | |
| want: addr([1]byte{1}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray1/Invalid"), | |
| inBuf: `"$$=="`, | |
| inVal: new([1]byte), | |
| want: addr([1]byte{}), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 1), []byte("$$==")) | |
| return err | |
| }()).withType('"', T[[1]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray1/Underflow"), | |
| inBuf: `""`, | |
| inVal: new([1]byte), | |
| want: addr([1]byte{}), | |
| wantErr: EU(errors.New("decoded length of 0 mismatches array length of 1")).withType('"', T[[1]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray1/Overflow"), | |
| inBuf: `"AQI="`, | |
| inVal: new([1]byte), | |
| want: addr([1]byte{1}), | |
| wantErr: EU(errors.New("decoded length of 2 mismatches array length of 1")).withType('"', T[[1]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray2/Valid"), | |
| inBuf: `"AQI="`, | |
| inVal: new([2]byte), | |
| want: addr([2]byte{1, 2}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray2/Invalid"), | |
| inBuf: `"$$$="`, | |
| inVal: new([2]byte), | |
| want: addr([2]byte{}), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 2), []byte("$$$=")) | |
| return err | |
| }()).withType('"', T[[2]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray2/Underflow"), | |
| inBuf: `"AQ=="`, | |
| inVal: new([2]byte), | |
| want: addr([2]byte{1, 0}), | |
| wantErr: EU(errors.New("decoded length of 1 mismatches array length of 2")).withType('"', T[[2]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray2/Underflow/Allowed"), | |
| opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, | |
| inBuf: `"AQ=="`, | |
| inVal: new([2]byte), | |
| want: addr([2]byte{1, 0}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray2/Overflow"), | |
| inBuf: `"AQID"`, | |
| inVal: new([2]byte), | |
| want: addr([2]byte{1, 2}), | |
| wantErr: EU(errors.New("decoded length of 3 mismatches array length of 2")).withType('"', T[[2]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray2/Overflow/Allowed"), | |
| opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, | |
| inBuf: `"AQID"`, | |
| inVal: new([2]byte), | |
| want: addr([2]byte{1, 2}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray3/Valid"), | |
| inBuf: `"AQID"`, | |
| inVal: new([3]byte), | |
| want: addr([3]byte{1, 2, 3}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray3/Invalid"), | |
| inBuf: `"$$$$"`, | |
| inVal: new([3]byte), | |
| want: addr([3]byte{}), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 3), []byte("$$$$")) | |
| return err | |
| }()).withType('"', T[[3]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray3/Underflow"), | |
| inBuf: `"AQI="`, | |
| inVal: addr([3]byte{0xff, 0xff, 0xff}), | |
| want: addr([3]byte{1, 2, 0}), | |
| wantErr: EU(errors.New("decoded length of 2 mismatches array length of 3")).withType('"', T[[3]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray3/Overflow"), | |
| inBuf: `"AQIDAQ=="`, | |
| inVal: new([3]byte), | |
| want: addr([3]byte{1, 2, 3}), | |
| wantErr: EU(errors.New("decoded length of 4 mismatches array length of 3")).withType('"', T[[3]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray4/Valid"), | |
| inBuf: `"AQIDBA=="`, | |
| inVal: new([4]byte), | |
| want: addr([4]byte{1, 2, 3, 4}), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray4/Invalid"), | |
| inBuf: `"$$$$$$=="`, | |
| inVal: new([4]byte), | |
| want: addr([4]byte{}), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 4), []byte("$$$$$$==")) | |
| return err | |
| }()).withType('"', T[[4]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray4/Underflow"), | |
| inBuf: `"AQID"`, | |
| inVal: new([4]byte), | |
| want: addr([4]byte{1, 2, 3, 0}), | |
| wantErr: EU(errors.New("decoded length of 3 mismatches array length of 4")).withType('"', T[[4]byte]()), | |
| }, { | |
| name: jsontest.Name("Bytes/ByteArray4/Overflow"), | |
| inBuf: `"AQIDBAU="`, | |
| inVal: new([4]byte), | |
| want: addr([4]byte{1, 2, 3, 4}), | |
| wantErr: EU(errors.New("decoded length of 5 mismatches array length of 4")).withType('"', T[[4]byte]()), | |
| }, { | |
| // NOTE: []namedByte is not assignable to []byte, | |
| // so the following should be treated as a array of uints. | |
| name: jsontest.Name("Bytes/NamedByteArray"), | |
| inBuf: `[104,101,108,108,111]`, | |
| inVal: new([5]namedByte), | |
| want: addr([5]namedByte{'h', 'e', 'l', 'l', 'o'}), | |
| }, { | |
| name: jsontest.Name("Bytes/Valid/Denormalized"), | |
| inBuf: `"AR=="`, | |
| inVal: new([]byte), | |
| want: addr([]byte{1}), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Unpadded1"), | |
| inBuf: `"AQ="`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 0), []byte("AQ=")) | |
| return err | |
| }()).withType('"', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Unpadded2"), | |
| inBuf: `"AQ"`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 0), []byte("AQ")) | |
| return err | |
| }()).withType('"', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Character"), | |
| inBuf: `"@@@@"`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 3), []byte("@@@@")) | |
| return err | |
| }()).withType('"', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(nil).withType('t', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Number"), | |
| inBuf: `0`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(nil).withType('0', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(nil).withType('{', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr([]byte("nochange")), | |
| want: addr([]byte("nochange")), | |
| wantErr: EU(nil).withType('[', bytesType), | |
| }, { | |
| name: jsontest.Name("Bytes/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `"aGVsbG8="`, | |
| inVal: new([]byte), | |
| want: addr([]byte("hello")), | |
| }, { | |
| name: jsontest.Name("Ints/Null"), | |
| inBuf: `null`, | |
| inVal: addr(int(1)), | |
| want: addr(int(0)), | |
| }, { | |
| name: jsontest.Name("Ints/Int"), | |
| inBuf: `1`, | |
| inVal: addr(int(0)), | |
| want: addr(int(1)), | |
| }, { | |
| name: jsontest.Name("Ints/Int8/MinOverflow"), | |
| inBuf: `-129`, | |
| inVal: addr(int8(-1)), | |
| want: addr(int8(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`-129`).withType('0', T[int8]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int8/Min"), | |
| inBuf: `-128`, | |
| inVal: addr(int8(0)), | |
| want: addr(int8(-128)), | |
| }, { | |
| name: jsontest.Name("Ints/Int8/Max"), | |
| inBuf: `127`, | |
| inVal: addr(int8(0)), | |
| want: addr(int8(127)), | |
| }, { | |
| name: jsontest.Name("Ints/Int8/MaxOverflow"), | |
| inBuf: `128`, | |
| inVal: addr(int8(-1)), | |
| want: addr(int8(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`128`).withType('0', T[int8]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int16/MinOverflow"), | |
| inBuf: `-32769`, | |
| inVal: addr(int16(-1)), | |
| want: addr(int16(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`-32769`).withType('0', T[int16]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int16/Min"), | |
| inBuf: `-32768`, | |
| inVal: addr(int16(0)), | |
| want: addr(int16(-32768)), | |
| }, { | |
| name: jsontest.Name("Ints/Int16/Max"), | |
| inBuf: `32767`, | |
| inVal: addr(int16(0)), | |
| want: addr(int16(32767)), | |
| }, { | |
| name: jsontest.Name("Ints/Int16/MaxOverflow"), | |
| inBuf: `32768`, | |
| inVal: addr(int16(-1)), | |
| want: addr(int16(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`32768`).withType('0', T[int16]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int32/MinOverflow"), | |
| inBuf: `-2147483649`, | |
| inVal: addr(int32(-1)), | |
| want: addr(int32(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`-2147483649`).withType('0', T[int32]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int32/Min"), | |
| inBuf: `-2147483648`, | |
| inVal: addr(int32(0)), | |
| want: addr(int32(-2147483648)), | |
| }, { | |
| name: jsontest.Name("Ints/Int32/Max"), | |
| inBuf: `2147483647`, | |
| inVal: addr(int32(0)), | |
| want: addr(int32(2147483647)), | |
| }, { | |
| name: jsontest.Name("Ints/Int32/MaxOverflow"), | |
| inBuf: `2147483648`, | |
| inVal: addr(int32(-1)), | |
| want: addr(int32(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`2147483648`).withType('0', T[int32]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int64/MinOverflow"), | |
| inBuf: `-9223372036854775809`, | |
| inVal: addr(int64(-1)), | |
| want: addr(int64(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`-9223372036854775809`).withType('0', T[int64]()), | |
| }, { | |
| name: jsontest.Name("Ints/Int64/Min"), | |
| inBuf: `-9223372036854775808`, | |
| inVal: addr(int64(0)), | |
| want: addr(int64(-9223372036854775808)), | |
| }, { | |
| name: jsontest.Name("Ints/Int64/Max"), | |
| inBuf: `9223372036854775807`, | |
| inVal: addr(int64(0)), | |
| want: addr(int64(9223372036854775807)), | |
| }, { | |
| name: jsontest.Name("Ints/Int64/MaxOverflow"), | |
| inBuf: `9223372036854775808`, | |
| inVal: addr(int64(-1)), | |
| want: addr(int64(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`9223372036854775808`).withType('0', T[int64]()), | |
| }, { | |
| name: jsontest.Name("Ints/Named"), | |
| inBuf: `-6464`, | |
| inVal: addr(namedInt64(0)), | |
| want: addr(namedInt64(-6464)), | |
| }, { | |
| name: jsontest.Name("Ints/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"-6464"`, | |
| inVal: new(int), | |
| want: addr(int(-6464)), | |
| }, { | |
| name: jsontest.Name("Ints/Stringified/Invalid"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `-6464`, | |
| inVal: new(int), | |
| want: new(int), | |
| wantErr: EU(nil).withType('0', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Stringified/LeadingZero"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"00"`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"00"`).withType('"', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Escaped"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"\u002d\u0036\u0034\u0036\u0034"`, | |
| inVal: new(int), | |
| want: addr(int(-6464)), | |
| }, { | |
| name: jsontest.Name("Ints/Valid/NegativeZero"), | |
| inBuf: `-0`, | |
| inVal: addr(int(1)), | |
| want: addr(int(0)), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Fraction"), | |
| inBuf: `1.0`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`1.0`).withType('0', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Exponent"), | |
| inBuf: `1e0`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`1e0`).withType('0', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/StringifiedFraction"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"1.0"`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"1.0"`).withType('"', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/StringifiedExponent"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"1e0"`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"1e0"`).withType('"', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Overflow"), | |
| inBuf: `100000000000000000000000000000`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`100000000000000000000000000000`).withType('0', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/OverflowSyntax"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"100000000000000000000000000000x"`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"100000000000000000000000000000x"`).withType('"', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Whitespace"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"0 "`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"0 "`).withType('"', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(nil).withType('t', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/String"), | |
| inBuf: `"0"`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(nil).withType('"', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(nil).withType('{', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr(int(-1)), | |
| want: addr(int(-1)), | |
| wantErr: EU(nil).withType('[', T[int]()), | |
| }, { | |
| name: jsontest.Name("Ints/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `1`, | |
| inVal: addr(int(0)), | |
| want: addr(int(1)), | |
| }, { | |
| name: jsontest.Name("Uints/Null"), | |
| inBuf: `null`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(0)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint"), | |
| inBuf: `1`, | |
| inVal: addr(uint(0)), | |
| want: addr(uint(1)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint8/Min"), | |
| inBuf: `0`, | |
| inVal: addr(uint8(1)), | |
| want: addr(uint8(0)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint8/Max"), | |
| inBuf: `255`, | |
| inVal: addr(uint8(0)), | |
| want: addr(uint8(255)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint8/MaxOverflow"), | |
| inBuf: `256`, | |
| inVal: addr(uint8(1)), | |
| want: addr(uint8(1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`256`).withType('0', T[uint8]()), | |
| }, { | |
| name: jsontest.Name("Uints/Uint16/Min"), | |
| inBuf: `0`, | |
| inVal: addr(uint16(1)), | |
| want: addr(uint16(0)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint16/Max"), | |
| inBuf: `65535`, | |
| inVal: addr(uint16(0)), | |
| want: addr(uint16(65535)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint16/MaxOverflow"), | |
| inBuf: `65536`, | |
| inVal: addr(uint16(1)), | |
| want: addr(uint16(1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`65536`).withType('0', T[uint16]()), | |
| }, { | |
| name: jsontest.Name("Uints/Uint32/Min"), | |
| inBuf: `0`, | |
| inVal: addr(uint32(1)), | |
| want: addr(uint32(0)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint32/Max"), | |
| inBuf: `4294967295`, | |
| inVal: addr(uint32(0)), | |
| want: addr(uint32(4294967295)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint32/MaxOverflow"), | |
| inBuf: `4294967296`, | |
| inVal: addr(uint32(1)), | |
| want: addr(uint32(1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`4294967296`).withType('0', T[uint32]()), | |
| }, { | |
| name: jsontest.Name("Uints/Uint64/Min"), | |
| inBuf: `0`, | |
| inVal: addr(uint64(1)), | |
| want: addr(uint64(0)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint64/Max"), | |
| inBuf: `18446744073709551615`, | |
| inVal: addr(uint64(0)), | |
| want: addr(uint64(18446744073709551615)), | |
| }, { | |
| name: jsontest.Name("Uints/Uint64/MaxOverflow"), | |
| inBuf: `18446744073709551616`, | |
| inVal: addr(uint64(1)), | |
| want: addr(uint64(1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`18446744073709551616`).withType('0', T[uint64]()), | |
| }, { | |
| name: jsontest.Name("Uints/Uintptr"), | |
| inBuf: `1`, | |
| inVal: addr(uintptr(0)), | |
| want: addr(uintptr(1)), | |
| }, { | |
| name: jsontest.Name("Uints/Named"), | |
| inBuf: `6464`, | |
| inVal: addr(namedUint64(0)), | |
| want: addr(namedUint64(6464)), | |
| }, { | |
| name: jsontest.Name("Uints/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"6464"`, | |
| inVal: new(uint), | |
| want: addr(uint(6464)), | |
| }, { | |
| name: jsontest.Name("Uints/Stringified/Invalid"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `6464`, | |
| inVal: new(uint), | |
| want: new(uint), | |
| wantErr: EU(nil).withType('0', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Stringified/LeadingZero"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"00"`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"00"`).withType('"', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Escaped"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"\u0036\u0034\u0036\u0034"`, | |
| inVal: new(uint), | |
| want: addr(uint(6464)), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/NegativeOne"), | |
| inBuf: `-1`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`-1`).withType('0', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/NegativeZero"), | |
| inBuf: `-0`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`-0`).withType('0', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Fraction"), | |
| inBuf: `1.0`, | |
| inVal: addr(uint(10)), | |
| want: addr(uint(10)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`1.0`).withType('0', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Exponent"), | |
| inBuf: `1e0`, | |
| inVal: addr(uint(10)), | |
| want: addr(uint(10)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`1e0`).withType('0', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/StringifiedFraction"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"1.0"`, | |
| inVal: addr(uint(10)), | |
| want: addr(uint(10)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"1.0"`).withType('"', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/StringifiedExponent"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"1e0"`, | |
| inVal: addr(uint(10)), | |
| want: addr(uint(10)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"1e0"`).withType('"', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Overflow"), | |
| inBuf: `100000000000000000000000000000`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(strconv.ErrRange).withVal(`100000000000000000000000000000`).withType('0', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/OverflowSyntax"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"100000000000000000000000000000x"`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"100000000000000000000000000000x"`).withType('"', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Whitespace"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"0 "`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"0 "`).withType('"', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(nil).withType('t', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/String"), | |
| inBuf: `"0"`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(nil).withType('"', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(nil).withType('{', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr(uint(1)), | |
| want: addr(uint(1)), | |
| wantErr: EU(nil).withType('[', T[uint]()), | |
| }, { | |
| name: jsontest.Name("Uints/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `1`, | |
| inVal: addr(uint(0)), | |
| want: addr(uint(1)), | |
| }, { | |
| name: jsontest.Name("Floats/Null"), | |
| inBuf: `null`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(0)), | |
| }, { | |
| name: jsontest.Name("Floats/Float32/Pi"), | |
| inBuf: `3.14159265358979323846264338327950288419716939937510582097494459`, | |
| inVal: addr(float32(32.32)), | |
| want: addr(float32(math.Pi)), | |
| }, { | |
| name: jsontest.Name("Floats/Float32/Underflow"), | |
| inBuf: `1e-1000`, | |
| inVal: addr(float32(32.32)), | |
| want: addr(float32(0)), | |
| }, { | |
| name: jsontest.Name("Floats/Float32/Overflow"), | |
| inBuf: `-1e1000`, | |
| inVal: addr(float32(32.32)), | |
| want: addr(float32(32.32)), | |
| wantErr: EU(strconv.ErrRange).withVal(`-1e1000`).withType('0', T[float32]()), | |
| }, { | |
| name: jsontest.Name("Floats/Float64/Pi"), | |
| inBuf: `3.14159265358979323846264338327950288419716939937510582097494459`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(math.Pi)), | |
| }, { | |
| name: jsontest.Name("Floats/Float64/Underflow"), | |
| inBuf: `1e-1000`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(0)), | |
| }, { | |
| name: jsontest.Name("Floats/Float64/Overflow"), | |
| inBuf: `-1e1000`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(strconv.ErrRange).withVal(`-1e1000`).withType('0', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Floats/Any/Overflow"), | |
| inBuf: `1e1000`, | |
| inVal: new(any), | |
| want: addr(any(float64(0))), | |
| wantErr: EU(strconv.ErrRange).withVal(`1e1000`).withType('0', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Floats/Any/ExistingFloat32/Overflow"), | |
| inBuf: `1e1000`, | |
| inVal: addr(any(float32(32.32))), | |
| want: addr(any(float32(32.32))), | |
| wantErr: EU(strconv.ErrRange).withVal(`1e1000`).withType('0', T[float32]()), | |
| }, { | |
| name: jsontest.Name("Floats/Any/ExistingFloat64/Overflow"), | |
| inBuf: `1e1000`, | |
| inVal: addr(any(float64(64.64))), | |
| want: addr(any(float64(64.64))), | |
| wantErr: EU(strconv.ErrRange).withVal(`1e1000`).withType('0', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Floats/Named"), | |
| inBuf: `64.64`, | |
| inVal: addr(namedFloat64(0)), | |
| want: addr(namedFloat64(64.64)), | |
| }, { | |
| name: jsontest.Name("Floats/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"64.64"`, | |
| inVal: new(float64), | |
| want: addr(float64(64.64)), | |
| }, { | |
| name: jsontest.Name("Floats/Stringified/Invalid"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `64.64`, | |
| inVal: new(float64), | |
| want: new(float64), | |
| wantErr: EU(nil).withType('0', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Floats/Escaped"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"\u0036\u0034\u002e\u0036\u0034"`, | |
| inVal: new(float64), | |
| want: addr(float64(64.64)), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/NaN"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"NaN"`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"NaN"`).withType('"', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/Infinity"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"Infinity"`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"Infinity"`).withType('"', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/Whitespace"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"1 "`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"1 "`).withType('"', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/GoSyntax"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `"1p-2"`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`"1p-2"`).withType('"', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(nil).withType('t', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/String"), | |
| inBuf: `"0"`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(nil).withType('"', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(nil).withType('{', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr(float64(64.64)), | |
| want: addr(float64(64.64)), | |
| wantErr: EU(nil).withType('[', float64Type), | |
| }, { | |
| name: jsontest.Name("Floats/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `1`, | |
| inVal: addr(float64(0)), | |
| want: addr(float64(1)), | |
| }, { | |
| name: jsontest.Name("Maps/Null"), | |
| inBuf: `null`, | |
| inVal: addr(map[string]string{"key": "value"}), | |
| want: new(map[string]string), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Bool"), | |
| inBuf: `{"true":"false"}`, | |
| inVal: new(map[bool]bool), | |
| want: addr(make(map[bool]bool)), | |
| wantErr: EU(nil).withPos(`{`, "/true").withType('"', boolType), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/NamedBool"), | |
| inBuf: `{"true":"false"}`, | |
| inVal: new(map[namedBool]bool), | |
| want: addr(make(map[namedBool]bool)), | |
| wantErr: EU(nil).withPos(`{`, "/true").withType('"', T[namedBool]()), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Array"), | |
| inBuf: `{"key":"value"}`, | |
| inVal: new(map[[1]string]string), | |
| want: addr(make(map[[1]string]string)), | |
| wantErr: EU(nil).withPos(`{`, "/key").withType('"', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidKey/Channel"), | |
| inBuf: `{"key":"value"}`, | |
| inVal: new(map[chan string]string), | |
| want: addr(make(map[chan string]string)), | |
| wantErr: EU(nil).withPos(`{`, "").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Int"), | |
| inBuf: `{"0":0,"-1":1,"2":2,"-3":3}`, | |
| inVal: new(map[int]int), | |
| want: addr(map[int]int{0: 0, -1: 1, 2: 2, -3: 3}), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/NamedInt"), | |
| inBuf: `{"0":0,"-1":1,"2":2,"-3":3}`, | |
| inVal: new(map[namedInt64]int), | |
| want: addr(map[namedInt64]int{0: 0, -1: 1, 2: 2, -3: 3}), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Uint"), | |
| inBuf: `{"0":0,"1":1,"2":2,"3":3}`, | |
| inVal: new(map[uint]uint), | |
| want: addr(map[uint]uint{0: 0, 1: 1, 2: 2, 3: 3}), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/NamedUint"), | |
| inBuf: `{"0":0,"1":1,"2":2,"3":3}`, | |
| inVal: new(map[namedUint64]uint), | |
| want: addr(map[namedUint64]uint{0: 0, 1: 1, 2: 2, 3: 3}), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Float"), | |
| inBuf: `{"1.234":1.234,"12.34":12.34,"123.4":123.4}`, | |
| inVal: new(map[float64]float64), | |
| want: addr(map[float64]float64{1.234: 1.234, 12.34: 12.34, 123.4: 123.4}), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Int"), | |
| inBuf: `{"0":1,"-0":-1}`, | |
| inVal: new(map[int]int), | |
| want: addr(map[int]int{0: 1}), | |
| wantErr: newDuplicateNameError("", []byte(`"-0"`), len64(`{"0":1,`)), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Int/MergeWithLegacySemantics"), | |
| opts: []Options{jsonflags.MergeWithLegacySemantics | 1}, | |
| inBuf: `{"0":1,"-0":-1}`, | |
| inVal: new(map[int]int), | |
| want: addr(map[int]int{0: 1}), | |
| wantErr: newDuplicateNameError("", []byte(`"-0"`), len64(`{"0":1,`)), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Int/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"0":1,"-0":-1}`, | |
| inVal: new(map[int]int), | |
| want: addr(map[int]int{0: -1}), // latter takes precedence | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Int/OverwriteExisting"), | |
| inBuf: `{"-0":-1}`, | |
| inVal: addr(map[int]int{0: 1}), | |
| want: addr(map[int]int{0: -1}), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Float"), | |
| inBuf: `{"1.0":"1.0","1":"1","1e0":"1e0"}`, | |
| inVal: new(map[float64]string), | |
| want: addr(map[float64]string{1: "1.0"}), | |
| wantErr: newDuplicateNameError("", []byte(`"1"`), len64(`{"1.0":"1.0",`)), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Float/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"1.0":"1.0","1":"1","1e0":"1e0"}`, | |
| inVal: new(map[float64]string), | |
| want: addr(map[float64]string{1: "1e0"}), // latter takes precedence | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/Float/OverwriteExisting"), | |
| inBuf: `{"1.0":"1.0"}`, | |
| inVal: addr(map[float64]string{1: "1"}), | |
| want: addr(map[float64]string{1: "1.0"}), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/NoCaseString"), | |
| inBuf: `{"hello":"hello","HELLO":"HELLO"}`, | |
| inVal: new(map[nocaseString]string), | |
| want: addr(map[nocaseString]string{"hello": "hello"}), | |
| wantErr: newDuplicateNameError("", []byte(`"HELLO"`), len64(`{"hello":"hello",`)), | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/NoCaseString/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"hello":"hello","HELLO":"HELLO"}`, | |
| inVal: new(map[nocaseString]string), | |
| want: addr(map[nocaseString]string{"hello": "HELLO"}), // latter takes precedence | |
| }, { | |
| name: jsontest.Name("Maps/DuplicateName/NoCaseString/OverwriteExisting"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"HELLO":"HELLO"}`, | |
| inVal: addr(map[nocaseString]string{"hello": "hello"}), | |
| want: addr(map[nocaseString]string{"hello": "HELLO"}), | |
| }, { | |
| name: jsontest.Name("Maps/ValidKey/Interface"), | |
| inBuf: `{"false":"false","true":"true","string":"string","0":"0","[]":"[]","{}":"{}"}`, | |
| inVal: new(map[any]string), | |
| want: addr(map[any]string{ | |
| "false": "false", | |
| "true": "true", | |
| "string": "string", | |
| "0": "0", | |
| "[]": "[]", | |
| "{}": "{}", | |
| }), | |
| }, { | |
| name: jsontest.Name("Maps/InvalidValue/Channel"), | |
| inBuf: `{"key":"value"}`, | |
| inVal: new(map[string]chan string), | |
| want: addr(map[string]chan string{ | |
| "key": nil, | |
| }), | |
| wantErr: EU(nil).withPos(`{"key":`, "/key").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Maps/RecursiveMap"), | |
| inBuf: `{"buzz":{},"fizz":{"bar":{},"foo":{}}}`, | |
| inVal: new(recursiveMap), | |
| want: addr(recursiveMap{ | |
| "fizz": { | |
| "foo": {}, | |
| "bar": {}, | |
| }, | |
| "buzz": {}, | |
| }), | |
| }, { | |
| // NOTE: The semantics differs from v1, | |
| // where existing map entries were not merged into. | |
| // See https://go.dev/issue/31924. | |
| name: jsontest.Name("Maps/Merge"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"k1":{"k2":"v2"},"k2":{"k1":"v1"},"k2":{"k2":"v2"}}`, | |
| inVal: addr(map[string]map[string]string{ | |
| "k1": {"k1": "v1"}, | |
| }), | |
| want: addr(map[string]map[string]string{ | |
| "k1": {"k1": "v1", "k2": "v2"}, | |
| "k2": {"k1": "v1", "k2": "v2"}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Maps/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr(map[string]string{"key": "value"}), | |
| want: addr(map[string]string{"key": "value"}), | |
| wantErr: EU(nil).withType('t', T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Maps/Invalid/String"), | |
| inBuf: `""`, | |
| inVal: addr(map[string]string{"key": "value"}), | |
| want: addr(map[string]string{"key": "value"}), | |
| wantErr: EU(nil).withType('"', T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Maps/Invalid/Number"), | |
| inBuf: `0`, | |
| inVal: addr(map[string]string{"key": "value"}), | |
| want: addr(map[string]string{"key": "value"}), | |
| wantErr: EU(nil).withType('0', T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Maps/Invalid/Array"), | |
| inBuf: `[]`, | |
| inVal: addr(map[string]string{"key": "value"}), | |
| want: addr(map[string]string{"key": "value"}), | |
| wantErr: EU(nil).withType('[', T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Maps/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `{"hello":"goodbye"}`, | |
| inVal: addr(map[string]string{}), | |
| want: addr(map[string]string{"hello": "goodbye"}), | |
| }, { | |
| name: jsontest.Name("Structs/Null"), | |
| inBuf: `null`, | |
| inVal: addr(structAll{String: "something"}), | |
| want: addr(structAll{}), | |
| }, { | |
| name: jsontest.Name("Structs/Empty"), | |
| inBuf: `{}`, | |
| inVal: addr(structAll{ | |
| String: "hello", | |
| Map: map[string]string{}, | |
| Slice: []string{}, | |
| }), | |
| want: addr(structAll{ | |
| String: "hello", | |
| Map: map[string]string{}, | |
| Slice: []string{}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Normal"), | |
| inBuf: `{ | |
| "Bool": true, | |
| "String": "hello", | |
| "Bytes": "AQID", | |
| "Int": -64, | |
| "Uint": 64, | |
| "Float": 3.14159, | |
| "Map": {"key": "value"}, | |
| "StructScalars": { | |
| "Bool": true, | |
| "String": "hello", | |
| "Bytes": "AQID", | |
| "Int": -64, | |
| "Uint": 64, | |
| "Float": 3.14159 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {"": true}, | |
| "MapString": {"": "hello"}, | |
| "MapBytes": {"": "AQID"}, | |
| "MapInt": {"": -64}, | |
| "MapUint": {"": 64}, | |
| "MapFloat": {"": 3.14159} | |
| }, | |
| "StructSlices": { | |
| "SliceBool": [true], | |
| "SliceString": ["hello"], | |
| "SliceBytes": ["AQID"], | |
| "SliceInt": [-64], | |
| "SliceUint": [64], | |
| "SliceFloat": [3.14159] | |
| }, | |
| "Slice": ["fizz","buzz"], | |
| "Array": ["goodbye"], | |
| "Pointer": {}, | |
| "Interface": null | |
| }`, | |
| inVal: new(structAll), | |
| want: addr(structAll{ | |
| Bool: true, | |
| String: "hello", | |
| Bytes: []byte{1, 2, 3}, | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| Map: map[string]string{"key": "value"}, | |
| StructScalars: structScalars{ | |
| Bool: true, | |
| String: "hello", | |
| Bytes: []byte{1, 2, 3}, | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| }, | |
| StructMaps: structMaps{ | |
| MapBool: map[string]bool{"": true}, | |
| MapString: map[string]string{"": "hello"}, | |
| MapBytes: map[string][]byte{"": {1, 2, 3}}, | |
| MapInt: map[string]int64{"": -64}, | |
| MapUint: map[string]uint64{"": +64}, | |
| MapFloat: map[string]float64{"": 3.14159}, | |
| }, | |
| StructSlices: structSlices{ | |
| SliceBool: []bool{true}, | |
| SliceString: []string{"hello"}, | |
| SliceBytes: [][]byte{{1, 2, 3}}, | |
| SliceInt: []int64{-64}, | |
| SliceUint: []uint64{+64}, | |
| SliceFloat: []float64{3.14159}, | |
| }, | |
| Slice: []string{"fizz", "buzz"}, | |
| Array: [1]string{"goodbye"}, | |
| Pointer: new(structAll), | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Merge"), | |
| inBuf: `{ | |
| "Bool": false, | |
| "String": "goodbye", | |
| "Int": -64, | |
| "Float": 3.14159, | |
| "Map": {"k2": "v2"}, | |
| "StructScalars": { | |
| "Bool": true, | |
| "String": "hello", | |
| "Bytes": "AQID", | |
| "Int": -64 | |
| }, | |
| "StructMaps": { | |
| "MapBool": {"": true}, | |
| "MapString": {"": "hello"}, | |
| "MapBytes": {"": "AQID"}, | |
| "MapInt": {"": -64}, | |
| "MapUint": {"": 64}, | |
| "MapFloat": {"": 3.14159} | |
| }, | |
| "StructSlices": { | |
| "SliceString": ["hello"], | |
| "SliceBytes": ["AQID"], | |
| "SliceInt": [-64], | |
| "SliceUint": [64] | |
| }, | |
| "Slice": ["fizz","buzz"], | |
| "Array": ["goodbye"], | |
| "Pointer": {}, | |
| "Interface": {"k2":"v2"} | |
| }`, | |
| inVal: addr(structAll{ | |
| Bool: true, | |
| String: "hello", | |
| Bytes: []byte{1, 2, 3}, | |
| Uint: +64, | |
| Float: math.NaN(), | |
| Map: map[string]string{"k1": "v1"}, | |
| StructScalars: structScalars{ | |
| String: "hello", | |
| Bytes: make([]byte, 2, 4), | |
| Uint: +64, | |
| Float: 3.14159, | |
| }, | |
| StructMaps: structMaps{ | |
| MapBool: map[string]bool{"": false}, | |
| MapBytes: map[string][]byte{"": {}}, | |
| MapInt: map[string]int64{"": 123}, | |
| MapFloat: map[string]float64{"": math.Inf(+1)}, | |
| }, | |
| StructSlices: structSlices{ | |
| SliceBool: []bool{true}, | |
| SliceBytes: [][]byte{nil, nil}, | |
| SliceInt: []int64{-123}, | |
| SliceUint: []uint64{+123}, | |
| SliceFloat: []float64{3.14159}, | |
| }, | |
| Slice: []string{"buzz", "fizz", "gizz"}, | |
| Array: [1]string{"hello"}, | |
| Pointer: new(structAll), | |
| Interface: map[string]string{"k1": "v1"}, | |
| }), | |
| want: addr(structAll{ | |
| Bool: false, | |
| String: "goodbye", | |
| Bytes: []byte{1, 2, 3}, | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| Map: map[string]string{"k1": "v1", "k2": "v2"}, | |
| StructScalars: structScalars{ | |
| Bool: true, | |
| String: "hello", | |
| Bytes: []byte{1, 2, 3}, | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| }, | |
| StructMaps: structMaps{ | |
| MapBool: map[string]bool{"": true}, | |
| MapString: map[string]string{"": "hello"}, | |
| MapBytes: map[string][]byte{"": {1, 2, 3}}, | |
| MapInt: map[string]int64{"": -64}, | |
| MapUint: map[string]uint64{"": +64}, | |
| MapFloat: map[string]float64{"": 3.14159}, | |
| }, | |
| StructSlices: structSlices{ | |
| SliceBool: []bool{true}, | |
| SliceString: []string{"hello"}, | |
| SliceBytes: [][]byte{{1, 2, 3}}, | |
| SliceInt: []int64{-64}, | |
| SliceUint: []uint64{+64}, | |
| SliceFloat: []float64{3.14159}, | |
| }, | |
| Slice: []string{"fizz", "buzz"}, | |
| Array: [1]string{"goodbye"}, | |
| Pointer: new(structAll), | |
| Interface: map[string]string{"k1": "v1", "k2": "v2"}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified"), | |
| inBuf: `{ | |
| "Int": "-64", | |
| "Uint": "64", | |
| "Float": "3.14159", | |
| "PointerInt": "-64", | |
| "PointerUint": "64", | |
| "PointerFloat": "3.14159" | |
| }`, | |
| inVal: new(structStringified), | |
| want: addr(structStringified{ | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| PointerInt: new(int64(-64)), | |
| PointerUint: new(uint64(+64)), | |
| PointerFloat: new(float64(3.14159)), | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/EmptyIntString"), | |
| inBuf: `{"Int":""}`, | |
| inVal: new(structStringified), | |
| want: new(structStringified), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`""`).withPos(`{"Int":`, "/Int").withType('"', T[int64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Bool"), | |
| inBuf: `{"Bool": "true"}`, | |
| inVal: new(structStringifiedBool), | |
| want: new(structStringifiedBool), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Bool": `, "/Bool").withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Bool"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Bool": true}`, | |
| inVal: new(structStringifiedBool), | |
| want: new(structStringifiedBool{Bool: true}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/String"), | |
| inBuf: `{"String": "\"hello\""}`, | |
| inVal: new(structStringifiedString), | |
| want: new(structStringifiedString), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"String": `, "/String").withType(0, T[string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/String"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"String": "\"hello\""}`, | |
| inVal: new(structStringifiedString), | |
| want: new(structStringifiedString{String: `"hello"`}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Bytes"), | |
| inBuf: `{"Bytes": "AQID"}`, | |
| inVal: new(structStringifiedBytes), | |
| want: new(structStringifiedBytes), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Bytes": `, "/Bytes").withType(0, T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Bytes"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Bytes": "AQID"}`, | |
| inVal: new(structStringifiedBytes), | |
| want: new(structStringifiedBytes{Bytes: []byte{1, 2, 3}}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Map"), | |
| inBuf: `{"Map": {"Key": "Value"}}`, | |
| inVal: new(structStringifiedMap), | |
| want: new(structStringifiedMap), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Map": `, "/Map").withType(0, T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Map"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Map": {"Key": "Value"}}`, | |
| inVal: new(structStringifiedMap), | |
| want: new(structStringifiedMap{Map: map[string]string{"Key": "Value"}}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Slice"), | |
| inBuf: `{"Slice": ["hello"]}`, | |
| inVal: new(structStringifiedSlice), | |
| want: new(structStringifiedSlice), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Slice": `, "/Slice").withType(0, T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Slice"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Slice": ["hello"]}`, | |
| inVal: new(structStringifiedSlice), | |
| want: new(structStringifiedSlice{Slice: []string{"hello"}}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Array"), | |
| inBuf: `{"Array": ["hello"]}`, | |
| inVal: new(structStringifiedArray), | |
| want: new(structStringifiedArray), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Array": `, "/Array").withType(0, T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Array"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Array": ["hello"]}`, | |
| inVal: new(structStringifiedArray), | |
| want: new(structStringifiedArray{Array: [1]string{"hello"}}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Struct"), | |
| inBuf: `{"Struct": {"Bool": true}}`, | |
| inVal: new(structStringifiedStruct), | |
| want: new(structStringifiedStruct), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Struct": `, "/Struct").withType(0, T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Struct"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Struct": {"Bool": true}}`, | |
| inVal: new(structStringifiedStruct), | |
| want: new(structStringifiedStruct{Struct: structAll{Bool: true}}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Pointer"), | |
| inBuf: `{"Pointer": {"Bool": true}}`, | |
| inVal: new(structStringifiedPointer), | |
| want: new(structStringifiedPointer{Pointer: new(structAll)}), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Pointer": `, "/Pointer").withType(0, T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Pointer"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Pointer": {"Bool": true}}`, | |
| inVal: new(structStringifiedPointer), | |
| want: new(structStringifiedPointer{Pointer: new(structAll{Bool: true})}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/PointerPointerInt"), | |
| inBuf: `{"Pointer": "5"}`, | |
| inVal: new(structStringifiedPointerPointerInt), | |
| want: new(structStringifiedPointerPointerInt{Pointer: new(new(5))}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/Interface"), | |
| inBuf: `{"Interface": null}`, | |
| inVal: new(structStringifiedInterface), | |
| want: new(structStringifiedInterface), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Interface": `, "/Interface").withType(0, T[any]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/Interface"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Interface": null}`, | |
| inVal: new(structStringifiedInterface), | |
| want: new(structStringifiedInterface), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{ | |
| "Bool": "true", | |
| "String": "\"hello\"", | |
| "Int": "-64", | |
| "Uint": "64", | |
| "Float": "3.14159", | |
| "PointerBool": "true", | |
| "PointerString": "\"hello\"", | |
| "PointerInt": "-64", | |
| "PointerUint": "64", | |
| "PointerFloat": "3.14159" | |
| }`, | |
| inVal: new(structStringifiedLegacy), | |
| want: addr(structStringifiedLegacy{ | |
| Bool: true, | |
| String: "hello", | |
| Int: -64, | |
| Uint: +64, | |
| Float: 3.14159, | |
| PointerBool: new(true), | |
| PointerString: new("hello"), | |
| PointerInt: new(int64(-64)), | |
| PointerUint: new(uint64(+64)), | |
| PointerFloat: new(float64(3.14159)), | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/EmptyIntString"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Int":""}`, | |
| inVal: new(structStringifiedLegacy), | |
| want: new(structStringifiedLegacy), | |
| wantErr: EU(strconv.ErrSyntax).withVal(`""`).withPos(`{"Int":`, "/Int").withType('"', T[int64]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Bool"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Bool": true}`, | |
| inVal: new(structStringifiedLegacy), | |
| wantErr: EU(nil).withPos(`{"Bool": `, "/Bool").withType('t', T[bool]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/String"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"String": "string"}`, | |
| inVal: new(structStringifiedLegacy), | |
| wantErr: EU(newInvalidCharacterError("s", "at start of string (expecting '\"')", 0, "")). | |
| withPos(`{"String": `, "/String").withType('"', T[string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Bytes"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Bytes": "AQID"}`, | |
| inVal: new(structStringifiedBytes), | |
| want: new(structStringifiedBytes), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Bytes": `, "/Bytes").withType(0, T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Bytes"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Bytes": "AQID"}`, | |
| inVal: new(structStringifiedBytes), | |
| want: new(structStringifiedBytes{Bytes: []byte{1, 2, 3}}), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Map"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Map": {"Key": "Value"}}`, | |
| inVal: new(structStringifiedMap), | |
| want: new(structStringifiedMap), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Map": `, "/Map").withType(0, T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Map"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Map": {"Key": "Value"}}`, | |
| inVal: new(structStringifiedMap), | |
| want: new(structStringifiedMap{Map: map[string]string{"Key": "Value"}}), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Slice"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Slice": ["hello"]}`, | |
| inVal: new(structStringifiedSlice), | |
| want: new(structStringifiedSlice), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Slice": `, "/Slice").withType(0, T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Slice"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Slice": ["hello"]}`, | |
| inVal: new(structStringifiedSlice), | |
| want: new(structStringifiedSlice{Slice: []string{"hello"}}), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Array"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Array": ["hello"]}`, | |
| inVal: new(structStringifiedArray), | |
| want: new(structStringifiedArray), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Array": `, "/Array").withType(0, T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Array"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Array": ["hello"]}`, | |
| inVal: new(structStringifiedArray), | |
| want: new(structStringifiedArray{Array: [1]string{"hello"}}), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Struct"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Struct": {"Bool": true}}`, | |
| inVal: new(structStringifiedStruct), | |
| want: new(structStringifiedStruct), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Struct": `, "/Struct").withType(0, T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Struct"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Struct": {"Bool": true}}`, | |
| inVal: new(structStringifiedStruct), | |
| want: new(structStringifiedStruct{Struct: structAll{Bool: true}}), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Pointer"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Pointer": {"Bool": true}}`, | |
| inVal: new(structStringifiedPointer), | |
| want: new(structStringifiedPointer{Pointer: new(structAll)}), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Pointer": `, "/Pointer").withType(0, T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Pointer"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Pointer": {"Bool": true}}`, | |
| inVal: new(structStringifiedPointer), | |
| want: new(structStringifiedPointer{Pointer: new(structAll{Bool: true})}), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Invalid/PointerPointerInt"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Pointer": 5}`, | |
| inVal: new(structStringifiedPointerPointerInt), | |
| want: new(structStringifiedPointerPointerInt{}), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Pointer": `, "/Pointer").withType(0, T[**int]()), | |
| }, { | |
| name: jsontest.Name("Structs/Stringified/Ignored/PointerPointerInt"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Pointer": 5}`, | |
| inVal: new(structStringifiedPointerPointerInt), | |
| want: new(structStringifiedPointerPointerInt{Pointer: new(new(5))}), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Invalid/Interface"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, | |
| inBuf: `{"Interface": null}`, | |
| inVal: new(structStringifiedInterface), | |
| want: new(structStringifiedInterface), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"Interface": `, "/Interface").withType(0, T[any]()), | |
| }, { | |
| name: jsontest.Name("Structs/LegacyStringified/Ignored/Interface"), | |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1, jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"Interface": null}`, | |
| inVal: new(structStringifiedInterface), | |
| want: new(structStringifiedInterface), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Unsupported"), | |
| inBuf: `{}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(errors.New("Go struct field Base16 has unsupported `format` tag option")).withType('{', T[structFormatBytes]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{ | |
| "Base16": "0123456789abcdef", | |
| "Base32": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", | |
| "Base32Hex": "0123456789ABCDEFGHIJKLMNOPQRSTUV", | |
| "Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", | |
| "Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", | |
| "Array": [1, 2, 3, 4] | |
| }`, | |
| inVal: new(structFormatBytes), | |
| want: addr(structFormatBytes{ | |
| Base16: []byte("\x01\x23\x45\x67\x89\xab\xcd\xef"), | |
| Base32: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), | |
| Base32Hex: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), | |
| Base64: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2ۯ\xc3\x1c\xb3\xd3]\xb7㞻\xf3߿"), | |
| Base64URL: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2ۯ\xc3\x1c\xb3\xd3]\xb7㞻\xf3߿"), | |
| Array: []byte{1, 2, 3, 4}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Format/ArrayBytes"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{ | |
| "Base16": "01020304", | |
| "Base32": "AEBAGBA=", | |
| "Base32Hex": "0410610=", | |
| "Base64": "AQIDBA==", | |
| "Base64URL": "AQIDBA==", | |
| "Array": [1, 2, 3, 4], | |
| "Default": "AQIDBA==" | |
| }`, | |
| inVal: new(structFormatArrayBytes), | |
| want: addr(structFormatArrayBytes{ | |
| Base16: [4]byte{1, 2, 3, 4}, | |
| Base32: [4]byte{1, 2, 3, 4}, | |
| Base32Hex: [4]byte{1, 2, 3, 4}, | |
| Base64: [4]byte{1, 2, 3, 4}, | |
| Base64URL: [4]byte{1, 2, 3, 4}, | |
| Array: [4]byte{1, 2, 3, 4}, | |
| Default: [4]byte{1, 2, 3, 4}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Format/ArrayBytes/Legacy"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsonflags.FormatBytesWithLegacySemantics | 1}, | |
| inBuf: `{ | |
| "Base16": "01020304", | |
| "Base32": "AEBAGBA=", | |
| "Base32Hex": "0410610=", | |
| "Base64": "AQIDBA==", | |
| "Base64URL": "AQIDBA==", | |
| "Array": [1, 2, 3, 4], | |
| "Default": [1, 2, 3, 4] | |
| }`, | |
| inVal: new(structFormatArrayBytes), | |
| want: addr(structFormatArrayBytes{ | |
| Base16: [4]byte{1, 2, 3, 4}, | |
| Base32: [4]byte{1, 2, 3, 4}, | |
| Base32Hex: [4]byte{1, 2, 3, 4}, | |
| Base64: [4]byte{1, 2, 3, 4}, | |
| Base64URL: [4]byte{1, 2, 3, 4}, | |
| Array: [4]byte{1, 2, 3, 4}, | |
| Default: [4]byte{1, 2, 3, 4}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Array"), | |
| opts: []Options{ | |
| jsonopts.ExperimentalSupportFormatTag(true), | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *byte) error { | |
| if string(b) == "true" { | |
| *v = 1 | |
| } else { | |
| *v = 0 | |
| } | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"Array":[false,true,false,true,false,true]}`, | |
| inVal: new(struct { | |
| Array []byte `json:",format:array"` | |
| }), | |
| want: addr(struct { | |
| Array []byte `json:",format:array"` | |
| }{ | |
| Array: []byte{0, 1, 0, 1, 0, 1}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/WrongKind"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": [1,2,3,4]}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(nil).withPos(`{"Base16": `, "/Base16").withType('[', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/AllPadding"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": "===="}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := hex.Decode(make([]byte, 2), []byte("=====")) | |
| return err | |
| }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/EvenPadding"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": "0123456789abcdef="}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := hex.Decode(make([]byte, 8), []byte("0123456789abcdef=")) | |
| return err | |
| }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/OddPadding"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": "0123456789abcdef0="}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := hex.Decode(make([]byte, 9), []byte("0123456789abcdef0=")) | |
| return err | |
| }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/LineFeed"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": "aa\naa"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := hex.Decode(make([]byte, 9), []byte("aa\naa")) | |
| return err | |
| }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/CarriageReturn"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": "aa\raa"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := hex.Decode(make([]byte, 9), []byte("aa\raa")) | |
| return err | |
| }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/Space"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base16": "aa aa"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := hex.Decode(make([]byte, 9), []byte("aa aa")) | |
| return err | |
| }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/Padding"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `[ | |
| {"Base32": "NA======"}, | |
| {"Base32": "NBSQ===="}, | |
| {"Base32": "NBSWY==="}, | |
| {"Base32": "NBSWY3A="}, | |
| {"Base32": "NBSWY3DP"} | |
| ]`, | |
| inVal: new([]structFormatBytes), | |
| want: addr([]structFormatBytes{ | |
| {Base32: []byte("h")}, | |
| {Base32: []byte("he")}, | |
| {Base32: []byte("hel")}, | |
| {Base32: []byte("hell")}, | |
| {Base32: []byte("hello")}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/Invalid/NoPadding"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `[ | |
| {"Base32": "NA"}, | |
| {"Base32": "NBSQ"}, | |
| {"Base32": "NBSWY"}, | |
| {"Base32": "NBSWY3A"}, | |
| {"Base32": "NBSWY3DP"} | |
| ]`, | |
| inVal: new([]structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := base32.StdEncoding.Decode(make([]byte, 1), []byte("NA")) | |
| return err | |
| }()).withPos(`[`+"\n\t\t\t\t"+`{"Base32": `, "/0/Base32").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/WrongAlphabet"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base32": "0123456789ABCDEFGHIJKLMNOPQRSTUV"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := base32.StdEncoding.Decode(make([]byte, 20), []byte("0123456789ABCDEFGHIJKLMNOPQRSTUV")) | |
| return err | |
| }()).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32Hex/WrongAlphabet"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base32Hex": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := base32.HexEncoding.Decode(make([]byte, 20), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")) | |
| return err | |
| }()).withPos(`{"Base32Hex": `, "/Base32Hex").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/NonAlphabet/LineFeed"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base32": "AAAA\nAAAA"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(errors.New("illegal character '\\n' at offset 4")).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/NonAlphabet/CarriageReturn"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base32": "AAAA\rAAAA"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(errors.New("illegal character '\\r' at offset 4")).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/NonAlphabet/Space"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base32": "AAAA AAAA"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(base32.CorruptInputError(4)).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/WrongAlphabet"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := base64.StdEncoding.Decode(make([]byte, 48), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")) | |
| return err | |
| }()).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64URL/WrongAlphabet"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(func() error { | |
| _, err := base64.URLEncoding.Decode(make([]byte, 48), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")) | |
| return err | |
| }()).withPos(`{"Base64URL": `, "/Base64URL").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/NonAlphabet/LineFeed"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base64": "aa=\n="}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(errors.New("illegal character '\\n' at offset 3")).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/NonAlphabet/CarriageReturn"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base64": "aa=\r="}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(errors.New("illegal character '\\r' at offset 3")).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Base64/NonAlphabet/Ignored"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true), jsonflags.ParseBytesWithLooseRFC4648 | 1}, | |
| inBuf: `{"Base64": "aa=\r\n="}`, | |
| inVal: new(structFormatBytes), | |
| want: &structFormatBytes{Base64: []byte{105}}, | |
| }, { | |
| name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/NonAlphabet/Space"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Base64": "aa= ="}`, | |
| inVal: new(structFormatBytes), | |
| wantErr: EU(base64.CorruptInputError(2)).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Floats"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `[ | |
| {"NonFinite": 3.141592653589793, "PointerNonFinite": 3.141592653589793}, | |
| {"NonFinite": "-Infinity", "PointerNonFinite": "-Infinity"}, | |
| {"NonFinite": "Infinity", "PointerNonFinite": "Infinity"} | |
| ]`, | |
| inVal: new([]structFormatFloats), | |
| want: addr([]structFormatFloats{ | |
| {NonFinite: math.Pi, PointerNonFinite: addr(math.Pi)}, | |
| {NonFinite: math.Inf(-1), PointerNonFinite: addr(math.Inf(-1))}, | |
| {NonFinite: math.Inf(+1), PointerNonFinite: addr(math.Inf(+1))}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Floats/NaN"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"NonFinite": "NaN"}`, | |
| inVal: new(structFormatFloats), | |
| // Avoid checking want since reflect.DeepEqual fails for NaNs. | |
| }, { | |
| name: jsontest.Name("Structs/Format/Floats/Invalid/NaN"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"NonFinite": "nan"}`, | |
| inVal: new(structFormatFloats), | |
| wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Floats/Invalid/PositiveInfinity"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"NonFinite": "+Infinity"}`, | |
| inVal: new(structFormatFloats), | |
| wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Floats/Invalid/NegativeInfinitySpace"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"NonFinite": "-Infinity "}`, | |
| inVal: new(structFormatFloats), | |
| wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Maps"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `[ | |
| {"EmitNull": null, "PointerEmitNull": null, "EmitEmpty": null, "PointerEmitEmpty": null, "EmitDefault": null, "PointerEmitDefault": null}, | |
| {"EmitNull": {}, "PointerEmitNull": {}, "EmitEmpty": {}, "PointerEmitEmpty": {}, "EmitDefault": {}, "PointerEmitDefault": {}}, | |
| {"EmitNull": {"k": "v"}, "PointerEmitNull": {"k": "v"}, "EmitEmpty": {"k": "v"}, "PointerEmitEmpty": {"k": "v"}, "EmitDefault": {"k": "v"}, "PointerEmitDefault": {"k": "v"}} | |
| ]`, | |
| inVal: new([]structFormatMaps), | |
| want: addr([]structFormatMaps{{ | |
| EmitNull: map[string]string(nil), PointerEmitNull: (*map[string]string)(nil), | |
| EmitEmpty: map[string]string(nil), PointerEmitEmpty: (*map[string]string)(nil), | |
| EmitDefault: map[string]string(nil), PointerEmitDefault: (*map[string]string)(nil), | |
| }, { | |
| EmitNull: map[string]string{}, PointerEmitNull: addr(map[string]string{}), | |
| EmitEmpty: map[string]string{}, PointerEmitEmpty: addr(map[string]string{}), | |
| EmitDefault: map[string]string{}, PointerEmitDefault: addr(map[string]string{}), | |
| }, { | |
| EmitNull: map[string]string{"k": "v"}, PointerEmitNull: addr(map[string]string{"k": "v"}), | |
| EmitEmpty: map[string]string{"k": "v"}, PointerEmitEmpty: addr(map[string]string{"k": "v"}), | |
| EmitDefault: map[string]string{"k": "v"}, PointerEmitDefault: addr(map[string]string{"k": "v"}), | |
| }}), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Slices"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `[ | |
| {"EmitNull": null, "PointerEmitNull": null, "EmitEmpty": null, "PointerEmitEmpty": null, "EmitDefault": null, "PointerEmitDefault": null}, | |
| {"EmitNull": [], "PointerEmitNull": [], "EmitEmpty": [], "PointerEmitEmpty": [], "EmitDefault": [], "PointerEmitDefault": []}, | |
| {"EmitNull": ["v"], "PointerEmitNull": ["v"], "EmitEmpty": ["v"], "PointerEmitEmpty": ["v"], "EmitDefault": ["v"], "PointerEmitDefault": ["v"]} | |
| ]`, | |
| inVal: new([]structFormatSlices), | |
| want: addr([]structFormatSlices{{ | |
| EmitNull: []string(nil), PointerEmitNull: (*[]string)(nil), | |
| EmitEmpty: []string(nil), PointerEmitEmpty: (*[]string)(nil), | |
| EmitDefault: []string(nil), PointerEmitDefault: (*[]string)(nil), | |
| }, { | |
| EmitNull: []string{}, PointerEmitNull: addr([]string{}), | |
| EmitEmpty: []string{}, PointerEmitEmpty: addr([]string{}), | |
| EmitDefault: []string{}, PointerEmitDefault: addr([]string{}), | |
| }, { | |
| EmitNull: []string{"v"}, PointerEmitNull: addr([]string{"v"}), | |
| EmitEmpty: []string{"v"}, PointerEmitEmpty: addr([]string{"v"}), | |
| EmitDefault: []string{"v"}, PointerEmitDefault: addr([]string{"v"}), | |
| }}), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Bool"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Bool":true}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, T[bool]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/String"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"String": "string"}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"String": `, "/String").withType(0, T[string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Bytes"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Bytes": "bytes"}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Bytes": `, "/Bytes").withType(0, T[[]byte]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Int"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Int": 1}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Int": `, "/Int").withType(0, T[int64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Uint"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Uint": 1}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Uint": `, "/Uint").withType(0, T[uint64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Float"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Float" : 1}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Float" : `, "/Float").withType(0, T[float64]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Map"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Map":{}}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Map":`, "/Map").withType(0, T[map[string]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Struct"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Struct": {}}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Struct": `, "/Struct").withType(0, T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Slice"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Slice": {}}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Slice": `, "/Slice").withType(0, T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Array"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Array": []}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Array": `, "/Array").withType(0, T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Structs/Format/Invalid/Interface"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"Interface": "anything"}`, | |
| inVal: new(structFormatInvalid), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Interface": `, "/Interface").withType(0, T[any]()), | |
| }, { | |
| name: jsontest.Name("Structs/Embed/Zero"), | |
| inBuf: `{"D":""}`, | |
| inVal: new(structEmbedded), | |
| want: new(structEmbedded), | |
| }, { | |
| name: jsontest.Name("Structs/Embed/Alloc"), | |
| inBuf: `{"E":"","F":"","G":"","A":"","B":"","D":""}`, | |
| inVal: new(structEmbedded), | |
| want: addr(structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{}, | |
| StructEmbed1: StructEmbed1{}, | |
| }, | |
| StructEmbed2: &StructEmbed2{}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Embed/NonZero"), | |
| inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, | |
| inVal: new(structEmbedded), | |
| want: addr(structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{A: "A1", B: "B1" /* C: "C1" */}, | |
| StructEmbed1: StructEmbed1{ /* C: "C2" */ D: "D2" /* E: "E2" */}, | |
| }, | |
| StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/Embed/Merge"), | |
| inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, | |
| inVal: addr(structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{B: "##", C: "C1"}, | |
| StructEmbed1: StructEmbed1{C: "C2", E: "E2"}, | |
| }, | |
| StructEmbed2: &StructEmbed2{E: "##", G: "G3"}, | |
| }), | |
| want: addr(structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{A: "A1", B: "B1", C: "C1"}, | |
| StructEmbed1: StructEmbed1{C: "C2", D: "D2", E: "E2"}, | |
| }, | |
| StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Noop"), | |
| inBuf: `{"A":1,"B":2}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(nil), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeN1/Nil"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeN1/Empty"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedTextValue{X: jsontext.Value{}}), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeN1/Whitespace"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedTextValue{X: jsontext.Value("\n\r\t ")}), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value("")}), | |
| wantErr: EU(errRawEmbedNotObject).withPos(`{"A":1,`, "/fizz").withType('"', T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeN1/Null"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedTextValue{X: jsontext.Value("null")}), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value("null")}), | |
| wantErr: EU(errRawEmbedNotObject).withPos(`{"A":1,`, "/fizz").withType('"', T[jsontext.Value]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeN1/ObjectN0"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedTextValue{X: jsontext.Value(` { } `)}), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(` {"fizz":"buzz"}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeN2/ObjectN1"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, | |
| inVal: addr(structEmbedTextValue{X: jsontext.Value(` { "fizz" : "buzz" } `)}), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(` { "fizz" : "buzz","fizz":"buzz","foo":[ 1 , 2 , 3 ]}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Merge/EndObject"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedTextValue{X: jsontext.Value(` } `)}), | |
| // NOTE: This produces invalid output, | |
| // but the value being merged into is already invalid. | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`,"fizz":"buzz"}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/MergeInvalidValue"), | |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`{"fizz":`)}), | |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/CaseSensitive"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz","a":3}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/RejectDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), | |
| wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz","fizz":"buzz"}`), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Nested/Noop"), | |
| inBuf: `{}`, | |
| inVal: new(structEmbedPointerEmbedTextValue), | |
| want: new(structEmbedPointerEmbedTextValue), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Nested/Alloc"), | |
| inBuf: `{"A":1,"fizz":"buzz"}`, | |
| inVal: new(structEmbedPointerEmbedTextValue), | |
| want: addr(structEmbedPointerEmbedTextValue{ | |
| X: &struct { | |
| A int | |
| X jsontext.Value `json:",embed"` | |
| }{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`)}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/TextValue/Nested/Merge"), | |
| inBuf: `{"fizz":"buzz"}`, | |
| inVal: addr(structEmbedPointerEmbedTextValue{ | |
| X: &struct { | |
| A int | |
| X jsontext.Value `json:",embed"` | |
| }{A: 1}, | |
| }), | |
| want: addr(structEmbedPointerEmbedTextValue{ | |
| X: &struct { | |
| A int | |
| X jsontext.Value `json:",embed"` | |
| }{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`)}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/Noop"), | |
| inBuf: `{"A":1,"B":2}`, | |
| inVal: new(structEmbedPointerTextValue), | |
| want: addr(structEmbedPointerTextValue{A: 1, X: nil, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/Alloc"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: new(structEmbedPointerTextValue), | |
| want: addr(structEmbedPointerTextValue{A: 1, X: addr(jsontext.Value(`{"fizz":"buzz"}`)), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/Merge"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedPointerTextValue{X: addr(jsontext.Value(`{"fizz":"buzz"}`))}), | |
| want: addr(structEmbedPointerTextValue{A: 1, X: addr(jsontext.Value(`{"fizz":"buzz","fizz":"buzz"}`)), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerTextValue/Nested/Nil"), | |
| inBuf: `{"fizz":"buzz"}`, | |
| inVal: new(structEmbedEmbedPointerTextValue), | |
| want: addr(structEmbedEmbedPointerTextValue{ | |
| X: struct { | |
| X *jsontext.Value `json:",embed"` | |
| }{X: addr(jsontext.Value(`{"fizz":"buzz"}`))}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Noop"), | |
| inBuf: `{"A":1,"B":2}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{A: 1, X: nil, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MergeN1/Nil"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MergeN1/Empty"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedMapStringAny{X: jsonObject{}}), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MergeN1/ObjectN1"), | |
| inBuf: `{"A":1,"fizz":{"charlie":"DELTA","echo":"foxtrot"},"B":2}`, | |
| inVal: addr(structEmbedMapStringAny{X: jsonObject{"fizz": jsonObject{ | |
| "alpha": "bravo", | |
| "charlie": "delta", | |
| }}}), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": jsonObject{ | |
| "alpha": "bravo", | |
| "charlie": "DELTA", | |
| "echo": "foxtrot", | |
| }}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MergeN2/ObjectN1"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, | |
| inVal: addr(structEmbedMapStringAny{X: jsonObject{"fizz": "wuzz"}}), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": "buzz", "foo": jsonArray{1.0, 2.0, 3.0}}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MergeInvalidValue"), | |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": nil}}), | |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/MergeInvalidValue/Existing"), | |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, | |
| inVal: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": true}}), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": true}}), | |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/CaseSensitive"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": "buzz", "a": 3.0}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/RejectDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), | |
| wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"A":1,"fizz":{"one":1,"two":-2},"B":2,"fizz":{"two":2,"three":3}}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{A: 1, X: jsonObject{"fizz": jsonObject{"one": 1.0, "two": 2.0, "three": 3.0}}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Nested/Noop"), | |
| inBuf: `{}`, | |
| inVal: new(structEmbedPointerEmbedMapStringAny), | |
| want: new(structEmbedPointerEmbedMapStringAny), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Nested/Alloc"), | |
| inBuf: `{"A":1,"fizz":"buzz"}`, | |
| inVal: new(structEmbedPointerEmbedMapStringAny), | |
| want: addr(structEmbedPointerEmbedMapStringAny{ | |
| X: &struct { | |
| A int | |
| X jsonObject `json:",embed"` | |
| }{A: 1, X: jsonObject{"fizz": "buzz"}}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringAny/Nested/Merge"), | |
| inBuf: `{"fizz":"buzz"}`, | |
| inVal: addr(structEmbedPointerEmbedMapStringAny{ | |
| X: &struct { | |
| A int | |
| X jsonObject `json:",embed"` | |
| }{A: 1}, | |
| }), | |
| want: addr(structEmbedPointerEmbedMapStringAny{ | |
| X: &struct { | |
| A int | |
| X jsonObject `json:",embed"` | |
| }{A: 1, X: jsonObject{"fizz": "buzz"}}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/UnmarshalFunc"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *any) error { | |
| var err error | |
| *v, err = strconv.ParseFloat(string(bytes.Trim(b, `"`)), 64) | |
| return err | |
| })), | |
| }, | |
| inBuf: `{"D":"1.1","E":"2.2","F":"3.3"}`, | |
| inVal: new(structEmbedMapStringAny), | |
| want: addr(structEmbedMapStringAny{X: jsonObject{"D": 1.1, "E": 2.2, "F": 3.3}}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/Noop"), | |
| inBuf: `{"A":1,"B":2}`, | |
| inVal: new(structEmbedPointerMapStringAny), | |
| want: addr(structEmbedPointerMapStringAny{A: 1, X: nil, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/Alloc"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: new(structEmbedPointerMapStringAny), | |
| want: addr(structEmbedPointerMapStringAny{A: 1, X: addr(jsonObject{"fizz": "buzz"}), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/Merge"), | |
| inBuf: `{"A":1,"fizz":"wuzz","B":2}`, | |
| inVal: addr(structEmbedPointerMapStringAny{X: addr(jsonObject{"fizz": "buzz"})}), | |
| want: addr(structEmbedPointerMapStringAny{A: 1, X: addr(jsonObject{"fizz": "wuzz"}), B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/PointerMapStringAny/Nested/Nil"), | |
| inBuf: `{"fizz":"buzz"}`, | |
| inVal: new(structEmbedEmbedPointerMapStringAny), | |
| want: addr(structEmbedEmbedPointerMapStringAny{ | |
| X: struct { | |
| X *jsonObject `json:",embed"` | |
| }{X: addr(jsonObject{"fizz": "buzz"})}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt"), | |
| inBuf: `{"zero": 0, "one": 1, "two": 2}`, | |
| inVal: new(structEmbedMapStringInt), | |
| want: addr(structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/Null"), | |
| inBuf: `{"zero": 0, "one": null, "two": 2}`, | |
| inVal: new(structEmbedMapStringInt), | |
| want: addr(structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 0, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/Invalid"), | |
| inBuf: `{"zero": 0, "one": {}, "two": 2}`, | |
| inVal: new(structEmbedMapStringInt), | |
| want: addr(structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 0}, | |
| }), | |
| wantErr: EU(nil).withPos(`{"zero": 0, "one": `, "/one").withType('{', T[int]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/StringifiedNumbers"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `{"zero": "0", "one": "1", "two": "2"}`, | |
| inVal: new(structEmbedMapStringInt), | |
| want: addr(structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapStringInt/UnmarshalFunc"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *int) error { | |
| i, err := strconv.ParseInt(string(bytes.Trim(b, `"`)), 10, 64) | |
| if err != nil { | |
| return err | |
| } | |
| *v = int(i) | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"zero": "0", "one": "1", "two": "2"}`, | |
| inVal: new(structEmbedMapStringInt), | |
| want: addr(structEmbedMapStringInt{ | |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt"), | |
| inBuf: `{"zero": 0, "one": 1, "two": 2}`, | |
| inVal: new(structEmbedMapNamedStringInt), | |
| want: addr(structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt/Null"), | |
| inBuf: `{"zero": 0, "one": null, "two": 2}`, | |
| inVal: new(structEmbedMapNamedStringInt), | |
| want: addr(structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 0, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt/Invalid"), | |
| inBuf: `{"zero": 0, "one": {}, "two": 2}`, | |
| inVal: new(structEmbedMapNamedStringInt), | |
| want: addr(structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 0}, | |
| }), | |
| wantErr: EU(nil).withPos(`{"zero": 0, "one": `, "/one").withType('{', T[int]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt/StringifiedNumbers"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `{"zero": "0", "one": 1, "two": "2"}`, | |
| inVal: new(structEmbedMapNamedStringInt), | |
| want: addr(structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 0}, | |
| }), | |
| wantErr: EU(nil).withPos(`{"zero": "0", "one": `, "/one").withType('0', T[int]()), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringInt/UnmarshalFunc"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *int) error { | |
| i, err := strconv.ParseInt(string(bytes.Trim(b, `"`)), 10, 64) | |
| if err != nil { | |
| return err | |
| } | |
| *v = int(i) | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"zero": "0", "one": "1", "two": "2"}`, | |
| inVal: new(structEmbedMapNamedStringInt), | |
| want: addr(structEmbedMapNamedStringInt{ | |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/Noop"), | |
| inBuf: `{"A":1,"B":2}`, | |
| inVal: new(structEmbedMapNamedStringAny), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: nil, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MergeN1/Nil"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: new(structEmbedMapNamedStringAny), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MergeN1/Empty"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: addr(structEmbedMapNamedStringAny{X: map[namedString]any{}}), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MergeN1/ObjectN1"), | |
| inBuf: `{"A":1,"fizz":{"charlie":"DELTA","echo":"foxtrot"},"B":2}`, | |
| inVal: addr(structEmbedMapNamedStringAny{X: map[namedString]any{"fizz": jsonObject{ | |
| "alpha": "bravo", | |
| "charlie": "delta", | |
| }}}), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": jsonObject{ | |
| "alpha": "bravo", | |
| "charlie": "DELTA", | |
| "echo": "foxtrot", | |
| }}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MergeN2/ObjectN1"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, | |
| inVal: addr(structEmbedMapNamedStringAny{X: map[namedString]any{"fizz": "wuzz"}}), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz", "foo": jsonArray{1.0, 2.0, 3.0}}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MergeInvalidValue"), | |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, | |
| inVal: new(structEmbedMapNamedStringAny), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": nil}}), | |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/MergeInvalidValue/Existing"), | |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, | |
| inVal: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": true}}), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": true}}), | |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/CaseSensitive"), | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, | |
| inVal: new(structEmbedMapNamedStringAny), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz", "a": 3.0}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/RejectDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, | |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, | |
| inVal: new(structEmbedMapNamedStringAny), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), | |
| wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/MapNamedStringAny/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"A":1,"fizz":{"one":1,"two":-2},"B":2,"fizz":{"two":2,"three":3}}`, | |
| inVal: new(structEmbedMapNamedStringAny), | |
| want: addr(structEmbedMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": map[string]any{"one": 1.0, "two": 2.0, "three": 3.0}}, B: 2}), | |
| }, { | |
| name: jsontest.Name("Structs/EmbeddedFallback/RejectUnknownMembers"), | |
| opts: []Options{RejectUnknownMembers(true)}, | |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, | |
| inVal: new(structEmbedTextValue), | |
| want: addr(structEmbedTextValue{ | |
| A: 1, | |
| X: jsontext.Value(`{"fizz":"buzz"}`), | |
| B: 2, | |
| }), | |
| }, { | |
| name: jsontest.Name("Structs/UnknownIgnored"), | |
| opts: []Options{RejectUnknownMembers(false)}, | |
| inBuf: `{"unknown":"fizzbuzz"}`, | |
| inVal: new(structAll), | |
| want: new(structAll), | |
| }, { | |
| name: jsontest.Name("Structs/RejectUnknownMembers"), | |
| opts: []Options{RejectUnknownMembers(true)}, | |
| inBuf: `{"unknown":"fizzbuzz"}`, | |
| inVal: new(structAll), | |
| want: new(structAll), | |
| wantErr: EU(ErrUnknownName).withPos(`{`, "/unknown").withType('"', T[structAll]()), | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedIgnored"), | |
| inBuf: `{"ignored":"unused"}`, | |
| inVal: new(structUnexportedIgnored), | |
| want: new(structUnexportedIgnored), | |
| }, { | |
| name: jsontest.Name("Structs/IgnoredUnexportedEmbedded"), | |
| inBuf: `{"namedString":"unused"}`, | |
| inVal: new(structIgnoredUnexportedEmbedded), | |
| want: new(structIgnoredUnexportedEmbedded), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/Exact"), | |
| inBuf: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/CaseInsensitiveDefault/Ambiguous"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"aa_a":"aa_a"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{AaA: "aa_a"}), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/CaseInsensitiveDefault/Ambiguous"), | |
| inBuf: `{"aa_a":"aa_a"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{}), | |
| wantErr: EU(errAmbiguousName).withPos(`{`, "/aa_a").withType('"', T[structNoCase]()), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/MatchCaseSensitiveDelimiter"), | |
| opts: []Options{jsonflags.MatchCaseSensitiveDelimiter | 1}, | |
| inBuf: `{"aa_a":"aa_a"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{}), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), | |
| opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, | |
| inBuf: `{"aa_a":"aa_a"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{AA_A: "aa_a"}), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/Merge/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true), jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"AaA":"AaA","aaa":"aaa","aAa":"aAa"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{AaA: "aAa"}), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/Merge/AllowDuplicateNames/Ambiguous"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"AaA":"AaA","aaa":"aaa","aAa":"aAa"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{AaA: "AaA"}), | |
| wantErr: EU(errAmbiguousName).withPos(`{"AaA":"AaA",`, "/aaa").withType('"', T[structNoCase]()), | |
| }, { | |
| name: jsontest.Name("Structs/NoCase/Merge/RejectDuplicateNames/Ambiguous"), | |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, | |
| inBuf: `{"AaA":"AaA","aaa":"aaa"}`, | |
| inVal: new(structNoCase), | |
| want: addr(structNoCase{AaA: "AaA"}), | |
| wantErr: EU(errAmbiguousName).withPos(`{"AaA":"AaA",`, "/aaa").withType('"', T[structNoCase]()), | |
| }, { | |
| name: jsontest.Name("Structs/CaseSensitive"), | |
| inBuf: `{"BOOL": true, "STRING": "hello", "BYTES": "AQID", "INT": -64, "UINT": 64, "FLOAT": 3.14159}`, | |
| inVal: new(structScalars), | |
| want: addr(structScalars{}), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCase/ExactDifferent"), | |
| inBuf: `{"AAA":"AAA","AaA":"AaA","AAa":"AAa","Aaa":"Aaa"}`, | |
| inVal: addr(structNoCaseEmbedTextValue{}), | |
| want: addr(structNoCaseEmbedTextValue{AAA: "AAA", AaA: "AaA", AAa: "AAa", Aaa: "Aaa"}), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCase/ExactConflict"), | |
| inBuf: `{"AAA":"AAA","AAA":"AAA"}`, | |
| inVal: addr(structNoCaseEmbedTextValue{}), | |
| want: addr(structNoCaseEmbedTextValue{AAA: "AAA"}), | |
| wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"AAA",`)), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/NoCase/OverwriteExact"), | |
| inBuf: `{"AAA":"after"}`, | |
| inVal: addr(structNoCaseEmbedTextValue{AAA: "before"}), | |
| want: addr(structNoCaseEmbedTextValue{AAA: "after"}), | |
| }, { | |
| name: jsontest.Name("Structs/AmbiguousName/NoCase"), | |
| inBuf: `{"aaa":"aaa"}`, | |
| inVal: addr(structNoCaseEmbedTextValue{}), | |
| want: addr(structNoCaseEmbedTextValue{}), | |
| wantErr: EU(errAmbiguousName).withPos(`{`, "/aaa").withType('"', T[structNoCaseEmbedTextValue]()), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/Embed/Unknown"), | |
| inBuf: `{"unknown":""}`, | |
| inVal: addr(structNoCaseEmbedTextValue{}), | |
| want: addr(structNoCaseEmbedTextValue{X: jsontext.Value(`{"unknown":""}`)}), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/Embed/UnknownMerge"), | |
| inBuf: `{"unknown":""}`, | |
| inVal: addr(structNoCaseEmbedTextValue{X: jsontext.Value(`{"unknown":""}`)}), | |
| want: addr(structNoCaseEmbedTextValue{X: jsontext.Value(`{"unknown":"","unknown":""}`)}), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/Embed/NoCaseOkay"), | |
| inBuf: `{"b":"","B":""}`, | |
| inVal: addr(structNoCaseEmbedTextValue{}), | |
| want: addr(structNoCaseEmbedTextValue{X: jsontext.Value(`{"b":"","B":""}`)}), | |
| }, { | |
| name: jsontest.Name("Structs/DuplicateName/Embed/ExactConflict"), | |
| inBuf: `{"b":"","b":""}`, | |
| inVal: addr(structNoCaseEmbedTextValue{}), | |
| want: addr(structNoCaseEmbedTextValue{X: jsontext.Value(`{"b":""}`)}), | |
| wantErr: newDuplicateNameError("", []byte(`"b"`), len64(`{"b":"",`)), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/ErrUnexpectedEOF"), | |
| inBuf: ``, | |
| inVal: addr(structAll{}), | |
| want: addr(structAll{}), | |
| wantErr: &jsontext.SyntacticError{Err: io.ErrUnexpectedEOF}, | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/ErrUnexpectedEOF"), | |
| inBuf: " \n\r\t", | |
| inVal: addr(structAll{}), | |
| want: addr(structAll{}), | |
| wantErr: &jsontext.SyntacticError{Err: io.ErrUnexpectedEOF, ByteOffset: len64(" \n\r\t")}, | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/NestedErrUnexpectedEOF"), | |
| inBuf: `{"Pointer":`, | |
| inVal: addr(structAll{}), | |
| want: addr(structAll{Pointer: new(structAll)}), | |
| wantErr: &jsontext.SyntacticError{ByteOffset: len64(`{"Pointer":`), JSONPointer: "/Pointer", Err: io.ErrUnexpectedEOF}, | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/Conflicting"), | |
| inBuf: `{}`, | |
| inVal: addr(structConflicting{}), | |
| want: addr(structConflicting{}), | |
| wantErr: EU(errors.New(`Go struct fields A and B conflict over JSON object name "conflict"`)).withType('{', T[structConflicting]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/NoneExported"), | |
| inBuf: ` {}`, | |
| inVal: addr(structNoneExported{}), | |
| want: addr(structNoneExported{}), | |
| wantErr: EU(errNoExportedFields).withPos(` `, "").withType('{', T[structNoneExported]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/MalformedTag"), | |
| inBuf: `{}`, | |
| inVal: addr(structMalformedTag{}), | |
| want: addr(structMalformedTag{}), | |
| wantErr: EU(errors.New("Go struct field Malformed has malformed `json` tag: invalid character '\"' at start of option (expecting Unicode letter)")).withType('{', T[structMalformedTag]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/UnexportedTag"), | |
| inBuf: `{}`, | |
| inVal: addr(structUnexportedTag{}), | |
| want: addr(structUnexportedTag{}), | |
| wantErr: EU(errors.New("unexported Go struct field unexported cannot have non-ignored `json:\"name\"` tag")).withType('{', T[structUnexportedTag]()), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/ExportedEmbedded"), | |
| inBuf: `{"NamedString":"hello"}`, | |
| inVal: addr(structExportedEmbedded{}), | |
| want: addr(structExportedEmbedded{}), | |
| wantErr: EU(errors.New("embedded Go struct field NamedString of non-struct type must be explicitly given a JSON name")).withType('{', T[structExportedEmbedded]()), | |
| }, { | |
| name: jsontest.Name("Structs/Valid/ExportedEmbedded"), | |
| opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, | |
| inBuf: `{"NamedString":"hello"}`, | |
| inVal: addr(structExportedEmbedded{}), | |
| want: addr(structExportedEmbedded{"hello"}), | |
| }, { | |
| name: jsontest.Name("Structs/Valid/ExportedEmbeddedTag"), | |
| inBuf: `{"name":"hello"}`, | |
| inVal: addr(structExportedEmbeddedTag{}), | |
| want: addr(structExportedEmbeddedTag{"hello"}), | |
| }, { | |
| name: jsontest.Name("Structs/Invalid/UnexportedEmbedded"), | |
| inBuf: `{}`, | |
| inVal: addr(structUnexportedEmbedded{}), | |
| want: addr(structUnexportedEmbedded{}), | |
| wantErr: EU(errors.New("embedded Go struct field namedString of non-struct type must be explicitly given a JSON name")).withType('{', T[structUnexportedEmbedded]()), | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStruct"), | |
| inBuf: `{"Bool":true,"FizzBuzz":5,"Addr":"192.168.0.1"}`, | |
| inVal: addr(structUnexportedEmbeddedStruct{}), | |
| want: addr(structUnexportedEmbeddedStruct{structOmitZeroAll{Bool: true}, 5, structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}), | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), | |
| inBuf: `{"Bool":true,"FizzBuzz":5}`, | |
| inVal: addr(structUnexportedEmbeddedStructPointer{}), | |
| wantErr: EU(errNilField).withPos(`{"Bool":`, "/Bool").withType(0, T[structUnexportedEmbeddedStructPointer]()), | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), | |
| inBuf: `{"FizzBuzz":5,"Addr":"192.168.0.1"}`, | |
| inVal: addr(structUnexportedEmbeddedStructPointer{}), | |
| wantErr: EU(errNilField).withPos(`{"FizzBuzz":5,"Addr":`, "/Addr").withType(0, T[structUnexportedEmbeddedStructPointer]()), | |
| }, { | |
| name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), | |
| inBuf: `{"Bool":true,"FizzBuzz":10,"Addr":"192.168.0.1"}`, | |
| inVal: addr(structUnexportedEmbeddedStructPointer{&structOmitZeroAll{Int: 5}, 5, &structNestedAddr{netip.AddrFrom4([4]byte{127, 0, 0, 1})}}), | |
| want: addr(structUnexportedEmbeddedStructPointer{&structOmitZeroAll{Bool: true, Int: 5}, 10, &structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}), | |
| }, { | |
| name: jsontest.Name("Structs/Unknown"), | |
| inBuf: `{ | |
| "object0": {}, | |
| "object1": {"key1": "value"}, | |
| "object2": {"key1": "value", "key2": "value"}, | |
| "objects": {"":{"":{"":{}}}}, | |
| "array0": [], | |
| "array1": ["value1"], | |
| "array2": ["value1", "value2"], | |
| "array": [[[]]], | |
| "scalars": [null, false, true, "string", 12.345] | |
| }`, | |
| inVal: addr(struct{}{}), | |
| want: addr(struct{}{}), | |
| }, { | |
| name: jsontest.Name("Structs/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `{"Field":"Value"}`, | |
| inVal: addr(struct{ Field string }{}), | |
| want: addr(struct{ Field string }{"Value"}), | |
| }, { | |
| name: jsontest.Name("Slices/Null"), | |
| inBuf: `null`, | |
| inVal: addr([]string{"something"}), | |
| want: addr([]string(nil)), | |
| }, { | |
| name: jsontest.Name("Slices/Bool"), | |
| inBuf: `[true,false]`, | |
| inVal: new([]bool), | |
| want: addr([]bool{true, false}), | |
| }, { | |
| name: jsontest.Name("Slices/String"), | |
| inBuf: `["hello","goodbye"]`, | |
| inVal: new([]string), | |
| want: addr([]string{"hello", "goodbye"}), | |
| }, { | |
| name: jsontest.Name("Slices/Bytes"), | |
| inBuf: `["aGVsbG8=","Z29vZGJ5ZQ=="]`, | |
| inVal: new([][]byte), | |
| want: addr([][]byte{[]byte("hello"), []byte("goodbye")}), | |
| }, { | |
| name: jsontest.Name("Slices/Int"), | |
| inBuf: `[-2,-1,0,1,2]`, | |
| inVal: new([]int), | |
| want: addr([]int{-2, -1, 0, 1, 2}), | |
| }, { | |
| name: jsontest.Name("Slices/Uint"), | |
| inBuf: `[0,1,2,3,4]`, | |
| inVal: new([]uint), | |
| want: addr([]uint{0, 1, 2, 3, 4}), | |
| }, { | |
| name: jsontest.Name("Slices/Float"), | |
| inBuf: `[3.14159,12.34]`, | |
| inVal: new([]float64), | |
| want: addr([]float64{3.14159, 12.34}), | |
| }, { | |
| // NOTE: The semantics differs from v1, where the slice length is reset | |
| // and new elements are appended to the end. | |
| // See https://go.dev/issue/21092. | |
| name: jsontest.Name("Slices/Merge"), | |
| inBuf: `[{"k3":"v3"},{"k4":"v4"}]`, | |
| inVal: addr([]map[string]string{{"k1": "v1"}, {"k2": "v2"}}[:1]), | |
| want: addr([]map[string]string{{"k3": "v3"}, {"k4": "v4"}}), | |
| }, { | |
| name: jsontest.Name("Slices/Invalid/Channel"), | |
| inBuf: `["hello"]`, | |
| inVal: new([]chan string), | |
| want: addr([]chan string{nil}), | |
| wantErr: EU(nil).withPos(`[`, "/0").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Slices/RecursiveSlice"), | |
| inBuf: `[[],[],[[]],[[],[]]]`, | |
| inVal: new(recursiveSlice), | |
| want: addr(recursiveSlice{ | |
| {}, | |
| {}, | |
| {{}}, | |
| {{}, {}}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Slices/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr([]string{"nochange"}), | |
| want: addr([]string{"nochange"}), | |
| wantErr: EU(nil).withType('t', T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Slices/Invalid/String"), | |
| inBuf: `""`, | |
| inVal: addr([]string{"nochange"}), | |
| want: addr([]string{"nochange"}), | |
| wantErr: EU(nil).withType('"', T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Slices/Invalid/Number"), | |
| inBuf: `0`, | |
| inVal: addr([]string{"nochange"}), | |
| want: addr([]string{"nochange"}), | |
| wantErr: EU(nil).withType('0', T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Slices/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr([]string{"nochange"}), | |
| want: addr([]string{"nochange"}), | |
| wantErr: EU(nil).withType('{', T[[]string]()), | |
| }, { | |
| name: jsontest.Name("Slices/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `[false,true]`, | |
| inVal: addr([]bool{true, false}), | |
| want: addr([]bool{false, true}), | |
| }, { | |
| name: jsontest.Name("Arrays/Null"), | |
| inBuf: `null`, | |
| inVal: addr([1]string{"something"}), | |
| want: addr([1]string{}), | |
| }, { | |
| name: jsontest.Name("Arrays/Bool"), | |
| inBuf: `[true,false]`, | |
| inVal: new([2]bool), | |
| want: addr([2]bool{true, false}), | |
| }, { | |
| name: jsontest.Name("Arrays/String"), | |
| inBuf: `["hello","goodbye"]`, | |
| inVal: new([2]string), | |
| want: addr([2]string{"hello", "goodbye"}), | |
| }, { | |
| name: jsontest.Name("Arrays/Bytes"), | |
| inBuf: `["aGVsbG8=","Z29vZGJ5ZQ=="]`, | |
| inVal: new([2][]byte), | |
| want: addr([2][]byte{[]byte("hello"), []byte("goodbye")}), | |
| }, { | |
| name: jsontest.Name("Arrays/Int"), | |
| inBuf: `[-2,-1,0,1,2]`, | |
| inVal: new([5]int), | |
| want: addr([5]int{-2, -1, 0, 1, 2}), | |
| }, { | |
| name: jsontest.Name("Arrays/Uint"), | |
| inBuf: `[0,1,2,3,4]`, | |
| inVal: new([5]uint), | |
| want: addr([5]uint{0, 1, 2, 3, 4}), | |
| }, { | |
| name: jsontest.Name("Arrays/Float"), | |
| inBuf: `[3.14159,12.34]`, | |
| inVal: new([2]float64), | |
| want: addr([2]float64{3.14159, 12.34}), | |
| }, { | |
| // NOTE: The semantics differs from v1, where elements are not merged. | |
| // This is to maintain consistent merge semantics with slices. | |
| name: jsontest.Name("Arrays/Merge"), | |
| inBuf: `[{"k3":"v3"},{"k4":"v4"}]`, | |
| inVal: addr([2]map[string]string{{"k1": "v1"}, {"k2": "v2"}}), | |
| want: addr([2]map[string]string{{"k3": "v3"}, {"k4": "v4"}}), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Channel"), | |
| inBuf: `["hello"]`, | |
| inVal: new([1]chan string), | |
| want: new([1]chan string), | |
| wantErr: EU(nil).withPos(`[`, "/0").withType(0, T[chan string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Underflow"), | |
| inBuf: `{"F":[ ]}`, | |
| inVal: new(struct{ F [1]string }), | |
| want: addr(struct{ F [1]string }{}), | |
| wantErr: EU(errArrayUnderflow).withPos(`{"F":[ `, "/F").withType(']', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Underflow/UnmarshalArrayFromAnyLength"), | |
| opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, | |
| inBuf: `[-1,-2]`, | |
| inVal: addr([4]int{1, 2, 3, 4}), | |
| want: addr([4]int{-1, -2, 0, 0}), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Overflow"), | |
| inBuf: `["1","2"]`, | |
| inVal: new([1]string), | |
| want: addr([1]string{"1"}), | |
| wantErr: EU(errArrayOverflow).withPos(`["1","2"`, "").withType(']', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Overflow/UnmarshalArrayFromAnyLength"), | |
| opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, | |
| inBuf: `[-1,-2,-3,-4,-5,-6]`, | |
| inVal: addr([4]int{1, 2, 3, 4}), | |
| want: addr([4]int{-1, -2, -3, -4}), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Bool"), | |
| inBuf: `true`, | |
| inVal: addr([1]string{"nochange"}), | |
| want: addr([1]string{"nochange"}), | |
| wantErr: EU(nil).withType('t', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/String"), | |
| inBuf: `""`, | |
| inVal: addr([1]string{"nochange"}), | |
| want: addr([1]string{"nochange"}), | |
| wantErr: EU(nil).withType('"', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Number"), | |
| inBuf: `0`, | |
| inVal: addr([1]string{"nochange"}), | |
| want: addr([1]string{"nochange"}), | |
| wantErr: EU(nil).withType('0', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/Invalid/Object"), | |
| inBuf: `{}`, | |
| inVal: addr([1]string{"nochange"}), | |
| want: addr([1]string{"nochange"}), | |
| wantErr: EU(nil).withType('{', T[[1]string]()), | |
| }, { | |
| name: jsontest.Name("Arrays/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `[false,true]`, | |
| inVal: addr([2]bool{true, false}), | |
| want: addr([2]bool{false, true}), | |
| }, { | |
| name: jsontest.Name("Pointers/NullL0"), | |
| inBuf: `null`, | |
| inVal: new(*string), | |
| want: addr((*string)(nil)), | |
| }, { | |
| name: jsontest.Name("Pointers/NullL1"), | |
| inBuf: `null`, | |
| inVal: addr(new(*string)), | |
| want: addr((**string)(nil)), | |
| }, { | |
| name: jsontest.Name("Pointers/Bool"), | |
| inBuf: `true`, | |
| inVal: addr(new(bool)), | |
| want: addr(addr(true)), | |
| }, { | |
| name: jsontest.Name("Pointers/String"), | |
| inBuf: `"hello"`, | |
| inVal: addr(new(string)), | |
| want: addr(addr("hello")), | |
| }, { | |
| name: jsontest.Name("Pointers/Bytes"), | |
| inBuf: `"aGVsbG8="`, | |
| inVal: addr(new([]byte)), | |
| want: addr(addr([]byte("hello"))), | |
| }, { | |
| name: jsontest.Name("Pointers/Int"), | |
| inBuf: `-123`, | |
| inVal: addr(new(int)), | |
| want: addr(addr(int(-123))), | |
| }, { | |
| name: jsontest.Name("Pointers/Uint"), | |
| inBuf: `123`, | |
| inVal: addr(new(int)), | |
| want: addr(addr(int(123))), | |
| }, { | |
| name: jsontest.Name("Pointers/Float"), | |
| inBuf: `123.456`, | |
| inVal: addr(new(float64)), | |
| want: addr(addr(float64(123.456))), | |
| }, { | |
| name: jsontest.Name("Pointers/Allocate"), | |
| inBuf: `"hello"`, | |
| inVal: addr((*string)(nil)), | |
| want: addr(addr("hello")), | |
| }, { | |
| name: jsontest.Name("Points/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `true`, | |
| inVal: addr(new(bool)), | |
| want: addr(addr(true)), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/Null"), | |
| inBuf: `null`, | |
| inVal: new(any), | |
| want: new(any), | |
| }, { | |
| name: jsontest.Name("Interfaces/NonEmpty/Null"), | |
| inBuf: `null`, | |
| inVal: new(io.Reader), | |
| want: new(io.Reader), | |
| }, { | |
| name: jsontest.Name("Interfaces/NonEmpty/Invalid"), | |
| inBuf: `"hello"`, | |
| inVal: new(io.Reader), | |
| want: new(io.Reader), | |
| wantErr: EU(internal.ErrNilInterface).withType(0, T[io.Reader]()), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/False"), | |
| inBuf: `false`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = false | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/True"), | |
| inBuf: `true`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = true | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/String"), | |
| inBuf: `"string"`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = "string" | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/Number"), | |
| inBuf: `3.14159`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = 3.14159 | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/Object"), | |
| inBuf: `{"k":"v"}`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = map[string]any{"k": "v"} | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Empty/Array"), | |
| inBuf: `["v"]`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = []any{"v"} | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/NamedAny/String"), | |
| inBuf: `"string"`, | |
| inVal: new(namedAny), | |
| want: func() namedAny { | |
| var vi namedAny = "string" | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Invalid"), | |
| inBuf: `]`, | |
| inVal: new(any), | |
| want: new(any), | |
| wantErr: newInvalidCharacterError("]", "at start of value", 0, ""), | |
| }, { | |
| // NOTE: The semantics differs from v1, | |
| // where existing map entries were not merged into. | |
| // See https://go.dev/issue/26946. | |
| // See https://go.dev/issue/33993. | |
| name: jsontest.Name("Interfaces/Merge/Map"), | |
| inBuf: `{"k2":"v2"}`, | |
| inVal: func() any { | |
| var vi any = map[string]string{"k1": "v1"} | |
| return &vi | |
| }(), | |
| want: func() any { | |
| var vi any = map[string]string{"k1": "v1", "k2": "v2"} | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Merge/Struct"), | |
| inBuf: `{"Array":["goodbye"]}`, | |
| inVal: func() any { | |
| var vi any = structAll{String: "hello"} | |
| return &vi | |
| }(), | |
| want: func() any { | |
| var vi any = structAll{String: "hello", Array: [1]string{"goodbye"}} | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Merge/NamedInt"), | |
| inBuf: `64`, | |
| inVal: func() any { | |
| var vi any = namedInt64(-64) | |
| return &vi | |
| }(), | |
| want: func() any { | |
| var vi any = namedInt64(+64) | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `true`, | |
| inVal: new(any), | |
| want: func() any { | |
| var vi any = true | |
| return &vi | |
| }(), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any"), | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{nil, false, true, "", 0.0, map[string]any{}, []any{}}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Named"), | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X namedAny }), | |
| want: addr(struct{ X namedAny }{[]any{nil, false, true, "", 0.0, map[string]any{}, []any{}}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Stringified"), | |
| opts: []Options{StringifyNumbers(true)}, | |
| inBuf: `{"X":"0"}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{"0"}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/UnmarshalFunc/Any"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *any) error { | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{"called"}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/UnmarshalFunc/Bool"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *bool) error { | |
| *v = string(b) != "true" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{nil, true, false, "", 0.0, map[string]any{}, []any{}}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/UnmarshalFunc/String"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{nil, false, true, "called", 0.0, map[string]any{}, []any{}}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/UnmarshalFunc/Float64"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *float64) error { | |
| *v = 3.14159 | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{nil, false, true, "", 3.14159, map[string]any{}, []any{}}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/UnmarshalFunc/MapStringAny"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *map[string]any) error { | |
| *v = map[string]any{"called": nil} | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{nil, false, true, "", 0.0, map[string]any{"called": nil}, []any{}}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/UnmarshalFunc/SliceAny"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *[]any) error { | |
| *v = []any{"called"} | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":[null,false,true,"",0,{},[]]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{"called"}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/NonEmpty"), | |
| inBuf: `{"X":{"fizz":"buzz"}}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{map[string]any{"fizz": "buzz"}}), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/RejectDuplicateNames"), | |
| inBuf: `{"X":{"fizz":"buzz","fizz":true}}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{map[string]any{"fizz": "buzz"}}), | |
| wantErr: newDuplicateNameError("/X", []byte(`"fizz"`), len64(`{"X":{"fizz":"buzz",`)), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Maps/AllowDuplicateNames"), | |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, | |
| inBuf: `{"X":{"fizz":"buzz","fizz":true}}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{map[string]any{"fizz": "buzz"}}), | |
| wantErr: EU(nil).withPos(`{"X":{"fizz":"buzz","fizz":`, "/X/fizz").withType('t', T[string]()), | |
| }, { | |
| name: jsontest.Name("Interfaces/Any/Slices/NonEmpty"), | |
| inBuf: `{"X":["fizz","buzz"]}`, | |
| inVal: new(struct{ X any }), | |
| want: addr(struct{ X any }{[]any{"fizz", "buzz"}}), | |
| }, { | |
| name: jsontest.Name("Methods/NilPointer/Null"), | |
| inBuf: `{"X":null}`, | |
| inVal: addr(struct{ X *allMethods }{X: (*allMethods)(nil)}), | |
| want: addr(struct{ X *allMethods }{X: (*allMethods)(nil)}), // method should not be called | |
| }, { | |
| name: jsontest.Name("Methods/NilPointer/Value"), | |
| inBuf: `{"X":"value"}`, | |
| inVal: addr(struct{ X *allMethods }{X: (*allMethods)(nil)}), | |
| want: addr(struct{ X *allMethods }{X: &allMethods{method: "UnmarshalJSONFrom", value: []byte(`"value"`)}}), | |
| }, { | |
| name: jsontest.Name("Methods/NilInterface/Null"), | |
| inBuf: `{"X":null}`, | |
| inVal: addr(struct{ X MarshalerTo }{X: (*allMethods)(nil)}), | |
| want: addr(struct{ X MarshalerTo }{X: nil}), // interface value itself is nil'd out | |
| }, { | |
| name: jsontest.Name("Methods/NilInterface/Value"), | |
| inBuf: `{"X":"value"}`, | |
| inVal: addr(struct{ X MarshalerTo }{X: (*allMethods)(nil)}), | |
| want: addr(struct{ X MarshalerTo }{X: &allMethods{method: "UnmarshalJSONFrom", value: []byte(`"value"`)}}), | |
| }, { | |
| name: jsontest.Name("Methods/AllMethods"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *allMethods }), | |
| want: addr(struct{ X *allMethods }{X: &allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}), | |
| }, { | |
| name: jsontest.Name("Methods/AllMethodsExceptJSONv2"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *allMethodsExceptJSONv2 }), | |
| want: addr(struct{ X *allMethodsExceptJSONv2 }{X: &allMethodsExceptJSONv2{allMethods: allMethods{method: "UnmarshalJSON", value: []byte(`"hello"`)}}}), | |
| }, { | |
| name: jsontest.Name("Methods/AllMethodsExceptJSONv1"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *allMethodsExceptJSONv1 }), | |
| want: addr(struct{ X *allMethodsExceptJSONv1 }{X: &allMethodsExceptJSONv1{allMethods: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), | |
| }, { | |
| name: jsontest.Name("Methods/AllMethodsExceptText"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *allMethodsExceptText }), | |
| want: addr(struct{ X *allMethodsExceptText }{X: &allMethodsExceptText{allMethods: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), | |
| }, { | |
| name: jsontest.Name("Methods/OnlyMethodJSONv2"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *onlyMethodJSONv2 }), | |
| want: addr(struct{ X *onlyMethodJSONv2 }{X: &onlyMethodJSONv2{allMethods: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), | |
| }, { | |
| name: jsontest.Name("Methods/OnlyMethodJSONv1"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *onlyMethodJSONv1 }), | |
| want: addr(struct{ X *onlyMethodJSONv1 }{X: &onlyMethodJSONv1{allMethods: allMethods{method: "UnmarshalJSON", value: []byte(`"hello"`)}}}), | |
| }, { | |
| name: jsontest.Name("Methods/OnlyMethodText"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X *onlyMethodText }), | |
| want: addr(struct{ X *onlyMethodText }{X: &onlyMethodText{allMethods: allMethods{method: "UnmarshalText", value: []byte(`hello`)}}}), | |
| }, { | |
| name: jsontest.Name("Methods/Text/Null"), | |
| inBuf: `{"X":null}`, | |
| inVal: addr(struct{ X unmarshalTextFunc }{unmarshalTextFunc(func(b []byte) error { | |
| return errMustNotCall | |
| })}), | |
| want: addr(struct{ X unmarshalTextFunc }{nil}), | |
| }, { | |
| name: jsontest.Name("Methods/IP"), | |
| inBuf: `"192.168.0.100"`, | |
| inVal: new(net.IP), | |
| want: addr(net.IPv4(192, 168, 0, 100)), | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/46516. | |
| name: jsontest.Name("Methods/Anonymous"), | |
| inBuf: `{"X":"hello"}`, | |
| inVal: new(struct{ X struct{ allMethods } }), | |
| want: addr(struct{ X struct{ allMethods } }{X: struct{ allMethods }{allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/22967. | |
| name: jsontest.Name("Methods/Addressable"), | |
| inBuf: `{"V":"hello","M":{"K":"hello"},"I":"hello"}`, | |
| inVal: addr(struct { | |
| V allMethods | |
| M map[string]allMethods | |
| I any | |
| }{ | |
| I: allMethods{}, // need to initialize with concrete value | |
| }), | |
| want: addr(struct { | |
| V allMethods | |
| M map[string]allMethods | |
| I any | |
| }{ | |
| V: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}, | |
| M: map[string]allMethods{"K": {method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}, | |
| I: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}, | |
| }), | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/29732. | |
| name: jsontest.Name("Methods/MapKey/JSONv2"), | |
| inBuf: `{"k1":"v1b","k2":"v2"}`, | |
| inVal: addr(map[structMethodJSONv2]string{{"k1"}: "v1a", {"k3"}: "v3"}), | |
| want: addr(map[structMethodJSONv2]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), | |
| }, { | |
| // NOTE: Fixes https://go.dev/issue/29732. | |
| name: jsontest.Name("Methods/MapKey/JSONv1"), | |
| inBuf: `{"k1":"v1b","k2":"v2"}`, | |
| inVal: addr(map[structMethodJSONv1]string{{"k1"}: "v1a", {"k3"}: "v3"}), | |
| want: addr(map[structMethodJSONv1]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), | |
| }, { | |
| name: jsontest.Name("Methods/MapKey/Text"), | |
| inBuf: `{"k1":"v1b","k2":"v2"}`, | |
| inVal: addr(map[structMethodText]string{{"k1"}: "v1a", {"k3"}: "v3"}), | |
| want: addr(map[structMethodText]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), | |
| }, { | |
| name: jsontest.Name("Methods/JSONv2/ErrUnsupported"), | |
| inBuf: `{"fizz":123}`, | |
| inVal: addr(unsupportedMethodJSONv2{}), | |
| want: addr(unsupportedMethodJSONv2{"called": 1, "fizz": 123}), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/Error"), | |
| inBuf: `{}`, | |
| inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { | |
| return errSomeError | |
| })), | |
| wantErr: EU(errSomeError).withType(0, T[unmarshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/TooFew"), | |
| inBuf: `{}`, | |
| inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { | |
| return nil // do nothing | |
| })), | |
| wantErr: EU(errNonSingularValue).withType(0, T[unmarshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/TooMany"), | |
| inBuf: `{}{}`, | |
| inVal: addr(unmarshalJSONv2Func(func(dec *jsontext.Decoder) error { | |
| dec.ReadValue() | |
| dec.ReadValue() | |
| return nil | |
| })), | |
| wantErr: EU(errNonSingularValue).withPos(`{}`, "").withType(0, T[unmarshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv2/ErrUnsupported"), | |
| inBuf: `{}`, | |
| inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { | |
| return errors.ErrUnsupported | |
| })), | |
| wantErr: EU(nil).withType(0, T[unmarshalJSONv2Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv1/Error"), | |
| inBuf: `{}`, | |
| inVal: addr(unmarshalJSONv1Func(func([]byte) error { | |
| return errSomeError | |
| })), | |
| wantErr: EU(errSomeError).withType('{', T[unmarshalJSONv1Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/JSONv1/ErrUnsupported"), | |
| inBuf: `{}`, | |
| inVal: addr(unmarshalJSONv1Func(func([]byte) error { | |
| return errors.ErrUnsupported | |
| })), | |
| wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "UnmarshalJSON method")).withType('{', T[unmarshalJSONv1Func]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/Text/Error"), | |
| inBuf: `"value"`, | |
| inVal: addr(unmarshalTextFunc(func([]byte) error { | |
| return errSomeError | |
| })), | |
| wantErr: EU(errSomeError).withType('"', T[unmarshalTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/Text/Syntax"), | |
| inBuf: `{}`, | |
| inVal: addr(unmarshalTextFunc(func([]byte) error { | |
| panic("should not be called") | |
| })), | |
| wantErr: EU(errNonStringValue).withType('{', T[unmarshalTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Methods/Invalid/Text/ErrUnsupported"), | |
| inBuf: `"value"`, | |
| inVal: addr(unmarshalTextFunc(func([]byte) error { | |
| return errors.ErrUnsupported | |
| })), | |
| wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "UnmarshalText method")).withType('"', T[unmarshalTextFunc]()), | |
| }, { | |
| name: jsontest.Name("Functions/String/V1"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { | |
| if string(b) != `""` { | |
| return fmt.Errorf("got %s, want %s", b, `""`) | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Functions/String/Empty"), | |
| opts: []Options{WithUnmarshalers(nil)}, | |
| inBuf: `"hello"`, | |
| inVal: addr(""), | |
| want: addr("hello"), | |
| }, { | |
| name: jsontest.Name("Functions/NamedString/V1/NoMatch"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *namedString) error { | |
| panic("should not be called") | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Functions/NamedString/V1/Match"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *namedString) error { | |
| if string(b) != `""` { | |
| return fmt.Errorf("got %s, want %s", b, `""`) | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(namedString("")), | |
| want: addr(namedString("called")), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| switch b, err := dec.ReadValue(); { | |
| case err != nil: | |
| return err | |
| case string(b) != `""`: | |
| return fmt.Errorf("got %s, want %s", b, `""`) | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Functions/NamedString/V2/NoMatch"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *namedString) error { | |
| panic("should not be called") | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Functions/NamedString/V2/Match"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *namedString) error { | |
| switch t, err := dec.ReadToken(); { | |
| case err != nil: | |
| return err | |
| case t.String() != ``: | |
| return fmt.Errorf("got %q, want %q", t, ``) | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(namedString("")), | |
| want: addr(namedString("called")), | |
| }, { | |
| name: jsontest.Name("Functions/String/Empty1/NoMatch"), | |
| opts: []Options{ | |
| WithUnmarshalers(new(Unmarshalers)), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Functions/String/Empty2/NoMatch"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers()), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Functions/String/V1/DirectError"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func([]byte, *string) error { | |
| return errSomeError | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| wantErr: EU(errSomeError).withType('"', reflect.PointerTo(stringType)), | |
| }, { | |
| name: jsontest.Name("Functions/String/V1/SkipError"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func([]byte, *string) error { | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| wantErr: EU(wrapErrUnsupported(errors.ErrUnsupported, "unmarshal function of type func([]byte, T) error")).withType('"', reflect.PointerTo(stringType)), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2/DirectError"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| return errSomeError | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| wantErr: EU(errSomeError).withType(0, reflect.PointerTo(stringType)), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2/TooFew"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| return nil | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| wantErr: EU(errNonSingularValue).withType(0, reflect.PointerTo(stringType)), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2/TooMany"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| if _, err := dec.ReadValue(); err != nil { | |
| return err | |
| } | |
| if _, err := dec.ReadValue(); err != nil { | |
| return err | |
| } | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"X":["",""]}`, | |
| inVal: addr(struct{ X []string }{}), | |
| want: addr(struct{ X []string }{[]string{""}}), | |
| wantErr: EU(errNonSingularValue).withPos(`{"X":["",`, "/X").withType(0, reflect.PointerTo(stringType)), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2/Skipped"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2/ProcessBeforeSkip"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| if _, err := dec.ReadValue(); err != nil { | |
| return err | |
| } | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| wantErr: EU(errUnsupportedMutation).withType(0, reflect.PointerTo(stringType)), | |
| }, { | |
| name: jsontest.Name("Functions/String/V2/WrappedUnsupported"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| return fmt.Errorf("wrap: %w", errors.ErrUnsupported) | |
| })), | |
| }, | |
| inBuf: `""`, | |
| inVal: addr(""), | |
| want: addr(""), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V1"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *nocaseString) error { | |
| if string(b) != `"hello"` { | |
| return fmt.Errorf("got %s, want %s", b, `"hello"`) | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[nocaseString]string{}), | |
| want: addr(map[nocaseString]string{"called": "world"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/TextMarshaler/V1"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v encoding.TextMarshaler) error { | |
| if string(b) != `"hello"` { | |
| return fmt.Errorf("got %s, want %s", b, `"hello"`) | |
| } | |
| *v.(*nocaseString) = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[nocaseString]string{}), | |
| want: addr(map[nocaseString]string{"called": "world"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/NoCaseString/V2"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *nocaseString) error { | |
| switch t, err := dec.ReadToken(); { | |
| case err != nil: | |
| return err | |
| case t.String() != "hello": | |
| return fmt.Errorf("got %q, want %q", t, "hello") | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[nocaseString]string{}), | |
| want: addr(map[nocaseString]string{"called": "world"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/TextMarshaler/V2"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v encoding.TextMarshaler) error { | |
| switch b, err := dec.ReadValue(); { | |
| case err != nil: | |
| return err | |
| case string(b) != `"hello"`: | |
| return fmt.Errorf("got %s, want %s", b, `"hello"`) | |
| } | |
| *v.(*nocaseString) = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[nocaseString]string{}), | |
| want: addr(map[nocaseString]string{"called": "world"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Key/String/V1/DuplicateName"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| if _, err := dec.ReadValue(); err != nil { | |
| return err | |
| } | |
| xd := export.Decoder(dec) | |
| *v = fmt.Sprintf("%d-%d", len(xd.Tokens.Stack), xd.Tokens.Last.Length()) | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"name":"value","name":"value"}`, | |
| inVal: addr(map[string]string{}), | |
| want: addr(map[string]string{"1-1": "1-2"}), | |
| wantErr: newDuplicateNameError("", []byte(`"name"`), len64(`{"name":"value",`)), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/NoCaseString/V1"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *nocaseString) error { | |
| if string(b) != `"world"` { | |
| return fmt.Errorf("got %s, want %s", b, `"world"`) | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[string]nocaseString{}), | |
| want: addr(map[string]nocaseString{"hello": "called"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/TextMarshaler/V1"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v encoding.TextMarshaler) error { | |
| if string(b) != `"world"` { | |
| return fmt.Errorf("got %s, want %s", b, `"world"`) | |
| } | |
| *v.(*nocaseString) = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[string]nocaseString{}), | |
| want: addr(map[string]nocaseString{"hello": "called"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/NoCaseString/V2"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *nocaseString) error { | |
| switch t, err := dec.ReadToken(); { | |
| case err != nil: | |
| return err | |
| case t.String() != "world": | |
| return fmt.Errorf("got %q, want %q", t, "world") | |
| } | |
| *v = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[string]nocaseString{}), | |
| want: addr(map[string]nocaseString{"hello": "called"}), | |
| }, { | |
| name: jsontest.Name("Functions/Map/Value/TextMarshaler/V2"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v encoding.TextMarshaler) error { | |
| switch b, err := dec.ReadValue(); { | |
| case err != nil: | |
| return err | |
| case string(b) != `"world"`: | |
| return fmt.Errorf("got %s, want %s", b, `"world"`) | |
| } | |
| *v.(*nocaseString) = "called" | |
| return nil | |
| })), | |
| }, | |
| inBuf: `{"hello":"world"}`, | |
| inVal: addr(map[string]nocaseString{}), | |
| want: addr(map[string]nocaseString{"hello": "called"}), | |
| }, { | |
| name: jsontest.Name("Funtions/Struct/Fields"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFunc(func(b []byte, v *bool) error { | |
| if string(b) != `"called1"` { | |
| return fmt.Errorf("got %s, want %s", b, `"called1"`) | |
| } | |
| *v = true | |
| return nil | |
| }), | |
| UnmarshalFunc(func(b []byte, v *string) error { | |
| if string(b) != `"called2"` { | |
| return fmt.Errorf("got %s, want %s", b, `"called2"`) | |
| } | |
| *v = "called2" | |
| return nil | |
| }), | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *[]byte) error { | |
| switch t, err := dec.ReadToken(); { | |
| case err != nil: | |
| return err | |
| case t.String() != "called3": | |
| return fmt.Errorf("got %q, want %q", t, "called3") | |
| } | |
| *v = []byte("called3") | |
| return nil | |
| }), | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *int64) error { | |
| switch b, err := dec.ReadValue(); { | |
| case err != nil: | |
| return err | |
| case string(b) != `"called4"`: | |
| return fmt.Errorf("got %s, want %s", b, `"called4"`) | |
| } | |
| *v = 123 | |
| return nil | |
| }), | |
| )), | |
| }, | |
| inBuf: `{"Bool":"called1","String":"called2","Bytes":"called3","Int":"called4","Uint":456,"Float":789}`, | |
| inVal: addr(structScalars{}), | |
| want: addr(structScalars{Bool: true, String: "called2", Bytes: []byte("called3"), Int: 123, Uint: 456, Float: 789}), | |
| }, { | |
| name: jsontest.Name("Functions/Struct/Embedded"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFunc(func([]byte, *structEmbeddedL1) error { | |
| panic("should not be called") | |
| }), | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *StructEmbed2) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, | |
| inVal: new(structEmbedded), | |
| want: addr(structEmbedded{ | |
| X: structEmbeddedL1{ | |
| X: &structEmbeddedL2{A: "A1", B: "B1" /* C: "C1" */}, | |
| StructEmbed1: StructEmbed1{ /* C: "C2" */ D: "D2" /* E: "E2" */}, | |
| }, | |
| StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, | |
| }), | |
| }, { | |
| name: jsontest.Name("Functions/Slice/Elem"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { | |
| *v = strings.Trim(strings.ToUpper(string(b)), `"`) | |
| return nil | |
| })), | |
| }, | |
| inBuf: `["hello","World"]`, | |
| inVal: addr([]string{}), | |
| want: addr([]string{"HELLO", "WORLD"}), | |
| }, { | |
| name: jsontest.Name("Functions/Array/Elem"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { | |
| *v = strings.Trim(strings.ToUpper(string(b)), `"`) | |
| return nil | |
| })), | |
| }, | |
| inBuf: `["hello","World"]`, | |
| inVal: addr([2]string{}), | |
| want: addr([2]string{"HELLO", "WORLD"}), | |
| }, { | |
| name: jsontest.Name("Functions/Pointer/Nil"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| t, err := dec.ReadToken() | |
| *v = strings.ToUpper(t.String()) | |
| return err | |
| })), | |
| }, | |
| inBuf: `{"X":"hello"}`, | |
| inVal: addr(struct{ X *string }{nil}), | |
| want: addr(struct{ X *string }{addr("HELLO")}), | |
| }, { | |
| name: jsontest.Name("Functions/Pointer/NonNil"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| t, err := dec.ReadToken() | |
| *v = strings.ToUpper(t.String()) | |
| return err | |
| })), | |
| }, | |
| inBuf: `{"X":"hello"}`, | |
| inVal: addr(struct{ X *string }{addr("")}), | |
| want: addr(struct{ X *string }{addr("HELLO")}), | |
| }, { | |
| name: jsontest.Name("Functions/Interface/Nil"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v fmt.Stringer) error { | |
| panic("should not be called") | |
| })), | |
| }, | |
| inBuf: `{"X":"hello"}`, | |
| inVal: addr(struct{ X fmt.Stringer }{nil}), | |
| want: addr(struct{ X fmt.Stringer }{nil}), | |
| wantErr: EU(internal.ErrNilInterface).withPos(`{"X":`, "/X").withType(0, T[fmt.Stringer]()), | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NetIP"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { | |
| *v = net.IP{} | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| inBuf: `{"X":"1.1.1.1"}`, | |
| inVal: addr(struct{ X fmt.Stringer }{nil}), | |
| want: addr(struct{ X fmt.Stringer }{net.IPv4(1, 1, 1, 1)}), | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NewPointerNetIP"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { | |
| *v = new(net.IP) | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| inBuf: `{"X":"1.1.1.1"}`, | |
| inVal: addr(struct{ X fmt.Stringer }{nil}), | |
| want: addr(struct{ X fmt.Stringer }{addr(net.IPv4(1, 1, 1, 1))}), | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NilPointerNetIP"), | |
| opts: []Options{ | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { | |
| *v = (*net.IP)(nil) | |
| return errors.ErrUnsupported | |
| })), | |
| }, | |
| inBuf: `{"X":"1.1.1.1"}`, | |
| inVal: addr(struct{ X fmt.Stringer }{nil}), | |
| want: addr(struct{ X fmt.Stringer }{addr(net.IPv4(1, 1, 1, 1))}), | |
| }, { | |
| name: jsontest.Name("Functions/Interface/NilPointerNetIP/Override"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { | |
| *v = (*net.IP)(nil) | |
| return errors.ErrUnsupported | |
| }), | |
| UnmarshalFunc(func(b []byte, v *net.IP) error { | |
| b = bytes.ReplaceAll(b, []byte(`1`), []byte(`8`)) | |
| return v.UnmarshalText(bytes.Trim(b, `"`)) | |
| }), | |
| )), | |
| }, | |
| inBuf: `{"X":"1.1.1.1"}`, | |
| inVal: addr(struct{ X fmt.Stringer }{nil}), | |
| want: addr(struct{ X fmt.Stringer }{addr(net.IPv4(8, 8, 8, 8))}), | |
| }, { | |
| name: jsontest.Name("Functions/Interface/Any"), | |
| opts: []Options{ | |
| WithUnmarshalers(func() *Unmarshalers { | |
| type P struct { | |
| D int | |
| N int64 | |
| } | |
| type PV struct { | |
| P P | |
| V any | |
| } | |
| var lastChecks []func() error | |
| checkLast := func() error { | |
| for _, fn := range lastChecks { | |
| if err := fn(); err != nil { | |
| return err | |
| } | |
| } | |
| return errors.ErrUnsupported | |
| } | |
| makeValueChecker := func(name string, want []PV) func(d *jsontext.Decoder, v any) error { | |
| checkNext := func(d *jsontext.Decoder, v any) error { | |
| xd := export.Decoder(d) | |
| p := P{len(xd.Tokens.Stack), xd.Tokens.Last.Length()} | |
| rv := reflect.ValueOf(v) | |
| pv := PV{p, v} | |
| switch { | |
| case len(want) == 0: | |
| return fmt.Errorf("%s: %v: got more values than expected", name, p) | |
| case !rv.IsValid() || rv.Kind() != reflect.Pointer || rv.IsNil(): | |
| return fmt.Errorf("%s: %v: got %#v, want non-nil pointer type", name, p, v) | |
| case !reflect.DeepEqual(pv, want[0]): | |
| return fmt.Errorf("%s:\n\tgot %#v\n\twant %#v", name, pv, want[0]) | |
| default: | |
| want = want[1:] | |
| return errors.ErrUnsupported | |
| } | |
| } | |
| lastChecks = append(lastChecks, func() error { | |
| if len(want) > 0 { | |
| return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) | |
| } | |
| return nil | |
| }) | |
| return checkNext | |
| } | |
| makePositionChecker := func(name string, want []P) func(d *jsontext.Decoder, v any) error { | |
| checkNext := func(d *jsontext.Decoder, v any) error { | |
| xd := export.Decoder(d) | |
| p := P{len(xd.Tokens.Stack), xd.Tokens.Last.Length()} | |
| switch { | |
| case len(want) == 0: | |
| return fmt.Errorf("%s: %v: got more values than wanted", name, p) | |
| case p != want[0]: | |
| return fmt.Errorf("%s: got %v, want %v", name, p, want[0]) | |
| default: | |
| want = want[1:] | |
| return errors.ErrUnsupported | |
| } | |
| } | |
| lastChecks = append(lastChecks, func() error { | |
| if len(want) > 0 { | |
| return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) | |
| } | |
| return nil | |
| }) | |
| return checkNext | |
| } | |
| // In contrast to marshal, unmarshal automatically allocates for | |
| // nil pointers, which causes unmarshal to visit more values. | |
| wantAny := []PV{ | |
| {P{1, 0}, addr(any(nil))}, | |
| {P{1, 1}, addr(any(valueStringer{}))}, | |
| {P{1, 1}, addr(valueStringer{})}, | |
| {P{1, 2}, addr(any((*valueStringer)(nil)))}, | |
| {P{1, 2}, addr((*valueStringer)(nil))}, | |
| {P{1, 2}, addr(valueStringer{})}, | |
| {P{1, 3}, addr(any(addr(valueStringer{})))}, | |
| {P{1, 3}, addr(addr(valueStringer{}))}, | |
| {P{1, 3}, addr(valueStringer{})}, | |
| {P{1, 4}, addr(any((**valueStringer)(nil)))}, | |
| {P{1, 4}, addr((**valueStringer)(nil))}, | |
| {P{1, 4}, addr((*valueStringer)(nil))}, | |
| {P{1, 4}, addr(valueStringer{})}, | |
| {P{1, 5}, addr(any(addr((*valueStringer)(nil))))}, | |
| {P{1, 5}, addr(addr((*valueStringer)(nil)))}, | |
| {P{1, 5}, addr((*valueStringer)(nil))}, | |
| {P{1, 5}, addr(valueStringer{})}, | |
| {P{1, 6}, addr(any(addr(addr(valueStringer{}))))}, | |
| {P{1, 6}, addr(addr(addr(valueStringer{})))}, | |
| {P{1, 6}, addr(addr(valueStringer{}))}, | |
| {P{1, 6}, addr(valueStringer{})}, | |
| {P{1, 7}, addr(any(pointerStringer{}))}, | |
| {P{1, 7}, addr(pointerStringer{})}, | |
| {P{1, 8}, addr(any((*pointerStringer)(nil)))}, | |
| {P{1, 8}, addr((*pointerStringer)(nil))}, | |
| {P{1, 8}, addr(pointerStringer{})}, | |
| {P{1, 9}, addr(any(addr(pointerStringer{})))}, | |
| {P{1, 9}, addr(addr(pointerStringer{}))}, | |
| {P{1, 9}, addr(pointerStringer{})}, | |
| {P{1, 10}, addr(any((**pointerStringer)(nil)))}, | |
| {P{1, 10}, addr((**pointerStringer)(nil))}, | |
| {P{1, 10}, addr((*pointerStringer)(nil))}, | |
| {P{1, 10}, addr(pointerStringer{})}, | |
| {P{1, 11}, addr(any(addr((*pointerStringer)(nil))))}, | |
| {P{1, 11}, addr(addr((*pointerStringer)(nil)))}, | |
| {P{1, 11}, addr((*pointerStringer)(nil))}, | |
| {P{1, 11}, addr(pointerStringer{})}, | |
| {P{1, 12}, addr(any(addr(addr(pointerStringer{}))))}, | |
| {P{1, 12}, addr(addr(addr(pointerStringer{})))}, | |
| {P{1, 12}, addr(addr(pointerStringer{}))}, | |
| {P{1, 12}, addr(pointerStringer{})}, | |
| {P{1, 13}, addr(any("LAST"))}, | |
| {P{1, 13}, addr("LAST")}, | |
| } | |
| checkAny := makeValueChecker("any", wantAny) | |
| anyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v any) error { | |
| return checkAny(dec, v) | |
| }) | |
| var wantPointerAny []PV | |
| for _, v := range wantAny { | |
| if _, ok := v.V.(*any); ok { | |
| wantPointerAny = append(wantPointerAny, v) | |
| } | |
| } | |
| checkPointerAny := makeValueChecker("*any", wantPointerAny) | |
| pointerAnyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *any) error { | |
| return checkPointerAny(dec, v) | |
| }) | |
| checkNamedAny := makeValueChecker("namedAny", wantAny) | |
| namedAnyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v namedAny) error { | |
| return checkNamedAny(dec, v) | |
| }) | |
| checkPointerNamedAny := makeValueChecker("*namedAny", nil) | |
| pointerNamedAnyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *namedAny) error { | |
| return checkPointerNamedAny(dec, v) | |
| }) | |
| type stringer = fmt.Stringer | |
| var wantStringer []PV | |
| for _, v := range wantAny { | |
| if _, ok := v.V.(stringer); ok { | |
| wantStringer = append(wantStringer, v) | |
| } | |
| } | |
| checkStringer := makeValueChecker("stringer", wantStringer) | |
| stringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v stringer) error { | |
| return checkStringer(dec, v) | |
| }) | |
| checkPointerStringer := makeValueChecker("*stringer", nil) | |
| pointerStringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *stringer) error { | |
| return checkPointerStringer(dec, v) | |
| }) | |
| wantValueStringer := []P{{1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}} | |
| checkPointerValueStringer := makePositionChecker("*valueStringer", wantValueStringer) | |
| pointerValueStringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *valueStringer) error { | |
| return checkPointerValueStringer(dec, v) | |
| }) | |
| wantPointerStringer := []P{{1, 7}, {1, 8}, {1, 9}, {1, 10}, {1, 11}, {1, 12}} | |
| checkPointerPointerStringer := makePositionChecker("*pointerStringer", wantPointerStringer) | |
| pointerPointerStringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *pointerStringer) error { | |
| return checkPointerPointerStringer(dec, v) | |
| }) | |
| lastUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| return checkLast() | |
| }) | |
| return JoinUnmarshalers( | |
| // This is just like unmarshaling into a Go array, | |
| // but avoids zeroing the element before calling unmarshal. | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *[14]any) error { | |
| if _, err := dec.ReadToken(); err != nil { | |
| return err | |
| } | |
| for i := range len(*v) { | |
| if err := UnmarshalDecode(dec, &(*v)[i]); err != nil { | |
| return err | |
| } | |
| } | |
| if _, err := dec.ReadToken(); err != nil { | |
| return err | |
| } | |
| return nil | |
| }), | |
| anyUnmarshaler, | |
| pointerAnyUnmarshaler, | |
| namedAnyUnmarshaler, | |
| pointerNamedAnyUnmarshaler, // never called | |
| stringerUnmarshaler, | |
| pointerStringerUnmarshaler, // never called | |
| pointerValueStringerUnmarshaler, | |
| pointerPointerStringerUnmarshaler, | |
| lastUnmarshaler, | |
| ) | |
| }()), | |
| }, | |
| inBuf: `[null,{},{},{},{},{},{},{},{},{},{},{},{},"LAST"]`, | |
| inVal: addr([...]any{ | |
| nil, // nil | |
| valueStringer{}, // T | |
| (*valueStringer)(nil), // *T | |
| addr(valueStringer{}), // *T | |
| (**valueStringer)(nil), // **T | |
| addr((*valueStringer)(nil)), // **T | |
| addr(addr(valueStringer{})), // **T | |
| pointerStringer{}, // T | |
| (*pointerStringer)(nil), // *T | |
| addr(pointerStringer{}), // *T | |
| (**pointerStringer)(nil), // **T | |
| addr((*pointerStringer)(nil)), // **T | |
| addr(addr(pointerStringer{})), // **T | |
| "LAST", | |
| }), | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/V1First"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFunc(func(b []byte, v *string) error { | |
| if string(b) != `"called"` { | |
| return fmt.Errorf("got %s, want %s", b, `"called"`) | |
| } | |
| *v = "called" | |
| return nil | |
| }), | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| inBuf: `"called"`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/V2First"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| switch t, err := dec.ReadToken(); { | |
| case err != nil: | |
| return err | |
| case t.String() != "called": | |
| return fmt.Errorf("got %q, want %q", t, "called") | |
| } | |
| *v = "called" | |
| return nil | |
| }), | |
| UnmarshalFunc(func([]byte, *string) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| inBuf: `"called"`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/V2Skipped"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { | |
| return errors.ErrUnsupported | |
| }), | |
| UnmarshalFunc(func(b []byte, v *string) error { | |
| if string(b) != `"called"` { | |
| return fmt.Errorf("got %s, want %s", b, `"called"`) | |
| } | |
| *v = "called" | |
| return nil | |
| }), | |
| )), | |
| }, | |
| inBuf: `"called"`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/NestedFirst"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| JoinUnmarshalers( | |
| UnmarshalFunc(func(b []byte, v *string) error { | |
| if string(b) != `"called"` { | |
| return fmt.Errorf("got %s, want %s", b, `"called"`) | |
| } | |
| *v = "called" | |
| return nil | |
| }), | |
| ), | |
| UnmarshalFunc(func([]byte, *string) error { | |
| panic("should not be called") | |
| }), | |
| )), | |
| }, | |
| inBuf: `"called"`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Functions/Precedence/NestedLast"), | |
| opts: []Options{ | |
| WithUnmarshalers(JoinUnmarshalers( | |
| UnmarshalFunc(func(b []byte, v *string) error { | |
| if string(b) != `"called"` { | |
| return fmt.Errorf("got %s, want %s", b, `"called"`) | |
| } | |
| *v = "called" | |
| return nil | |
| }), | |
| JoinUnmarshalers( | |
| UnmarshalFunc(func([]byte, *string) error { | |
| panic("should not be called") | |
| }), | |
| ), | |
| )), | |
| }, | |
| inBuf: `"called"`, | |
| inVal: addr(""), | |
| want: addr("called"), | |
| }, { | |
| name: jsontest.Name("Duration/Null"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D1":null,"D2":null}`, | |
| inVal: addr(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{1, 1}), | |
| want: addr(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{0, 0}), | |
| }, { | |
| name: jsontest.Name("Duration/Zero"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D1":"0s","D2":0}`, | |
| inVal: addr(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{1, 1}), | |
| want: addr(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{0, 0}), | |
| }, { | |
| name: jsontest.Name("Duration/Positive"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D1":"34293h33m9.123456789s","D2":123456789123456789}`, | |
| inVal: new(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }), | |
| want: addr(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{ | |
| 123456789123456789, | |
| 123456789123456789, | |
| }), | |
| }, { | |
| name: jsontest.Name("Duration/Negative"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D1":"-34293h33m9.123456789s","D2":-123456789123456789}`, | |
| inVal: new(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }), | |
| want: addr(struct { | |
| D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| D2 time.Duration `json:",format:nano"` | |
| }{ | |
| -123456789123456789, | |
| -123456789123456789, | |
| }), | |
| }, { | |
| name: jsontest.Name("Duration/Nanos/String"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":"12345"}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",string,format:nano"` | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",string,format:nano"` | |
| }{12345}), | |
| }, { | |
| name: jsontest.Name("Duration/Nanos/String/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":"+12345"}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",string,format:nano"` | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",string,format:nano"` | |
| }{1}), | |
| wantErr: EU(fmt.Errorf(`invalid duration "+12345": %w`, strconv.ErrSyntax)).withPos(`{"D":`, "/D").withType('"', timeDurationType), | |
| }, { | |
| name: jsontest.Name("Duration/Nanos/Mismatch"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":"34293h33m9.123456789s"}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",format:nano"` | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",format:nano"` | |
| }{1}), | |
| wantErr: EU(nil).withPos(`{"D":`, "/D").withType('"', timeDurationType), | |
| }, { | |
| name: jsontest.Name("Duration/Nanos"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":1.324}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",format:nano"` | |
| }{-1}), | |
| want: addr(struct { | |
| D time.Duration `json:",format:nano"` | |
| }{1}), | |
| }, { | |
| name: jsontest.Name("Duration/String/Mismatch"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":-123456789123456789}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| }{1}), | |
| wantErr: EU(nil).withPos(`{"D":`, "/D").withType('0', timeDurationType), | |
| }, { | |
| name: jsontest.Name("Duration/String/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":"5minkutes"}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| }{1}), | |
| wantErr: EU(func() error { | |
| _, err := time.ParseDuration("5minkutes") | |
| return err | |
| }()).withPos(`{"D":`, "/D").withType('"', timeDurationType), | |
| }, { | |
| name: jsontest.Name("Duration/Syntax/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":x}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. | |
| }{1}), | |
| wantErr: newInvalidCharacterError("x", "at start of value", len64(`{"D":`), "/D"), | |
| }, { | |
| name: jsontest.Name("Duration/Format"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{ | |
| "D1": "12h34m56.078090012s", | |
| "D2": "12h34m56.078090012s", | |
| "D3": 45296.078090012, | |
| "D4": "45296.078090012", | |
| "D5": 45296078.090012, | |
| "D6": "45296078.090012", | |
| "D7": 45296078090.012, | |
| "D8": "45296078090.012", | |
| "D9": 45296078090012, | |
| "D10": "45296078090012", | |
| "D11": "PT12H34M56.078090012S" | |
| }`, | |
| inVal: new(structDurationFormat), | |
| want: addr(structDurationFormat{ | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| }), | |
| }, { | |
| name: jsontest.Name("Duration/Format/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"D":"0s"}`, | |
| inVal: addr(struct { | |
| D time.Duration `json:",format:invalid"` | |
| }{1}), | |
| want: addr(struct { | |
| D time.Duration `json:",format:invalid"` | |
| }{1}), | |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"D":`, "/D").withType(0, timeDurationType), | |
| }, { | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: jsontest.Name("Duration/Format/Legacy"), | |
| opts: []Options{jsonflags.FormatDurationAsNano | 1}, | |
| inBuf: `{"D1":45296078090012,"D2":"12h34m56.078090012s"}`, | |
| inVal: new(structDurationFormat), | |
| want: addr(structDurationFormat{ | |
| D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, | |
| }), | |
| }, { */ | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: jsontest.Name("Duration/MapKey"), | |
| inBuf: `{"1s":""}`, | |
| inVal: new(map[time.Duration]string), | |
| want: addr(map[time.Duration]string{time.Second: ""}), | |
| }, { */ | |
| name: jsontest.Name("Duration/MapKey/Legacy"), | |
| opts: []Options{jsonflags.FormatDurationAsNano | 1}, | |
| inBuf: `{"1000000000":""}`, | |
| inVal: new(map[time.Duration]string), | |
| want: addr(map[time.Duration]string{time.Second: ""}), | |
| }, { | |
| /* TODO(https://go.dev/issue/71631): Re-enable this test case. | |
| name: jsontest.Name("Duration/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `"1s"`, | |
| inVal: addr(time.Duration(0)), | |
| want: addr(time.Second), | |
| }, { */ | |
| name: jsontest.Name("Time/Zero"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"T1":"0001-01-01T00:00:00Z","T2":"01 Jan 01 00:00 UTC","T3":"0001-01-01","T4":"0001-01-01T00:00:00Z","T5":"0001-01-01T00:00:00Z"}`, | |
| inVal: new(struct { | |
| T1 time.Time | |
| T2 time.Time `json:",format:RFC822"` | |
| T3 time.Time `json:",format:'2006-01-02'"` | |
| T4 time.Time `json:",omitzero"` | |
| T5 time.Time `json:",omitempty"` | |
| }), | |
| want: addr(struct { | |
| T1 time.Time | |
| T2 time.Time `json:",format:RFC822"` | |
| T3 time.Time `json:",format:'2006-01-02'"` | |
| T4 time.Time `json:",omitzero"` | |
| T5 time.Time `json:",omitempty"` | |
| }{ | |
| mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"), | |
| mustParseTime(time.RFC822, "01 Jan 01 00:00 UTC"), | |
| mustParseTime("2006-01-02", "0001-01-01"), | |
| mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"), | |
| mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"), | |
| }), | |
| }, { | |
| name: jsontest.Name("Time/Format"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{ | |
| "T1": "1234-01-02T03:04:05.000000006Z", | |
| "T2": "Mon Jan 2 03:04:05 1234", | |
| "T3": "Mon Jan 2 03:04:05 UTC 1234", | |
| "T4": "Mon Jan 02 03:04:05 +0000 1234", | |
| "T5": "02 Jan 34 03:04 UTC", | |
| "T6": "02 Jan 34 03:04 +0000", | |
| "T7": "Monday, 02-Jan-34 03:04:05 UTC", | |
| "T8": "Mon, 02 Jan 1234 03:04:05 UTC", | |
| "T9": "Mon, 02 Jan 1234 03:04:05 +0000", | |
| "T10": "1234-01-02T03:04:05Z", | |
| "T11": "1234-01-02T03:04:05.000000006Z", | |
| "T12": "3:04AM", | |
| "T13": "Jan 2 03:04:05", | |
| "T14": "Jan 2 03:04:05.000", | |
| "T15": "Jan 2 03:04:05.000000", | |
| "T16": "Jan 2 03:04:05.000000006", | |
| "T17": "1234-01-02 03:04:05", | |
| "T18": "1234-01-02", | |
| "T19": "03:04:05", | |
| "T20": "1234-01-02", | |
| "T21": "\"weird\"1234", | |
| "T22": -23225777754.999999994, | |
| "T23": "-23225777754.999999994", | |
| "T24": -23225777754999.999994, | |
| "T25": "-23225777754999.999994", | |
| "T26": -23225777754999999.994, | |
| "T27": "-23225777754999999.994", | |
| "T28": -23225777754999999994, | |
| "T29": "-23225777754999999994" | |
| }`, | |
| inVal: new(structTimeFormat), | |
| want: addr(structTimeFormat{ | |
| mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), | |
| mustParseTime(time.ANSIC, "Mon Jan 2 03:04:05 1234"), | |
| mustParseTime(time.UnixDate, "Mon Jan 2 03:04:05 UTC 1234"), | |
| mustParseTime(time.RubyDate, "Mon Jan 02 03:04:05 +0000 1234"), | |
| mustParseTime(time.RFC822, "02 Jan 34 03:04 UTC"), | |
| mustParseTime(time.RFC822Z, "02 Jan 34 03:04 +0000"), | |
| mustParseTime(time.RFC850, "Monday, 02-Jan-34 03:04:05 UTC"), | |
| mustParseTime(time.RFC1123, "Mon, 02 Jan 1234 03:04:05 UTC"), | |
| mustParseTime(time.RFC1123Z, "Mon, 02 Jan 1234 03:04:05 +0000"), | |
| mustParseTime(time.RFC3339, "1234-01-02T03:04:05Z"), | |
| mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), | |
| mustParseTime(time.Kitchen, "3:04AM"), | |
| mustParseTime(time.Stamp, "Jan 2 03:04:05"), | |
| mustParseTime(time.StampMilli, "Jan 2 03:04:05.000"), | |
| mustParseTime(time.StampMicro, "Jan 2 03:04:05.000000"), | |
| mustParseTime(time.StampNano, "Jan 2 03:04:05.000000006"), | |
| mustParseTime(time.DateTime, "1234-01-02 03:04:05"), | |
| mustParseTime(time.DateOnly, "1234-01-02"), | |
| mustParseTime(time.TimeOnly, "03:04:05"), | |
| mustParseTime("2006-01-02", "1234-01-02"), | |
| mustParseTime(`\"weird\"2006`, `\"weird\"1234`), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| }), | |
| }, { | |
| name: jsontest.Name("Time/Format/String/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"T":"2023-01-01T00:00:00Z"}`, | |
| inVal: new(structTimeFormatStringInvalid), | |
| want: new(structTimeFormatStringInvalid), | |
| wantErr: EU(errInvalidStringTag).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/UnixString/InvalidNumber"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{ | |
| "T23": -23225777754.999999994, | |
| "T25": -23225777754999.999994, | |
| "T27": -23225777754999999.994, | |
| "T29": -23225777754999999994 | |
| }`, | |
| inVal: new(structTimeFormat), | |
| want: new(structTimeFormat), | |
| wantErr: EU(nil).withPos(`{`+"\n\t\t\t"+`"T23": `, "/T23").withType('0', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/UnixString/InvalidString"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{ | |
| "T22": "-23225777754.999999994", | |
| "T24": "-23225777754999.999994", | |
| "T26": "-23225777754999999.994", | |
| "T28": "-23225777754999999994" | |
| }`, | |
| inVal: new(structTimeFormat), | |
| want: new(structTimeFormat), | |
| wantErr: EU(nil).withPos(`{`+"\n\t\t\t"+`"T22": `, "/T22").withType('"', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/Null"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"T1":null,"T2":null,"T3":null,"T4":null,"T5":null,"T6":null,"T7":null,"T8":null,"T9":null,"T10":null,"T11":null,"T12":null,"T13":null,"T14":null,"T15":null,"T16":null,"T17":null,"T18":null,"T19":null,"T20":null,"T21":null,"T22":null,"T23":null,"T24":null,"T25":null,"T26":null,"T27":null,"T28":null,"T29":null}`, | |
| inVal: addr(structTimeFormat{ | |
| mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), | |
| mustParseTime(time.ANSIC, "Mon Jan 2 03:04:05 1234"), | |
| mustParseTime(time.UnixDate, "Mon Jan 2 03:04:05 UTC 1234"), | |
| mustParseTime(time.RubyDate, "Mon Jan 02 03:04:05 +0000 1234"), | |
| mustParseTime(time.RFC822, "02 Jan 34 03:04 UTC"), | |
| mustParseTime(time.RFC822Z, "02 Jan 34 03:04 +0000"), | |
| mustParseTime(time.RFC850, "Monday, 02-Jan-34 03:04:05 UTC"), | |
| mustParseTime(time.RFC1123, "Mon, 02 Jan 1234 03:04:05 UTC"), | |
| mustParseTime(time.RFC1123Z, "Mon, 02 Jan 1234 03:04:05 +0000"), | |
| mustParseTime(time.RFC3339, "1234-01-02T03:04:05Z"), | |
| mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), | |
| mustParseTime(time.Kitchen, "3:04AM"), | |
| mustParseTime(time.Stamp, "Jan 2 03:04:05"), | |
| mustParseTime(time.StampMilli, "Jan 2 03:04:05.000"), | |
| mustParseTime(time.StampMicro, "Jan 2 03:04:05.000000"), | |
| mustParseTime(time.StampNano, "Jan 2 03:04:05.000000006"), | |
| mustParseTime(time.DateTime, "1234-01-02 03:04:05"), | |
| mustParseTime(time.DateOnly, "1234-01-02"), | |
| mustParseTime(time.TimeOnly, "03:04:05"), | |
| mustParseTime("2006-01-02", "1234-01-02"), | |
| mustParseTime(`\"weird\"2006`, `\"weird\"1234`), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| time.Unix(-23225777755, 6).UTC(), | |
| }), | |
| want: new(structTimeFormat), | |
| }, { | |
| name: jsontest.Name("Time/RFC3339/Mismatch"), | |
| inBuf: `{"T":1234}`, | |
| inVal: new(struct { | |
| T time.Time | |
| }), | |
| wantErr: EU(nil).withPos(`{"T":`, "/T").withType('0', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/RFC3339/ParseError"), | |
| inBuf: `{"T":"2021-09-29T12:44:52"}`, | |
| inVal: new(struct { | |
| T time.Time | |
| }), | |
| wantErr: EU(func() error { | |
| _, err := time.Parse(time.RFC3339, "2021-09-29T12:44:52") | |
| return err | |
| }()).withPos(`{"T":`, "/T").withType('"', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/Invalid"), | |
| opts: []Options{jsonopts.ExperimentalSupportFormatTag(true)}, | |
| inBuf: `{"T":""}`, | |
| inVal: new(struct { | |
| T time.Time `json:",format:UndefinedConstant"` | |
| }), | |
| wantErr: EU(errors.New(`invalid format flag "UndefinedConstant"`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/SingleDigitHour"), | |
| inBuf: `{"T":"2000-01-01T1:12:34Z"}`, | |
| inVal: new(struct{ T time.Time }), | |
| wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T1:12:34Z", "15", "1", "")).withPos(`{"T":`, "/T").withType('"', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/SubsecondComma"), | |
| inBuf: `{"T":"2000-01-01T00:00:00,000Z"}`, | |
| inVal: new(struct{ T time.Time }), | |
| wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T00:00:00,000Z", ".", ",", "")).withPos(`{"T":`, "/T").withType('"', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/TimezoneHourOverflow"), | |
| inBuf: `{"T":"2000-01-01T00:00:00+24:00"}`, | |
| inVal: new(struct{ T time.Time }), | |
| wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T00:00:00+24:00", "Z07:00", "+24:00", ": timezone hour out of range")).withPos(`{"T":`, "/T").withType('"', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Format/TimezoneMinuteOverflow"), | |
| inBuf: `{"T":"2000-01-01T00:00:00+00:60"}`, | |
| inVal: new(struct{ T time.Time }), | |
| wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T00:00:00+00:60", "Z07:00", "+00:60", ": timezone minute out of range")).withPos(`{"T":`, "/T").withType('"', timeTimeType), | |
| }, { | |
| name: jsontest.Name("Time/Syntax/Invalid"), | |
| inBuf: `{"T":x}`, | |
| inVal: new(struct { | |
| T time.Time | |
| }), | |
| wantErr: newInvalidCharacterError("x", "at start of value", len64(`{"T":`), "/T"), | |
| }, { | |
| name: jsontest.Name("Time/IgnoreInvalidFormat"), | |
| opts: []Options{invalidFormatOption}, | |
| inBuf: `"2000-01-01T00:00:00Z"`, | |
| inVal: addr(time.Time{}), | |
| want: addr(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)), | |
| }} | |
| for _, tt := range tests { | |
| if tt.skip { | |
| continue | |
| } | |
| t.Run(tt.name.Name, func(t *testing.T) { | |
| got := tt.inVal | |
| gotErr := Unmarshal([]byte(tt.inBuf), got, tt.opts...) | |
| if !reflect.DeepEqual(got, tt.want) && tt.want != nil { | |
| t.Errorf("%s: Unmarshal output mismatch:\ngot %v\nwant %v", tt.name.Where, got, tt.want) | |
| } | |
| if !reflect.DeepEqual(gotErr, tt.wantErr) { | |
| t.Errorf("%s: Unmarshal error mismatch:\ngot %v\nwant %v", tt.name.Where, gotErr, tt.wantErr) | |
| } | |
| }) | |
| } | |
| } | |
| func TestMarshalInvalidNamespace(t *testing.T) { | |
| tests := []struct { | |
| name jsontest.CaseName | |
| val any | |
| }{ | |
| {jsontest.Name("Map"), map[string]string{"X": "\xde\xad\xbe\xef"}}, | |
| {jsontest.Name("Struct"), struct{ X string }{"\xde\xad\xbe\xef"}}, | |
| {jsontest.Name("MapBadKey"), map[string]int{"\xde\xad\xbe\xef": 0}}, | |
| } | |
| for _, tt := range tests { | |
| t.Run(tt.name.Name, func(t *testing.T) { | |
| enc := jsontext.NewEncoder(new(bytes.Buffer)) | |
| if err := MarshalEncode(enc, tt.val); err == nil { | |
| t.Fatalf("%s: MarshalEncode error is nil, want non-nil", tt.name.Where) | |
| } | |
| for _, tok := range []jsontext.Token{ | |
| jsontext.Null, jsontext.String(""), jsontext.Int(0), jsontext.BeginObject, jsontext.EndObject, jsontext.BeginArray, jsontext.EndArray, | |
| } { | |
| if err := enc.WriteToken(tok); err == nil { | |
| t.Fatalf("%s: WriteToken error is nil, want non-nil", tt.name.Where) | |
| } | |
| } | |
| for _, val := range []string{`null`, `""`, `0`, `{}`, `[]`} { | |
| if err := enc.WriteValue([]byte(val)); err == nil { | |
| t.Fatalf("%s: WriteToken error is nil, want non-nil", tt.name.Where) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| // TestMarshalEncodeInvalidNamespaceAtName verifies that MarshalEncode at an | |
| // object-name position with an invalidated namespace surfaces the underlying | |
| // namespace error rather than wrapping nil. | |
| func TestMarshalEncodeInvalidNamespaceAtName(t *testing.T) { | |
| enc := jsontext.NewEncoder(new(bytes.Buffer)) | |
| // The bad-UTF-8 key fails the map marshal at the key-write step, | |
| // leaving the encoder at an object-name position with the namespace | |
| // invalidated. | |
| if err := MarshalEncode(enc, map[string]int{"\xde\xad\xbe\xef": 0}); err == nil { | |
| t.Fatal("MarshalEncode error is nil, want non-nil") | |
| } | |
| var serr *jsontext.SyntacticError | |
| if err := MarshalEncode(enc, 0); !errors.As(err, &serr) || serr.Err == nil { | |
| t.Fatalf("MarshalEncode error = %v, want *jsontext.SyntacticError wrapping a non-nil error", err) | |
| } | |
| } | |
| func TestUnmarshalInvalidNamespace(t *testing.T) { | |
| tests := []struct { | |
| name jsontest.CaseName | |
| val any | |
| }{ | |
| {jsontest.Name("Map"), addr(map[string]int{})}, | |
| {jsontest.Name("Struct"), addr(struct{ X int }{})}, | |
| } | |
| for _, tt := range tests { | |
| t.Run(tt.name.Name, func(t *testing.T) { | |
| dec := jsontext.NewDecoder(strings.NewReader(`{"X":""}`)) | |
| if err := UnmarshalDecode(dec, tt.val); err == nil { | |
| t.Fatalf("%s: UnmarshalDecode error is nil, want non-nil", tt.name.Where) | |
| } | |
| if _, err := dec.ReadToken(); err == nil { | |
| t.Fatalf("%s: ReadToken error is nil, want non-nil", tt.name.Where) | |
| } | |
| if _, err := dec.ReadValue(); err == nil { | |
| t.Fatalf("%s: ReadValue error is nil, want non-nil", tt.name.Where) | |
| } | |
| }) | |
| } | |
| } | |
| func TestUnmarshalReuse(t *testing.T) { | |
| t.Run("Bytes", func(t *testing.T) { | |
| in := make([]byte, 3) | |
| want := &in[0] | |
| if err := Unmarshal([]byte(`"AQID"`), &in); err != nil { | |
| t.Fatalf("Unmarshal error: %v", err) | |
| } | |
| got := &in[0] | |
| if got != want { | |
| t.Errorf("input buffer was not reused") | |
| } | |
| }) | |
| t.Run("Slices", func(t *testing.T) { | |
| in := make([]int, 3) | |
| want := &in[0] | |
| if err := Unmarshal([]byte(`[0,1,2]`), &in); err != nil { | |
| t.Fatalf("Unmarshal error: %v", err) | |
| } | |
| got := &in[0] | |
| if got != want { | |
| t.Errorf("input slice was not reused") | |
| } | |
| }) | |
| t.Run("Maps", func(t *testing.T) { | |
| in := make(map[string]string) | |
| want := reflect.ValueOf(in).Pointer() | |
| if err := Unmarshal([]byte(`{"key":"value"}`), &in); err != nil { | |
| t.Fatalf("Unmarshal error: %v", err) | |
| } | |
| got := reflect.ValueOf(in).Pointer() | |
| if got != want { | |
| t.Errorf("input map was not reused") | |
| } | |
| }) | |
| t.Run("Pointers", func(t *testing.T) { | |
| in := addr(addr(addr("hello"))) | |
| want := **in | |
| if err := Unmarshal([]byte(`"goodbye"`), &in); err != nil { | |
| t.Fatalf("Unmarshal error: %v", err) | |
| } | |
| got := **in | |
| if got != want { | |
| t.Errorf("input pointer was not reused") | |
| } | |
| }) | |
| } | |
| type unmarshalerEOF struct{} | |
| func (unmarshalerEOF) UnmarshalJSONFrom(dec *jsontext.Decoder) error { | |
| return io.EOF // should be wrapped and converted by Unmarshal to io.ErrUnexpectedEOF | |
| } | |
| // TestUnmarshalEOF verifies that io.EOF is only ever returned by | |
| // UnmarshalDecode for a top-level value. | |
| func TestUnmarshalEOF(t *testing.T) { | |
| opts := WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, _ *struct{}) error { | |
| return io.EOF // should be wrapped and converted by Unmarshal to io.ErrUnexpectedEOF | |
| })) | |
| for _, in := range []string{"", "[", "[null", "[null]"} { | |
| for _, newOut := range []func() any{ | |
| func() any { return new(unmarshalerEOF) }, | |
| func() any { return new([]unmarshalerEOF) }, | |
| func() any { return new(struct{}) }, | |
| func() any { return new([]struct{}) }, | |
| } { | |
| wantErr := io.ErrUnexpectedEOF | |
| if gotErr := Unmarshal([]byte(in), newOut(), opts); !errors.Is(gotErr, wantErr) { | |
| t.Errorf("Unmarshal = %v, want %v", gotErr, wantErr) | |
| } | |
| if gotErr := UnmarshalRead(strings.NewReader(in), newOut(), opts); !errors.Is(gotErr, wantErr) { | |
| t.Errorf("Unmarshal = %v, want %v", gotErr, wantErr) | |
| } | |
| switch gotErr := UnmarshalDecode(jsontext.NewDecoder(strings.NewReader(in)), newOut(), opts); { | |
| case in != "" && !errors.Is(gotErr, wantErr): | |
| t.Errorf("Unmarshal = %v, want %v", gotErr, wantErr) | |
| case in == "" && gotErr != io.EOF: | |
| t.Errorf("Unmarshal = %v, want %v", gotErr, io.EOF) | |
| } | |
| } | |
| } | |
| } | |
| type ReaderFunc func([]byte) (int, error) | |
| func (f ReaderFunc) Read(b []byte) (int, error) { return f(b) } | |
| type WriterFunc func([]byte) (int, error) | |
| func (f WriterFunc) Write(b []byte) (int, error) { return f(b) } | |
| func TestCoderBufferGrowth(t *testing.T) { | |
| // The growth rate of the internal buffer should be exponential, | |
| // but should not grow unbounded. | |
| checkGrowth := func(ns []int) { | |
| t.Helper() | |
| var sumBytes, sumRates, numGrows float64 | |
| prev := ns[0] | |
| for i := 1; i < len(ns)-1; i++ { | |
| n := ns[i] | |
| if n != prev { | |
| sumRates += float64(n) / float64(prev) | |
| numGrows++ | |
| prev = n | |
| } | |
| if n > 1<<20 { | |
| t.Fatalf("single Read/Write too large: %d", n) | |
| } | |
| sumBytes += float64(n) | |
| } | |
| if mean := sumBytes / float64(len(ns)); mean < 1<<10 { | |
| t.Fatalf("average Read/Write too small: %0.1f", mean) | |
| } | |
| switch mean := sumRates / numGrows; { | |
| case mean < 1.25: | |
| t.Fatalf("average growth rate too slow: %0.3f", mean) | |
| case mean > 2.00: | |
| t.Fatalf("average growth rate too fast: %0.3f", mean) | |
| } | |
| } | |
| // bb is identical to bytes.Buffer, | |
| // but a different type to avoid any optimizations for bytes.Buffer. | |
| bb := struct{ *bytes.Buffer }{new(bytes.Buffer)} | |
| var writeSizes []int | |
| if err := MarshalWrite(WriterFunc(func(b []byte) (int, error) { | |
| n, err := bb.Write(b) | |
| writeSizes = append(writeSizes, n) | |
| return n, err | |
| }), make([]struct{}, 1e6)); err != nil { | |
| t.Fatalf("MarshalWrite error: %v", err) | |
| } | |
| checkGrowth(writeSizes) | |
| var readSizes []int | |
| if err := UnmarshalRead(ReaderFunc(func(b []byte) (int, error) { | |
| n, err := bb.Read(b) | |
| readSizes = append(readSizes, n) | |
| return n, err | |
| }), new([]struct{})); err != nil { | |
| t.Fatalf("UnmarshalRead error: %v", err) | |
| } | |
| checkGrowth(readSizes) | |
| } | |
| func TestUintSet(t *testing.T) { | |
| type operation any // has | insert | |
| type has struct { | |
| in uint | |
| want bool | |
| } | |
| type insert struct { | |
| in uint | |
| want bool | |
| } | |
| // Sequence of operations to perform (order matters). | |
| ops := []operation{ | |
| has{0, false}, | |
| has{63, false}, | |
| has{64, false}, | |
| has{1234, false}, | |
| insert{3, true}, | |
| has{2, false}, | |
| has{3, true}, | |
| has{4, false}, | |
| has{63, false}, | |
| insert{3, false}, | |
| insert{63, true}, | |
| has{63, true}, | |
| insert{64, true}, | |
| insert{64, false}, | |
| has{64, true}, | |
| insert{3264, true}, | |
| has{3264, true}, | |
| insert{3, false}, | |
| has{3, true}, | |
| } | |
| var us uintSet | |
| for i, op := range ops { | |
| switch op := op.(type) { | |
| case has: | |
| if got := us.has(op.in); got != op.want { | |
| t.Fatalf("%d: uintSet.has(%v) = %v, want %v", i, op.in, got, op.want) | |
| } | |
| case insert: | |
| if got := us.insert(op.in); got != op.want { | |
| t.Fatalf("%d: uintSet.insert(%v) = %v, want %v", i, op.in, got, op.want) | |
| } | |
| default: | |
| panic(fmt.Sprintf("unknown operation: %T", op)) | |
| } | |
| } | |
| } | |
| func TestUnmarshalDecodeOptions(t *testing.T) { | |
| var calledFuncs int | |
| var calledOptions Options | |
| in := strings.NewReader(strings.Repeat("\"\xde\xad\xbe\xef\"\n", 5)) | |
| dec := jsontext.NewDecoder(in, | |
| jsontext.AllowInvalidUTF8(true), // decoder-specific option | |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, _ any) error { | |
| opts := dec.Options() | |
| if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { | |
| t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") | |
| } | |
| calledFuncs++ | |
| calledOptions = opts | |
| return errors.ErrUnsupported | |
| })), // unmarshal-specific option; only relevant for UnmarshalDecode | |
| ) | |
| if err := UnmarshalDecode(dec, new(string)); err != nil { | |
| t.Fatalf("UnmarshalDecode: %v", err) | |
| } | |
| if calledFuncs != 1 { | |
| t.Fatalf("calledFuncs = %d, want 1", calledFuncs) | |
| } | |
| if err := UnmarshalDecode(dec, new(string), calledOptions); err != nil { | |
| t.Fatalf("UnmarshalDecode: %v", err) | |
| } | |
| if calledFuncs != 2 { | |
| t.Fatalf("calledFuncs = %d, want 2", calledFuncs) | |
| } | |
| if err := UnmarshalDecode(dec, new(string), | |
| jsontext.AllowInvalidUTF8(false), // expect to see invalid UTF-8 error | |
| WithUnmarshalers(nil), // avoid calling previously registered unmarshalers | |
| ); !errors.Is(err, jsonwire.ErrInvalidUTF8) { | |
| t.Fatalf("UnmarshalDecode = %v, want %v", err, jsonwire.ErrInvalidUTF8) | |
| } | |
| if err := UnmarshalDecode(dec, new(string), | |
| WithUnmarshalers(nil), | |
| ); err != nil { | |
| t.Fatalf("UnmarshalDecode: %v", err) | |
| } | |
| if calledFuncs != 2 { | |
| t.Fatalf("calledFuncs = %d, want 2", calledFuncs) | |
| } | |
| if err := UnmarshalDecode(dec, new(string)); err != nil { | |
| t.Fatalf("UnmarshalDecode: %v", err) | |
| } | |
| if calledFuncs != 3 { | |
| t.Fatalf("calledFuncs = %d, want 3", calledFuncs) | |
| } | |
| if err := UnmarshalDecode(dec, new(string), JoinOptions( | |
| WithUnmarshalers(UnmarshalFromFunc(func(_ *jsontext.Decoder, _ any) error { | |
| opts := dec.Options() | |
| if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { | |
| t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") | |
| } | |
| calledFuncs = math.MaxInt | |
| return errors.ErrUnsupported | |
| })), // should override | |
| )); err != nil { | |
| t.Fatalf("UnmarshalDecode: %v", err) | |
| } | |
| if calledFuncs != math.MaxInt { | |
| t.Fatalf("calledFuncs = %d, want %d", calledFuncs, math.MaxInt) | |
| } | |
| // Reset with the decoder options as part of the arguments should not | |
| // observe mutations to the options until after Reset is done. | |
| opts := dec.Options() // AllowInvalidUTF8 is currently true | |
| dec.Reset(in, jsontext.AllowInvalidUTF8(false), opts) // earlier AllowInvalidUTF8(false) should be overridden by latter AllowInvalidUTF8(true) in opts | |
| if v, _ := GetOption(dec.Options(), jsontext.AllowInvalidUTF8); v == false { | |
| t.Errorf("Options.AllowInvalidUTF8 = false, want true") | |
| } | |
| // Verify that AllowInvalidUTF8 and AllowDuplicateNames cannot be changed | |
| // when positioned at a JSON object name, but can be changed for the value. | |
| { | |
| dec2 := jsontext.NewDecoder(strings.NewReader(`{"name":{"dupe":"value","dupe":"value"}}`)) | |
| if tok, err := dec2.ReadToken(); tok.Kind() != '{' || err != nil { | |
| t.Fatalf("ReadToken = (%v, %v), want ('{', nil)", tok.Kind(), err) | |
| } | |
| var name string | |
| if err := UnmarshalDecode(dec2, &name, jsontext.AllowDuplicateNames(true)); !errors.Is(err, errChangingDuplicateNames) { | |
| t.Errorf("UnmarshalDecode(name) = %v, want %v", err, errChangingDuplicateNames) | |
| } | |
| if err := UnmarshalDecode(dec2, &name, jsontext.AllowInvalidUTF8(true)); !errors.Is(err, errChangingInvalidUTF8) { | |
| t.Errorf("UnmarshalDecode(name) = %v, want %v", err, errChangingInvalidUTF8) | |
| } | |
| // Setting the same option value does not report an error. | |
| if err := UnmarshalDecode(dec2, &name, jsontext.AllowDuplicateNames(false)); err != nil { | |
| t.Errorf("UnmarshalDecode(name) = %v, want nil", err) | |
| } else if name != "name" { | |
| t.Errorf("UnmarshalDecode(name) = %q, want %q", name, "name") | |
| } | |
| // At value position, changing AllowDuplicateNames is allowed. | |
| var value jsontext.Value | |
| if err := UnmarshalDecode(dec2, &value, jsontext.AllowDuplicateNames(true)); err != nil { | |
| t.Errorf("UnmarshalDecode(value) = %v, want nil", err) | |
| } | |
| const want = `{"dupe":"value","dupe":"value"}` | |
| if string(value) != string(want) { | |
| t.Errorf("UnmarshalDecode(value) = %s, want %s", value, want) | |
| } | |
| } | |
| } | |
| func TestUnmarshalDecodeStream(t *testing.T) { | |
| tests := []struct { | |
| in string | |
| want []any | |
| err error | |
| }{ | |
| {in: ``, err: io.EOF}, | |
| {in: `{`, err: &jsontext.SyntacticError{ByteOffset: len64(`{`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `{"`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `{"k"`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k"`), JSONPointer: "/k", Err: io.ErrUnexpectedEOF}}, | |
| {in: `{"k":`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k":`), JSONPointer: "/k", Err: io.ErrUnexpectedEOF}}, | |
| {in: `{"k",`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k"`), JSONPointer: "/k", Err: jsonwire.NewInvalidCharacterError(",", "after object name (expecting ':')")}}, | |
| {in: `{"k"}`, err: &jsontext.SyntacticError{ByteOffset: len64(`{"k"`), JSONPointer: "/k", Err: jsonwire.NewInvalidCharacterError("}", "after object name (expecting ':')")}}, | |
| {in: `[`, err: &jsontext.SyntacticError{ByteOffset: len64(`[`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `[0`, err: &jsontext.SyntacticError{ByteOffset: len64(`[0`), Err: io.ErrUnexpectedEOF}}, | |
| {in: ` [0`, err: &jsontext.SyntacticError{ByteOffset: len64(` [0`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `[0.`, err: &jsontext.SyntacticError{ByteOffset: len64(`[`), JSONPointer: "/0", Err: io.ErrUnexpectedEOF}}, | |
| {in: `[0. `, err: &jsontext.SyntacticError{ByteOffset: len64(`[0.`), JSONPointer: "/0", Err: jsonwire.NewInvalidCharacterError(" ", "in number (expecting digit)")}}, | |
| {in: `[0,`, err: &jsontext.SyntacticError{ByteOffset: len64(`[0,`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `[0:`, err: &jsontext.SyntacticError{ByteOffset: len64(`[0`), Err: jsonwire.NewInvalidCharacterError(":", "after array element (expecting ',' or ']')")}}, | |
| {in: `n`, err: &jsontext.SyntacticError{ByteOffset: len64(`n`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `nul`, err: &jsontext.SyntacticError{ByteOffset: len64(`nul`), Err: io.ErrUnexpectedEOF}}, | |
| {in: `fal `, err: &jsontext.SyntacticError{ByteOffset: len64(`fal`), Err: jsonwire.NewInvalidCharacterError(" ", "in literal false (expecting 's')")}}, | |
| {in: `false`, want: []any{false}, err: io.EOF}, | |
| {in: `false0.0[]null`, want: []any{false, 0.0, []any{}, nil}, err: io.EOF}, | |
| } | |
| for _, tt := range tests { | |
| d := jsontext.NewDecoder(strings.NewReader(tt.in)) | |
| var got []any | |
| for { | |
| var v any | |
| if err := UnmarshalDecode(d, &v); err != nil { | |
| if !reflect.DeepEqual(err, tt.err) { | |
| t.Errorf("`%s`: UnmarshalDecode error = %v, want %v", tt.in, err, tt.err) | |
| } | |
| break | |
| } | |
| got = append(got, v) | |
| } | |
| if !reflect.DeepEqual(got, tt.want) { | |
| t.Errorf("`%s`: UnmarshalDecode = %v, want %v", tt.in, got, tt.want) | |
| } | |
| } | |
| } | |
| // BenchmarkUnmarshalDecodeOptions is a minimal decode operation to measure | |
| // the overhead options setup before the unmarshal operation. | |
| func BenchmarkUnmarshalDecodeOptions(b *testing.B) { | |
| var i int | |
| in := new(bytes.Buffer) | |
| dec := jsontext.NewDecoder(in) | |
| makeBench := func(opts ...Options) func(*testing.B) { | |
| return func(b *testing.B) { | |
| for range b.N { | |
| in.WriteString("0 ") | |
| } | |
| dec.Reset(in) | |
| b.ResetTimer() | |
| for range b.N { | |
| UnmarshalDecode(dec, &i, opts...) | |
| } | |
| } | |
| } | |
| b.Run("None", makeBench()) | |
| b.Run("Same", makeBench(&export.Decoder(dec).Struct)) | |
| b.Run("New", makeBench(DefaultOptionsV2())) | |
| } | |
| func TestMarshalEncodeOptions(t *testing.T) { | |
| var calledFuncs int | |
| var calledOptions Options | |
| out := new(bytes.Buffer) | |
| enc := jsontext.NewEncoder( | |
| out, | |
| jsontext.AllowInvalidUTF8(true), // encoder-specific option | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, _ any) error { | |
| opts := enc.Options() | |
| if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { | |
| t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") | |
| } | |
| calledFuncs++ | |
| calledOptions = opts | |
| return errors.ErrUnsupported | |
| })), // marshal-specific option; only relevant for MarshalEncode | |
| ) | |
| if err := MarshalEncode(enc, "\xde\xad\xbe\xef"); err != nil { | |
| t.Fatalf("MarshalEncode: %v", err) | |
| } | |
| if calledFuncs != 1 { | |
| t.Fatalf("calledFuncs = %d, want 1", calledFuncs) | |
| } | |
| if err := MarshalEncode(enc, "\xde\xad\xbe\xef", calledOptions); err != nil { | |
| t.Fatalf("MarshalEncode: %v", err) | |
| } | |
| if calledFuncs != 2 { | |
| t.Fatalf("calledFuncs = %d, want 2", calledFuncs) | |
| } | |
| if err := MarshalEncode(enc, "\xde\xad\xbe\xef", | |
| jsontext.AllowInvalidUTF8(false), // expect to see invalid UTF-8 error | |
| WithMarshalers(nil), // avoid calling previously registered marshalers | |
| ); !errors.Is(err, jsonwire.ErrInvalidUTF8) { | |
| t.Fatalf("MarshalEncode = %v, want %v", err, jsonwire.ErrInvalidUTF8) | |
| } | |
| if err := MarshalEncode(enc, "\xde\xad\xbe\xef", | |
| WithMarshalers(nil), // should override | |
| ); err != nil { | |
| t.Fatalf("MarshalEncode: %v", err) | |
| } | |
| if calledFuncs != 2 { | |
| t.Fatalf("calledFuncs = %d, want 2", calledFuncs) | |
| } | |
| if err := MarshalEncode(enc, "\xde\xad\xbe\xef"); err != nil { | |
| t.Fatalf("MarshalEncode: %v", err) | |
| } | |
| if calledFuncs != 3 { | |
| t.Fatalf("calledFuncs = %d, want 3", calledFuncs) | |
| } | |
| if err := MarshalEncode(enc, "\xde\xad\xbe\xef", JoinOptions( | |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, _ any) error { | |
| opts := enc.Options() | |
| if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { | |
| t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") | |
| } | |
| calledFuncs = math.MaxInt | |
| return errors.ErrUnsupported | |
| })), // should override | |
| )); err != nil { | |
| t.Fatalf("MarshalEncode: %v", err) | |
| } | |
| if calledFuncs != math.MaxInt { | |
| t.Fatalf("calledFuncs = %d, want %d", calledFuncs, math.MaxInt) | |
| } | |
| if out.String() != strings.Repeat("\"\xde\xad\ufffd\ufffd\"\n", 5) { | |
| t.Fatalf("output mismatch:\n\tgot: %s\n\twant: %s", out.String(), strings.Repeat("\"\xde\xad\xbe\xef\"\n", 5)) | |
| } | |
| // Reset with the encoder options as part of the arguments should not | |
| // observe mutations to the options until after Reset is done. | |
| opts := enc.Options() // AllowInvalidUTF8 is currently true | |
| enc.Reset(out, jsontext.AllowInvalidUTF8(false), opts) // earlier AllowInvalidUTF8(false) should be overridden by latter AllowInvalidUTF8(true) in opts | |
| if v, _ := GetOption(enc.Options(), jsontext.AllowInvalidUTF8); v == false { | |
| t.Errorf("Options.AllowInvalidUTF8 = false, want true") | |
| } | |
| // Verify that AllowInvalidUTF8 and AllowDuplicateNames cannot be changed | |
| // when positioned at a JSON object name, but can be changed for the value. | |
| { | |
| var buf2 bytes.Buffer | |
| enc2 := jsontext.NewEncoder(&buf2) | |
| if err := enc2.WriteToken(jsontext.BeginObject); err != nil { | |
| t.Fatalf("WriteToken(BeginObject) = %v, want nil", err) | |
| } | |
| // Verify that you cannot change AllowDuplicateNames or AllowInvalidUTF8 settings for the JSON member name. | |
| if err := MarshalEncode(enc2, "name", jsontext.AllowDuplicateNames(true)); !errors.Is(err, errChangingDuplicateNames) { | |
| t.Errorf("MarshalEncode(name) = %v, want %v", err, errChangingDuplicateNames) | |
| } | |
| if err := MarshalEncode(enc2, "name", jsontext.AllowInvalidUTF8(true)); !errors.Is(err, errChangingInvalidUTF8) { | |
| t.Errorf("MarshalEncode(name) = %v, want %v", err, errChangingInvalidUTF8) | |
| } | |
| // Setting the same option value does not report an error. | |
| if err := MarshalEncode(enc2, "name", jsontext.AllowDuplicateNames(false)); err != nil { | |
| t.Errorf("MarshalEncode(name) = %v, want nil", err) | |
| } | |
| // At value position, changing AllowDuplicateNames is allowed. | |
| if err := MarshalEncode(enc2, jsontext.Value(`{"dupe":"value","dupe":"value"}`), jsontext.AllowDuplicateNames(true)); err != nil { | |
| t.Errorf("MarshalEncode(value) = %v, want nil", err) | |
| } | |
| // Verify that you can change AllowInvalidUTF8 for the JSON member value. | |
| if err := MarshalEncode(enc2, "name2"); err != nil { | |
| t.Errorf("MarshalEncode(name) = %v, want nil", err) | |
| } | |
| if err := MarshalEncode(enc2, "value\xde\xad\xbe\xef"); !errors.Is(err, jsonwire.ErrInvalidUTF8) { | |
| t.Errorf("MarshalEncode(value) = %v, want %v", err, jsonwire.ErrInvalidUTF8) | |
| } | |
| if err := MarshalEncode(enc2, "value\xde\xad\xbe\xef", jsontext.AllowInvalidUTF8(true)); err != nil { | |
| t.Errorf("MarshalEncode(value) = %v, want nil", err) | |
| } | |
| if err := enc2.WriteToken(jsontext.EndObject); err != nil { | |
| t.Errorf("WriteToken(EndObject) = %v, want nil", err) | |
| } | |
| } | |
| // Verify that whitespace options cannot change within a MarshalEncode call, | |
| // but setting them to identical values is allowed. | |
| { | |
| var buf3 bytes.Buffer | |
| enc3 := jsontext.NewEncoder(&buf3, jsontext.WithIndent("\t")) | |
| if err := MarshalEncode(enc3, "value", jsontext.Multiline(false)); !errors.Is(err, errChangingWhitespace) { | |
| t.Errorf("MarshalEncode = %v, want %v", err, errChangingWhitespace) | |
| } | |
| if err := MarshalEncode(enc3, "value", jsontext.WithIndent(" ")); !errors.Is(err, errChangingWhitespace) { | |
| t.Errorf("MarshalEncode = %v, want %v", err, errChangingWhitespace) | |
| } | |
| // Setting identical whitespace options does not report an error. | |
| if err := MarshalEncode(enc3, "value", enc3.Options()); err != nil { | |
| t.Errorf("MarshalEncode = %v, want nil", err) | |
| } | |
| } | |
| } | |
| // BenchmarkMarshalEncodeOptions is a minimal encode operation to measure | |
| // the overhead of options setup before the marshal operation. | |
| func BenchmarkMarshalEncodeOptions(b *testing.B) { | |
| var i int | |
| out := new(bytes.Buffer) | |
| enc := jsontext.NewEncoder(out) | |
| makeBench := func(opts ...Options) func(*testing.B) { | |
| return func(b *testing.B) { | |
| out.Reset() | |
| enc.Reset(out) | |
| b.ResetTimer() | |
| for range b.N { | |
| MarshalEncode(enc, &i, opts...) | |
| } | |
| } | |
| } | |
| b.Run("None", makeBench()) | |
| b.Run("Same", makeBench(&export.Encoder(enc).Struct)) | |
| b.Run("New", makeBench(DefaultOptionsV2())) | |
| } |