| // 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:"-"` |
| } |
| structWeirdNames struct { |
| Empty string `json:"''"` |
| Comma string `json:"','"` |
| Quote string `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 |
| } |
| structStringifiedAll struct { |
| Bool bool `json:",string"` |
| String string `json:",string"` |
| Bytes []byte `json:",string"` |
| Int int64 `json:",string"` |
| Uint uint64 `json:",string"` |
| Float float64 `json:",string"` |
| Map map[string]string `json:",string"` |
| StructScalars structScalars `json:",string"` |
| StructMaps structMaps `json:",string"` |
| StructSlices structSlices `json:",string"` |
| Slice []string `json:",string"` |
| Array [1]string `json:",string"` |
| Pointer *structStringifiedAll `json:",string"` |
| Interface any `json:",string"` |
| } |
| 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"` |
| } |
| 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"` |
| } |
| structInlined struct { |
| X structInlinedL1 `json:",inline"` |
| *StructEmbed2 // implicit inline |
| } |
| structInlinedL1 struct { |
| X *structInlinedL2 `json:",inline"` |
| StructEmbed1 `json:",inline"` |
| } |
| structInlinedL2 struct{ A, B, C string } |
| StructEmbed1 struct{ C, D, E string } |
| StructEmbed2 struct{ E, F, G string } |
| structUnknownTextValue struct { |
| A int `json:",omitzero"` |
| X jsontext.Value `json:",unknown"` |
| B int `json:",omitzero"` |
| } |
| structInlineTextValue struct { |
| A int `json:",omitzero"` |
| X jsontext.Value `json:",inline"` |
| B int `json:",omitzero"` |
| } |
| structInlinePointerTextValue struct { |
| A int `json:",omitzero"` |
| X *jsontext.Value `json:",inline"` |
| B int `json:",omitzero"` |
| } |
| structInlinePointerInlineTextValue struct { |
| X *struct { |
| A int |
| X jsontext.Value `json:",inline"` |
| } `json:",inline"` |
| } |
| structInlineInlinePointerTextValue struct { |
| X struct { |
| X *jsontext.Value `json:",inline"` |
| } `json:",inline"` |
| } |
| structInlineMapStringAny struct { |
| A int `json:",omitzero"` |
| X jsonObject `json:",inline"` |
| B int `json:",omitzero"` |
| } |
| structInlinePointerMapStringAny struct { |
| A int `json:",omitzero"` |
| X *jsonObject `json:",inline"` |
| B int `json:",omitzero"` |
| } |
| structInlinePointerInlineMapStringAny struct { |
| X *struct { |
| A int |
| X jsonObject `json:",inline"` |
| } `json:",inline"` |
| } |
| structInlineInlinePointerMapStringAny struct { |
| X struct { |
| X *jsonObject `json:",inline"` |
| } `json:",inline"` |
| } |
| structInlineMapStringInt struct { |
| X map[string]int `json:",inline"` |
| } |
| structInlineMapNamedStringInt struct { |
| X map[namedString]int `json:",inline"` |
| } |
| structInlineMapNamedStringAny struct { |
| A int `json:",omitzero"` |
| X map[namedString]any `json:",inline"` |
| B int `json:",omitzero"` |
| } |
| structNoCaseInlineTextValue 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:",inline"` |
| } |
| structNoCaseInlineMapStringAny struct { |
| AAA string `json:",omitempty"` |
| AaA string `json:",omitempty,case:ignore"` |
| AAa string `json:",omitempty,case:ignore"` |
| Aaa string `json:",omitempty"` |
| X jsonObject `json:",inline"` |
| } |
| |
| 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 |
| } |
| |
| 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:",inline"` |
| B2 cyclicB `json:",inline"` |
| } |
| cyclicB struct { |
| F int |
| A *cyclicA `json:",inline"` |
| } |
| ) |
| |
| 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 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 |
| } |
| |
| var invalidFormatOption = &jsonopts.Struct{ |
| ArshalValues: jsonopts.ArshalValues{FormatDepth: 1000, 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"), |
| opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, |
| in: []bool{false, true}, |
| want: `["false","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/StringifiedBool"), |
| opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, |
| in: []string{"", "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/WeirdNames"), |
| in: structWeirdNames{Empty: "empty", Comma: "comma", Quote: "quote"}, |
| want: `{"":"empty",",":"comma","\"":"quote"}`, |
| }, { |
| name: jsontest.Name("Structs/EscapedNames"), |
| opts: []Options{jsontext.EscapeForHTML(true), jsontext.EscapeForJS(true)}, |
| in: struct { |
| S string "json:\"'abc<>&\u2028\u2029xyz'\"" |
| M any |
| I structInlineTextValue |
| }{ |
| S: "abc<>&\u2028\u2029xyz", |
| M: map[string]string{"abc<>&\u2028\u2029xyz": "abc<>&\u2028\u2029xyz"}, |
| I: structInlineTextValue{X: jsontext.Value(`{"abc<>&` + "\u2028\u2029" + `xyz":"abc<>&` + "\u2028\u2029" + `xyz"}`)}, |
| }, |
| want: `{"abc\u003c\u003e\u0026\u2028\u2029xyz":"abc\u003c\u003e\u0026\u2028\u2029xyz","M":{"abc\u003c\u003e\u0026\u2028\u2029xyz":"abc\u003c\u003e\u0026\u2028\u2029xyz"},"I":{"abc\u003c\u003e\u0026\u2028\u2029xyz":"abc\u003c\u003e\u0026\u2028\u2029xyz"}}`, |
| }, { |
| 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: structStringifiedAll{ |
| Bool: true, |
| String: "hello", |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // should be stringified |
| Uint: +64, // should be stringified |
| Float: 3.14159, // should be stringified |
| Map: map[string]string{"key": "value"}, |
| StructScalars: structScalars{ |
| Bool: true, |
| String: "hello", |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // should be stringified |
| Uint: +64, // should be stringified |
| Float: 3.14159, // should be stringified |
| }, |
| StructMaps: structMaps{ |
| MapBool: map[string]bool{"": true}, |
| MapString: map[string]string{"": "hello"}, |
| MapBytes: map[string][]byte{"": {1, 2, 3}}, |
| MapInt: map[string]int64{"": -64}, // should be stringified |
| MapUint: map[string]uint64{"": +64}, // should be stringified |
| MapFloat: map[string]float64{"": 3.14159}, // should be stringified |
| }, |
| StructSlices: structSlices{ |
| SliceBool: []bool{true}, |
| SliceString: []string{"hello"}, |
| SliceBytes: [][]byte{{1, 2, 3}}, |
| SliceInt: []int64{-64}, // should be stringified |
| SliceUint: []uint64{+64}, // should be stringified |
| SliceFloat: []float64{3.14159}, // should be stringified |
| }, |
| Slice: []string{"fizz", "buzz"}, |
| Array: [1]string{"goodbye"}, |
| Pointer: new(structStringifiedAll), // should be stringified |
| Interface: (*structStringifiedAll)(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/LegacyStringified"), |
| opts: []Options{jsontext.Multiline(true), jsonflags.StringifyWithLegacySemantics | 1}, |
| in: structStringifiedAll{ |
| Bool: true, // should be stringified |
| String: "hello", // should be stringified |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // should be stringified |
| Uint: +64, // should be stringified |
| Float: 3.14159, // should be stringified |
| 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(structStringifiedAll), // should be stringified |
| Interface: (*structStringifiedAll)(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/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"), |
| opts: []Options{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{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{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{ |
| 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{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{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{ |
| 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{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"), |
| in: structFormatInvalid{Bool: true}, |
| want: `{"Bool"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, boolType), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/String"), |
| in: structFormatInvalid{String: "string"}, |
| want: `{"String"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"String":`, "/String").withType(0, stringType), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Bytes"), |
| in: structFormatInvalid{Bytes: []byte("bytes")}, |
| want: `{"Bytes"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Bytes":`, "/Bytes").withType(0, bytesType), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Int"), |
| in: structFormatInvalid{Int: 1}, |
| want: `{"Int"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Int":`, "/Int").withType(0, T[int64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Uint"), |
| in: structFormatInvalid{Uint: 1}, |
| want: `{"Uint"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Uint":`, "/Uint").withType(0, T[uint64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Float"), |
| in: structFormatInvalid{Float: 1}, |
| want: `{"Float"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Float":`, "/Float").withType(0, T[float64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Map"), |
| 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"), |
| 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"), |
| in: structFormatInvalid{Slice: []string{}}, |
| want: `{"Slice"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Slice":`, "/Slice").withType(0, T[[]string]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Array"), |
| 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"), |
| in: structFormatInvalid{Interface: "anything"}, |
| want: `{"Interface"`, |
| wantErr: EM(errInvalidFormatFlag).withPos(`{"Interface":`, "/Interface").withType(0, T[any]()), |
| }, { |
| name: jsontest.Name("Structs/Inline/Zero"), |
| in: structInlined{}, |
| want: `{"D":""}`, |
| }, { |
| name: jsontest.Name("Structs/Inline/Alloc"), |
| in: structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{}, |
| StructEmbed1: StructEmbed1{}, |
| }, |
| StructEmbed2: &StructEmbed2{}, |
| }, |
| want: `{"A":"","B":"","D":"","E":"","F":"","G":""}`, |
| }, { |
| name: jsontest.Name("Structs/Inline/NonZero"), |
| in: structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{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/Inline/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/InlinedFallback/TextValue/Nil"), |
| in: structInlineTextValue{X: jsontext.Value(nil)}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/Empty"), |
| in: structInlineTextValue{X: jsontext.Value("")}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/NonEmptyN1"), |
| in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" } `)}, |
| want: `{"fizz":"buzz"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/NonEmptyN2"), |
| in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "foo" : "bar" } `)}, |
| want: `{"fizz":"buzz","foo":"bar"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/NonEmptyWithOthers"), |
| in: structInlineTextValue{ |
| A: 1, |
| X: jsontext.Value(` { "fizz" : "buzz" , "foo" : "bar" } `), |
| B: 2, |
| }, |
| // NOTE: Inlined fallback fields are always serialized last. |
| want: `{"A":1,"B":2,"fizz":"buzz","foo":"bar"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/RejectDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, |
| in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "fizz" : "buzz" } `)}, |
| want: `{"fizz":"buzz"`, |
| wantErr: newDuplicateNameError("/fizz", nil, len64(`{"fizz":"buzz"`)), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "fizz" : "buzz" } `)}, |
| want: `{"fizz":"buzz","fizz":"buzz"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/RejectInvalidUTF8"), |
| opts: []Options{jsontext.AllowInvalidUTF8(false)}, |
| in: structInlineTextValue{X: jsontext.Value(`{"` + "\xde\xad\xbe\xef" + `":"value"}`)}, |
| want: `{`, |
| wantErr: newInvalidUTF8Error(len64(`{"`+"\xde\xad"), ""), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/AllowInvalidUTF8"), |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, |
| in: structInlineTextValue{X: jsontext.Value(`{"` + "\xde\xad\xbe\xef" + `":"value"}`)}, |
| want: `{"ޭ��":"value"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidWhitespace"), |
| in: structInlineTextValue{X: jsontext.Value("\n\r\t ")}, |
| want: `{`, |
| wantErr: EM(io.ErrUnexpectedEOF).withPos(`{`, "").withType(0, T[jsontext.Value]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidObject"), |
| in: structInlineTextValue{X: jsontext.Value(` true `)}, |
| want: `{`, |
| wantErr: EM(errRawInlinedNotObject).withPos(`{`, "").withType(0, T[jsontext.Value]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidObjectName"), |
| in: structInlineTextValue{X: jsontext.Value(` { true : false } `)}, |
| want: `{`, |
| wantErr: EM(newNonStringNameError(len64(" { "), "")).withPos(`{`, "").withType(0, T[jsontext.Value]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidEndObject"), |
| in: structInlineTextValue{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/InlinedFallback/TextValue/InvalidDualObject"), |
| in: structInlineTextValue{X: jsontext.Value(`{}{}`)}, |
| want: `{`, |
| wantErr: EM(newInvalidCharacterError("{", "after top-level value", len64(`{}`), "")).withPos(`{`, "").withType(0, T[jsontext.Value]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Nil"), |
| in: structInlinePointerInlineTextValue{}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Nil"), |
| in: structInlinePointerTextValue{}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/NonEmpty"), |
| in: structInlinePointerTextValue{X: addr(jsontext.Value(` { "fizz" : "buzz" } `))}, |
| want: `{"fizz":"buzz"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Nested/Nil"), |
| in: structInlineInlinePointerTextValue{}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nil"), |
| in: structInlineMapStringAny{X: nil}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Empty"), |
| in: structInlineMapStringAny{X: make(jsonObject)}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/NonEmptyN1"), |
| in: structInlineMapStringAny{X: jsonObject{"fizz": nil}}, |
| want: `{"fizz":null}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/NonEmptyN2"), |
| in: structInlineMapStringAny{X: jsonObject{"fizz": time.Time{}, "buzz": math.Pi}}, |
| want: `{"buzz":3.141592653589793,"fizz":"0001-01-01T00:00:00Z"}`, |
| canonicalize: true, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/NonEmptyWithOthers"), |
| in: structInlineMapStringAny{ |
| A: 1, |
| X: jsonObject{"fizz": nil}, |
| B: 2, |
| }, |
| // NOTE: Inlined fallback fields are always serialized last. |
| want: `{"A":1,"B":2,"fizz":null}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/RejectInvalidUTF8"), |
| opts: []Options{jsontext.AllowInvalidUTF8(false)}, |
| in: structInlineMapStringAny{X: jsonObject{"\xde\xad\xbe\xef": nil}}, |
| want: `{`, |
| wantErr: EM(jsonwire.ErrInvalidUTF8).withPos(`{`, "").withType(0, stringType), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/AllowInvalidUTF8"), |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, |
| in: structInlineMapStringAny{X: jsonObject{"\xde\xad\xbe\xef": nil}}, |
| want: `{"ޭ��":null}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/InvalidValue"), |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, |
| in: structInlineMapStringAny{X: jsonObject{"name": make(chan string)}}, |
| want: `{"name"`, |
| wantErr: EM(nil).withPos(`{"name":`, "/name").withType(0, T[chan string]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Nil"), |
| in: structInlinePointerInlineMapStringAny{}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MarshalFunc"), |
| opts: []Options{ |
| WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { |
| return []byte(fmt.Sprintf(`"%v"`, v)), nil |
| })), |
| }, |
| in: structInlineMapStringAny{X: jsonObject{"fizz": 3.14159}}, |
| want: `{"fizz":"3.14159"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Nil"), |
| in: structInlinePointerMapStringAny{X: nil}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/NonEmpty"), |
| in: structInlinePointerMapStringAny{X: addr(jsonObject{"name": "value"})}, |
| want: `{"name":"value"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Nested/Nil"), |
| in: structInlineInlinePointerMapStringAny{}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt"), |
| in: structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }, |
| want: `{"one":1,"two":2,"zero":0}`, |
| canonicalize: true, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Deterministic"), |
| opts: []Options{Deterministic(true)}, |
| in: structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }, |
| want: `{"one":1,"two":2,"zero":0}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), |
| opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(false)}, |
| in: structInlineMapStringInt{ |
| X: map[string]int{"\xff": 0, "\xfe": 1}, |
| }, |
| want: `{"�":1`, |
| wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":1`)), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), |
| opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, |
| in: structInlineMapStringInt{ |
| X: map[string]int{"\xff": 0, "\xfe": 1}, |
| }, |
| want: `{"�":1,"�":0}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/StringifiedNumbers"), |
| opts: []Options{StringifyNumbers(true)}, |
| in: structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }, |
| want: `{"one":"1","two":"2","zero":"0"}`, |
| canonicalize: true, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/MarshalFunc"), |
| opts: []Options{ |
| WithMarshalers(JoinMarshalers( |
| // Marshalers do not affect the string key of inlined 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: structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }, |
| want: `{"one":"1","two":"2","zero":"0"}`, |
| canonicalize: true, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt"), |
| in: structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, |
| }, |
| want: `{"one":1,"two":2,"zero":0}`, |
| canonicalize: true, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/Deterministic"), |
| opts: []Options{Deterministic(true)}, |
| in: structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, |
| }, |
| want: `{"one":1,"two":2,"zero":0}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/Nil"), |
| in: structInlineMapNamedStringAny{X: nil}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/Empty"), |
| in: structInlineMapNamedStringAny{X: make(map[namedString]any)}, |
| want: `{}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/NonEmptyN1"), |
| in: structInlineMapNamedStringAny{X: map[namedString]any{"fizz": nil}}, |
| want: `{"fizz":null}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/NonEmptyN2"), |
| in: structInlineMapNamedStringAny{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/InlinedFallback/MapNamedStringAny/NonEmptyWithOthers"), |
| in: structInlineMapNamedStringAny{ |
| A: 1, |
| X: map[namedString]any{"fizz": nil}, |
| B: 2, |
| }, |
| // NOTE: Inlined fallback fields are always serialized last. |
| want: `{"A":1,"B":2,"fizz":null}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/RejectInvalidUTF8"), |
| opts: []Options{jsontext.AllowInvalidUTF8(false)}, |
| in: structInlineMapNamedStringAny{X: map[namedString]any{"\xde\xad\xbe\xef": nil}}, |
| want: `{`, |
| wantErr: EM(jsonwire.ErrInvalidUTF8).withPos(`{`, "").withType(0, T[namedString]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/AllowInvalidUTF8"), |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, |
| in: structInlineMapNamedStringAny{X: map[namedString]any{"\xde\xad\xbe\xef": nil}}, |
| want: `{"ޭ��":null}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/InvalidValue"), |
| opts: []Options{jsontext.AllowInvalidUTF8(true)}, |
| in: structInlineMapNamedStringAny{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/InlinedFallback/MapNamedStringAny/MarshalFunc"), |
| opts: []Options{ |
| WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { |
| return []byte(fmt.Sprintf(`"%v"`, v)), nil |
| })), |
| }, |
| in: structInlineMapNamedStringAny{X: map[namedString]any{"fizz": 3.14159}}, |
| want: `{"fizz":"3.14159"}`, |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/DiscardUnknownMembers"), |
| opts: []Options{DiscardUnknownMembers(true)}, |
| in: structInlineTextValue{ |
| A: 1, |
| X: jsontext.Value(` { "fizz" : "buzz" } `), |
| B: 2, |
| }, |
| // NOTE: DiscardUnknownMembers has no effect since this is "inline". |
| want: `{"A":1,"B":2,"fizz":"buzz"}`, |
| }, { |
| name: jsontest.Name("Structs/UnknownFallback/DiscardUnknownMembers"), |
| opts: []Options{DiscardUnknownMembers(true)}, |
| in: structUnknownTextValue{ |
| A: 1, |
| X: jsontext.Value(` { "fizz" : "buzz" } `), |
| B: 2, |
| }, |
| want: `{"A":1,"B":2}`, |
| }, { |
| name: jsontest.Name("Structs/UnknownFallback"), |
| in: structUnknownTextValue{ |
| A: 1, |
| X: jsontext.Value(` { "fizz" : "buzz" } `), |
| B: 2, |
| }, |
| want: `{"A":1,"B":2,"fizz":"buzz"}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/Other"), |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"dupe":"","dupe":""}`), |
| }, |
| want: `{"dupe":""`, |
| wantErr: newDuplicateNameError("", []byte(`"dupe"`), len64(`{"dupe":""`)), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/Other/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"dupe": "", "dupe": ""}`), |
| }, |
| want: `{"dupe":"","dupe":""}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactDifferent"), |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"Aaa": "", "AaA": "", "AAa": "", "AAA": ""}`), |
| }, |
| want: `{"Aaa":"","AaA":"","AAa":"","AAA":""}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactConflict"), |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"Aaa": "", "Aaa": ""}`), |
| }, |
| want: `{"Aaa":""`, |
| wantErr: newDuplicateNameError("", []byte(`"Aaa"`), len64(`{"Aaa":""`)), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactConflict/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"Aaa": "", "Aaa": ""}`), |
| }, |
| want: `{"Aaa":"","Aaa":""}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/NoCaseConflict"), |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"Aaa": "", "AaA": "", "aaa": ""}`), |
| }, |
| want: `{"Aaa":"","AaA":""`, |
| wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"Aaa":"","AaA":""`)), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/NoCaseConflict/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| in: structNoCaseInlineTextValue{ |
| X: jsontext.Value(`{"Aaa": "", "AaA": "", "aaa": ""}`), |
| }, |
| want: `{"Aaa":"","AaA":"","aaa":""}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactDifferentWithField"), |
| in: structNoCaseInlineTextValue{ |
| AAA: "x", |
| AaA: "x", |
| X: jsontext.Value(`{"Aaa": ""}`), |
| }, |
| want: `{"AAA":"x","AaA":"x","Aaa":""}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactConflictWithField"), |
| in: structNoCaseInlineTextValue{ |
| 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/NoCaseInlineTextValue/NoCaseConflictWithField"), |
| in: structNoCaseInlineTextValue{ |
| 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: structNoCaseInlineTextValue{ |
| 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: structNoCaseInlineTextValue{ |
| 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: structNoCaseInlineTextValue{ |
| 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: structNoCaseInlineTextValue{ |
| 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/NoCaseInlineMapStringAny/ExactDifferent"), |
| in: structNoCaseInlineMapStringAny{ |
| X: jsonObject{"Aaa": "", "AaA": "", "AAa": "", "AAA": ""}, |
| }, |
| want: `{"AAA":"","AAa":"","AaA":"","Aaa":""}`, |
| canonicalize: true, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineMapStringAny/ExactDifferentWithField"), |
| in: structNoCaseInlineMapStringAny{ |
| AAA: "x", |
| AaA: "x", |
| X: jsonObject{"Aaa": ""}, |
| }, |
| want: `{"AAA":"x","AaA":"x","Aaa":""}`, |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCaseInlineMapStringAny/ExactConflictWithField"), |
| in: structNoCaseInlineMapStringAny{ |
| 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/NoCaseInlineMapStringAny/NoCaseConflictWithField"), |
| in: structNoCaseInlineMapStringAny{ |
| 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 or single quote)")).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/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/SkipFunc"), |
| in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { |
| return SkipFunc |
| }), |
| wantErr: EM(errors.New("marshal method cannot be skipped")).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/SkipFunc"), |
| in: marshalJSONv1Func(func() ([]byte, error) { |
| return nil, SkipFunc |
| }), |
| wantErr: EM(errors.New("marshal method cannot be skipped")).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/SkipFunc"), |
| in: marshalTextFunc(func() ([]byte, error) { |
| return nil, SkipFunc |
| }), |
| wantErr: EM(wrapSkipFunc(SkipFunc, "marshal 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, SkipFunc |
| })), |
| }, |
| in: true, |
| wantErr: EM(wrapSkipFunc(SkipFunc, "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 SkipFunc |
| })), |
| }, |
| 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 SkipFunc |
| })), |
| }, |
| in: true, |
| want: `"hello"`, |
| wantErr: EM(errSkipMutation).withPos(`"hello"`, "").withType(0, T[bool]()), |
| }, { |
| name: jsontest.Name("Functions/Bool/V2/WrappedSkipError"), |
| opts: []Options{ |
| WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { |
| return fmt.Errorf("wrap: %w", SkipFunc) |
| })), |
| }, |
| in: true, |
| wantErr: EM(fmt.Errorf("wrap: %w", SkipFunc)).withType(0, T[bool]()), |
| }, { |
| 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/Inlined"), |
| opts: []Options{ |
| WithMarshalers(JoinMarshalers( |
| MarshalFunc(func(v structInlinedL1) ([]byte, error) { |
| panic("should not be called") |
| }), |
| MarshalToFunc(func(enc *jsontext.Encoder, v *StructEmbed2) error { |
| panic("should not be called") |
| }), |
| )), |
| }, |
| in: structInlined{}, |
| 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"), |
| 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"]`, |
| 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 SkipFunc |
| } |
| 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 SkipFunc |
| } |
| } |
| 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 SkipFunc |
| } |
| } |
| 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, |
| ) |
| }()), |
| }, |
| }, { |
| 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 SkipFunc |
| }), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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{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"), |
| 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{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"), |
| 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/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 |
| }{{ |
| 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.StringifyBoolsAndStrings | 1}, |
| inBuf: `"true"`, |
| inVal: addr(false), |
| want: addr(true), |
| }, { |
| name: jsontest.Name("Bools/StringifiedBool/False"), |
| opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, |
| inBuf: `"false"`, |
| inVal: addr(true), |
| want: addr(false), |
| }, { |
| name: jsontest.Name("Bools/StringifiedBool/InvalidWhitespace"), |
| opts: []Options{jsonflags.StringifyBoolsAndStrings | 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.StringifyBoolsAndStrings | 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.StringifyBoolsAndStrings | 1}, |
| inBuf: `"\"foo\""`, |
| inVal: new(string), |
| want: addr("foo"), |
| }, { |
| name: jsontest.Name("Strings/StringifiedString/InvalidWhitespace"), |
| opts: []Options{jsonflags.StringifyBoolsAndStrings | 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.StringifyBoolsAndStrings | 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(-math.MaxFloat32)), |
| 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(-math.MaxFloat64)), |
| 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(math.MaxFloat64))), |
| 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/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(structStringifiedAll), |
| want: addr(structStringifiedAll{ |
| Bool: true, |
| String: "hello", |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // may be stringified |
| Uint: +64, // may be stringified |
| Float: 3.14159, // may be stringified |
| Map: map[string]string{"key": "value"}, |
| StructScalars: structScalars{ |
| Bool: true, |
| String: "hello", |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // may be stringified |
| Uint: +64, // may be stringified |
| Float: 3.14159, // may be stringified |
| }, |
| StructMaps: structMaps{ |
| MapBool: map[string]bool{"": true}, |
| MapString: map[string]string{"": "hello"}, |
| MapBytes: map[string][]byte{"": {1, 2, 3}}, |
| MapInt: map[string]int64{"": -64}, // may be stringified |
| MapUint: map[string]uint64{"": +64}, // may be stringified |
| MapFloat: map[string]float64{"": 3.14159}, // may be stringified |
| }, |
| StructSlices: structSlices{ |
| SliceBool: []bool{true}, |
| SliceString: []string{"hello"}, |
| SliceBytes: [][]byte{{1, 2, 3}}, |
| SliceInt: []int64{-64}, // may be stringified |
| SliceUint: []uint64{+64}, // may be stringified |
| SliceFloat: []float64{3.14159}, // may be stringified |
| }, |
| Slice: []string{"fizz", "buzz"}, |
| Array: [1]string{"goodbye"}, |
| Pointer: new(structStringifiedAll), // may be stringified |
| }), |
| }, { |
| name: jsontest.Name("Structs/Stringified/String"), |
| 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(structStringifiedAll), |
| want: addr(structStringifiedAll{ |
| Bool: true, |
| String: "hello", |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // may be stringified |
| Uint: +64, // may be stringified |
| Float: 3.14159, // may be stringified |
| Map: map[string]string{"key": "value"}, |
| StructScalars: structScalars{ |
| Bool: true, |
| String: "hello", |
| Bytes: []byte{1, 2, 3}, |
| Int: -64, // may be stringified |
| Uint: +64, // may be stringified |
| Float: 3.14159, // may be stringified |
| }, |
| StructMaps: structMaps{ |
| MapBool: map[string]bool{"": true}, |
| MapString: map[string]string{"": "hello"}, |
| MapBytes: map[string][]byte{"": {1, 2, 3}}, |
| MapInt: map[string]int64{"": -64}, // may be stringified |
| MapUint: map[string]uint64{"": +64}, // may be stringified |
| MapFloat: map[string]float64{"": 3.14159}, // may be stringified |
| }, |
| StructSlices: structSlices{ |
| SliceBool: []bool{true}, |
| SliceString: []string{"hello"}, |
| SliceBytes: [][]byte{{1, 2, 3}}, |
| SliceInt: []int64{-64}, // may be stringified |
| SliceUint: []uint64{+64}, // may be stringified |
| SliceFloat: []float64{3.14159}, // may be stringified |
| }, |
| Slice: []string{"fizz", "buzz"}, |
| Array: [1]string{"goodbye"}, |
| Pointer: new(structStringifiedAll), // may be stringified |
| }), |
| }, { |
| name: jsontest.Name("Structs/Stringified/InvalidEmpty"), |
| inBuf: `{"Int":""}`, |
| inVal: new(structStringifiedAll), |
| want: new(structStringifiedAll), |
| wantErr: EU(strconv.ErrSyntax).withVal(`""`).withPos(`{"Int":`, "/Int").withType('"', T[int64]()), |
| }, { |
| name: jsontest.Name("Structs/LegacyStringified"), |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, |
| 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"] |
| }`, |
| inVal: new(structStringifiedAll), |
| want: addr(structStringifiedAll{ |
| 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"}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/LegacyStringified/InvalidBool"), |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, |
| inBuf: `{"Bool": true}`, |
| inVal: new(structStringifiedAll), |
| wantErr: EU(nil).withPos(`{"Bool": `, "/Bool").withType('t', T[bool]()), |
| }, { |
| name: jsontest.Name("Structs/LegacyStringified/InvalidString"), |
| opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, |
| inBuf: `{"String": "string"}`, |
| inVal: new(structStringifiedAll), |
| wantErr: EU(newInvalidCharacterError("s", "at start of string (expecting '\"')", 0, "")). |
| withPos(`{"String": `, "/String").withType('"', T[string]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Bytes"), |
| 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"), |
| 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{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{ |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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{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"), |
| inBuf: `{"Base64": "aa= ="}`, |
| inVal: new(structFormatBytes), |
| wantErr: EU(base64.CorruptInputError(2)).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Floats"), |
| 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"), |
| inBuf: `{"NonFinite": "NaN"}`, |
| inVal: new(structFormatFloats), |
| // Avoid checking want since reflect.DeepEqual fails for NaNs. |
| }, { |
| name: jsontest.Name("Structs/Format/Floats/Invalid/NaN"), |
| inBuf: `{"NonFinite": "nan"}`, |
| inVal: new(structFormatFloats), |
| wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Floats/Invalid/PositiveInfinity"), |
| inBuf: `{"NonFinite": "+Infinity"}`, |
| inVal: new(structFormatFloats), |
| wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Floats/Invalid/NegativeInfinitySpace"), |
| inBuf: `{"NonFinite": "-Infinity "}`, |
| inVal: new(structFormatFloats), |
| wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Maps"), |
| 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"), |
| 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"), |
| inBuf: `{"Bool":true}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, T[bool]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/String"), |
| inBuf: `{"String": "string"}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"String": `, "/String").withType(0, T[string]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Bytes"), |
| inBuf: `{"Bytes": "bytes"}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Bytes": `, "/Bytes").withType(0, T[[]byte]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Int"), |
| inBuf: `{"Int": 1}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Int": `, "/Int").withType(0, T[int64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Uint"), |
| inBuf: `{"Uint": 1}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Uint": `, "/Uint").withType(0, T[uint64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Float"), |
| inBuf: `{"Float" : 1}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Float" : `, "/Float").withType(0, T[float64]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Map"), |
| inBuf: `{"Map":{}}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Map":`, "/Map").withType(0, T[map[string]string]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Struct"), |
| inBuf: `{"Struct": {}}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Struct": `, "/Struct").withType(0, T[structAll]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Slice"), |
| inBuf: `{"Slice": {}}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Slice": `, "/Slice").withType(0, T[[]string]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Array"), |
| inBuf: `{"Array": []}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Array": `, "/Array").withType(0, T[[1]string]()), |
| }, { |
| name: jsontest.Name("Structs/Format/Invalid/Interface"), |
| inBuf: `{"Interface": "anything"}`, |
| inVal: new(structFormatInvalid), |
| wantErr: EU(errInvalidFormatFlag).withPos(`{"Interface": `, "/Interface").withType(0, T[any]()), |
| }, { |
| name: jsontest.Name("Structs/Inline/Zero"), |
| inBuf: `{"D":""}`, |
| inVal: new(structInlined), |
| want: new(structInlined), |
| }, { |
| name: jsontest.Name("Structs/Inline/Alloc"), |
| inBuf: `{"E":"","F":"","G":"","A":"","B":"","D":""}`, |
| inVal: new(structInlined), |
| want: addr(structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{}, |
| StructEmbed1: StructEmbed1{}, |
| }, |
| StructEmbed2: &StructEmbed2{}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/Inline/NonZero"), |
| inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, |
| inVal: new(structInlined), |
| want: addr(structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{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/Inline/Merge"), |
| inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, |
| inVal: addr(structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{B: "##", C: "C1"}, |
| StructEmbed1: StructEmbed1{C: "C2", E: "E2"}, |
| }, |
| StructEmbed2: &StructEmbed2{E: "##", G: "G3"}, |
| }), |
| want: addr(structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{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/InlinedFallback/TextValue/Noop"), |
| inBuf: `{"A":1,"B":2}`, |
| inVal: new(structInlineTextValue), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(nil), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Nil"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structInlineTextValue), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Empty"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineTextValue{X: jsontext.Value{}}), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Whitespace"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineTextValue{X: jsontext.Value("\n\r\t ")}), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value("")}), |
| wantErr: EU(errRawInlinedNotObject).withPos(`{"A":1,`, "/fizz").withType('"', T[jsontext.Value]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Null"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineTextValue{X: jsontext.Value("null")}), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value("null")}), |
| wantErr: EU(errRawInlinedNotObject).withPos(`{"A":1,`, "/fizz").withType('"', T[jsontext.Value]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/ObjectN0"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineTextValue{X: jsontext.Value(` { } `)}), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(` {"fizz":"buzz"}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN2/ObjectN1"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, |
| inVal: addr(structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" } `)}), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(` { "fizz" : "buzz","fizz":"buzz","foo":[ 1 , 2 , 3 ]}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/Merge/EndObject"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineTextValue{X: jsontext.Value(` } `)}), |
| // NOTE: This produces invalid output, |
| // but the value being merged into is already invalid. |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`,"fizz":"buzz"}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeInvalidValue"), |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, |
| inVal: new(structInlineTextValue), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":`)}), |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/CaseSensitive"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, |
| inVal: new(structInlineTextValue), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz","a":3}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/RejectDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, |
| inVal: new(structInlineTextValue), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), |
| wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, |
| inVal: new(structInlineTextValue), |
| want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz","fizz":"buzz"}`), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Noop"), |
| inBuf: `{}`, |
| inVal: new(structInlinePointerInlineTextValue), |
| want: new(structInlinePointerInlineTextValue), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Alloc"), |
| inBuf: `{"A":1,"fizz":"buzz"}`, |
| inVal: new(structInlinePointerInlineTextValue), |
| want: addr(structInlinePointerInlineTextValue{ |
| X: &struct { |
| A int |
| X jsontext.Value `json:",inline"` |
| }{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`)}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Merge"), |
| inBuf: `{"fizz":"buzz"}`, |
| inVal: addr(structInlinePointerInlineTextValue{ |
| X: &struct { |
| A int |
| X jsontext.Value `json:",inline"` |
| }{A: 1}, |
| }), |
| want: addr(structInlinePointerInlineTextValue{ |
| X: &struct { |
| A int |
| X jsontext.Value `json:",inline"` |
| }{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`)}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Noop"), |
| inBuf: `{"A":1,"B":2}`, |
| inVal: new(structInlinePointerTextValue), |
| want: addr(structInlinePointerTextValue{A: 1, X: nil, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Alloc"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structInlinePointerTextValue), |
| want: addr(structInlinePointerTextValue{A: 1, X: addr(jsontext.Value(`{"fizz":"buzz"}`)), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Merge"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlinePointerTextValue{X: addr(jsontext.Value(`{"fizz":"buzz"}`))}), |
| want: addr(structInlinePointerTextValue{A: 1, X: addr(jsontext.Value(`{"fizz":"buzz","fizz":"buzz"}`)), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Nested/Nil"), |
| inBuf: `{"fizz":"buzz"}`, |
| inVal: new(structInlineInlinePointerTextValue), |
| want: addr(structInlineInlinePointerTextValue{ |
| X: struct { |
| X *jsontext.Value `json:",inline"` |
| }{X: addr(jsontext.Value(`{"fizz":"buzz"}`))}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Noop"), |
| inBuf: `{"A":1,"B":2}`, |
| inVal: new(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{A: 1, X: nil, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN1/Nil"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN1/Empty"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineMapStringAny{X: jsonObject{}}), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN1/ObjectN1"), |
| inBuf: `{"A":1,"fizz":{"charlie":"DELTA","echo":"foxtrot"},"B":2}`, |
| inVal: addr(structInlineMapStringAny{X: jsonObject{"fizz": jsonObject{ |
| "alpha": "bravo", |
| "charlie": "delta", |
| }}}), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": jsonObject{ |
| "alpha": "bravo", |
| "charlie": "DELTA", |
| "echo": "foxtrot", |
| }}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN2/ObjectN1"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, |
| inVal: addr(structInlineMapStringAny{X: jsonObject{"fizz": "wuzz"}}), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz", "foo": jsonArray{1.0, 2.0, 3.0}}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeInvalidValue"), |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, |
| inVal: new(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": nil}}), |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeInvalidValue/Existing"), |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, |
| inVal: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": true}}), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": true}}), |
| wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/CaseSensitive"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, |
| inVal: new(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz", "a": 3.0}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/RejectDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, |
| inVal: new(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), |
| wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| inBuf: `{"A":1,"fizz":{"one":1,"two":-2},"B":2,"fizz":{"two":2,"three":3}}`, |
| inVal: new(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": jsonObject{"one": 1.0, "two": 2.0, "three": 3.0}}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Noop"), |
| inBuf: `{}`, |
| inVal: new(structInlinePointerInlineMapStringAny), |
| want: new(structInlinePointerInlineMapStringAny), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Alloc"), |
| inBuf: `{"A":1,"fizz":"buzz"}`, |
| inVal: new(structInlinePointerInlineMapStringAny), |
| want: addr(structInlinePointerInlineMapStringAny{ |
| X: &struct { |
| A int |
| X jsonObject `json:",inline"` |
| }{A: 1, X: jsonObject{"fizz": "buzz"}}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Merge"), |
| inBuf: `{"fizz":"buzz"}`, |
| inVal: addr(structInlinePointerInlineMapStringAny{ |
| X: &struct { |
| A int |
| X jsonObject `json:",inline"` |
| }{A: 1}, |
| }), |
| want: addr(structInlinePointerInlineMapStringAny{ |
| X: &struct { |
| A int |
| X jsonObject `json:",inline"` |
| }{A: 1, X: jsonObject{"fizz": "buzz"}}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/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(structInlineMapStringAny), |
| want: addr(structInlineMapStringAny{X: jsonObject{"D": 1.1, "E": 2.2, "F": 3.3}}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Noop"), |
| inBuf: `{"A":1,"B":2}`, |
| inVal: new(structInlinePointerMapStringAny), |
| want: addr(structInlinePointerMapStringAny{A: 1, X: nil, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Alloc"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structInlinePointerMapStringAny), |
| want: addr(structInlinePointerMapStringAny{A: 1, X: addr(jsonObject{"fizz": "buzz"}), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Merge"), |
| inBuf: `{"A":1,"fizz":"wuzz","B":2}`, |
| inVal: addr(structInlinePointerMapStringAny{X: addr(jsonObject{"fizz": "buzz"})}), |
| want: addr(structInlinePointerMapStringAny{A: 1, X: addr(jsonObject{"fizz": "wuzz"}), B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Nested/Nil"), |
| inBuf: `{"fizz":"buzz"}`, |
| inVal: new(structInlineInlinePointerMapStringAny), |
| want: addr(structInlineInlinePointerMapStringAny{ |
| X: struct { |
| X *jsonObject `json:",inline"` |
| }{X: addr(jsonObject{"fizz": "buzz"})}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt"), |
| inBuf: `{"zero": 0, "one": 1, "two": 2}`, |
| inVal: new(structInlineMapStringInt), |
| want: addr(structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Null"), |
| inBuf: `{"zero": 0, "one": null, "two": 2}`, |
| inVal: new(structInlineMapStringInt), |
| want: addr(structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 0, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Invalid"), |
| inBuf: `{"zero": 0, "one": {}, "two": 2}`, |
| inVal: new(structInlineMapStringInt), |
| want: addr(structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 0}, |
| }), |
| wantErr: EU(nil).withPos(`{"zero": 0, "one": `, "/one").withType('{', T[int]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapStringInt/StringifiedNumbers"), |
| opts: []Options{StringifyNumbers(true)}, |
| inBuf: `{"zero": "0", "one": "1", "two": "2"}`, |
| inVal: new(structInlineMapStringInt), |
| want: addr(structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/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(structInlineMapStringInt), |
| want: addr(structInlineMapStringInt{ |
| X: map[string]int{"zero": 0, "one": 1, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt"), |
| inBuf: `{"zero": 0, "one": 1, "two": 2}`, |
| inVal: new(structInlineMapNamedStringInt), |
| want: addr(structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/Null"), |
| inBuf: `{"zero": 0, "one": null, "two": 2}`, |
| inVal: new(structInlineMapNamedStringInt), |
| want: addr(structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 0, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/Invalid"), |
| inBuf: `{"zero": 0, "one": {}, "two": 2}`, |
| inVal: new(structInlineMapNamedStringInt), |
| want: addr(structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 0}, |
| }), |
| wantErr: EU(nil).withPos(`{"zero": 0, "one": `, "/one").withType('{', T[int]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/StringifiedNumbers"), |
| opts: []Options{StringifyNumbers(true)}, |
| inBuf: `{"zero": "0", "one": 1, "two": "2"}`, |
| inVal: new(structInlineMapNamedStringInt), |
| want: addr(structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 0}, |
| }), |
| wantErr: EU(nil).withPos(`{"zero": "0", "one": `, "/one").withType('0', T[int]()), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/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(structInlineMapNamedStringInt), |
| want: addr(structInlineMapNamedStringInt{ |
| X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, |
| }), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/Noop"), |
| inBuf: `{"A":1,"B":2}`, |
| inVal: new(structInlineMapNamedStringAny), |
| want: addr(structInlineMapNamedStringAny{A: 1, X: nil, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN1/Nil"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structInlineMapNamedStringAny), |
| want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN1/Empty"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: addr(structInlineMapNamedStringAny{X: map[namedString]any{}}), |
| want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN1/ObjectN1"), |
| inBuf: `{"A":1,"fizz":{"charlie":"DELTA","echo":"foxtrot"},"B":2}`, |
| inVal: addr(structInlineMapNamedStringAny{X: map[namedString]any{"fizz": jsonObject{ |
| "alpha": "bravo", |
| "charlie": "delta", |
| }}}), |
| want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": jsonObject{ |
| "alpha": "bravo", |
| "charlie": "DELTA", |
| "echo": "foxtrot", |
| }}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN2/ObjectN1"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, |
| inVal: addr(structInlineMapNamedStringAny{X: map[namedString]any{"fizz": "wuzz"}}), |
| want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz", "foo": jsonArray{1.0, 2.0, 3.0}}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeInvalidValue"), |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, |
| inVal: new(structInlineMapNamedStringAny), |
| want: addr(structInlineMapNamedStringAny{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/InlinedFallback/MapNamedStringAny/MergeInvalidValue/Existing"), |
| inBuf: `{"A":1,"fizz":nil,"B":2}`, |
| inVal: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": true}}), |
| want: addr(structInlineMapNamedStringAny{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/InlinedFallback/MapNamedStringAny/CaseSensitive"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, |
| inVal: new(structInlineMapNamedStringAny), |
| want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz", "a": 3.0}, B: 2}), |
| }, { |
| name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/RejectDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, |
| inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, |
| inVal: new(structInlineMapNamedStringAny), |
| want: addr(structInlineMapNamedStringAny{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/InlinedFallback/MapNamedStringAny/AllowDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(true)}, |
| inBuf: `{"A":1,"fizz":{"one":1,"two":-2},"B":2,"fizz":{"two":2,"three":3}}`, |
| inVal: new(structInlineMapNamedStringAny), |
| want: addr(structInlineMapNamedStringAny{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/InlinedFallback/RejectUnknownMembers"), |
| opts: []Options{RejectUnknownMembers(true)}, |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structInlineTextValue), |
| // NOTE: DiscardUnknownMembers has no effect since this is "inline". |
| want: addr(structInlineTextValue{ |
| A: 1, |
| X: jsontext.Value(`{"fizz":"buzz"}`), |
| B: 2, |
| }), |
| }, { |
| name: jsontest.Name("Structs/UnknownFallback/RejectUnknownMembers"), |
| opts: []Options{RejectUnknownMembers(true)}, |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structUnknownTextValue), |
| want: addr(structUnknownTextValue{A: 1}), |
| wantErr: EU(ErrUnknownName).withPos(`{"A":1,`, "/fizz").withType('"', T[structUnknownTextValue]()), |
| }, { |
| name: jsontest.Name("Structs/UnknownFallback"), |
| inBuf: `{"A":1,"fizz":"buzz","B":2}`, |
| inVal: new(structUnknownTextValue), |
| want: addr(structUnknownTextValue{ |
| 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/WeirdNames"), |
| inBuf: `{"":"empty",",":"comma","\"":"quote"}`, |
| inVal: new(structWeirdNames), |
| want: addr(structWeirdNames{Empty: "empty", Comma: "comma", Quote: "quote"}), |
| }, { |
| 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"), |
| inBuf: `{"aa_a":"aa_a"}`, |
| inVal: new(structNoCase), |
| want: addr(structNoCase{AaA: "aa_a"}), |
| }, { |
| 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)}, |
| inBuf: `{"AaA":"AaA","aaa":"aaa","aAa":"aAa"}`, |
| inVal: new(structNoCase), |
| want: addr(structNoCase{AaA: "aAa"}), |
| }, { |
| name: jsontest.Name("Structs/NoCase/Merge/RejectDuplicateNames"), |
| opts: []Options{jsontext.AllowDuplicateNames(false)}, |
| inBuf: `{"AaA":"AaA","aaa":"aaa"}`, |
| inVal: new(structNoCase), |
| want: addr(structNoCase{AaA: "AaA"}), |
| wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"AaA":"AaA",`)), |
| }, { |
| 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(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{AAA: "AAA", AaA: "AaA", AAa: "AAa", Aaa: "Aaa"}), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCase/ExactConflict"), |
| inBuf: `{"AAA":"AAA","AAA":"AAA"}`, |
| inVal: addr(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{AAA: "AAA"}), |
| wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"AAA",`)), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCase/OverwriteExact"), |
| inBuf: `{"AAA":"after"}`, |
| inVal: addr(structNoCaseInlineTextValue{AAA: "before"}), |
| want: addr(structNoCaseInlineTextValue{AAA: "after"}), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCase/NoCaseConflict"), |
| inBuf: `{"aaa":"aaa","aaA":"aaA"}`, |
| inVal: addr(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{AaA: "aaa"}), |
| wantErr: newDuplicateNameError("", []byte(`"aaA"`), len64(`{"aaa":"aaa",`)), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/NoCase/OverwriteNoCase"), |
| inBuf: `{"aaa":"aaa","aaA":"aaA"}`, |
| inVal: addr(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{AaA: "aaa"}), |
| wantErr: newDuplicateNameError("", []byte(`"aaA"`), len64(`{"aaa":"aaa",`)), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/Inline/Unknown"), |
| inBuf: `{"unknown":""}`, |
| inVal: addr(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"unknown":""}`)}), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/Inline/UnknownMerge"), |
| inBuf: `{"unknown":""}`, |
| inVal: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"unknown":""}`)}), |
| want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"unknown":"","unknown":""}`)}), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/Inline/NoCaseOkay"), |
| inBuf: `{"b":"","B":""}`, |
| inVal: addr(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"b":"","B":""}`)}), |
| }, { |
| name: jsontest.Name("Structs/DuplicateName/Inline/ExactConflict"), |
| inBuf: `{"b":"","b":""}`, |
| inVal: addr(structNoCaseInlineTextValue{}), |
| want: addr(structNoCaseInlineTextValue{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 or single quote)")).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/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"), |
| 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/SkipFunc"), |
| inBuf: `{}`, |
| inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { |
| return SkipFunc |
| })), |
| wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).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/SkipFunc"), |
| inBuf: `{}`, |
| inVal: addr(unmarshalJSONv1Func(func([]byte) error { |
| return SkipFunc |
| })), |
| wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal 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/SkipFunc"), |
| inBuf: `"value"`, |
| inVal: addr(unmarshalTextFunc(func([]byte) error { |
| return SkipFunc |
| })), |
| wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal 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 SkipFunc |
| })), |
| }, |
| inBuf: `""`, |
| inVal: addr(""), |
| want: addr(""), |
| wantErr: EU(wrapSkipFunc(SkipFunc, "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 SkipFunc |
| })), |
| }, |
| 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 SkipFunc |
| })), |
| }, |
| inBuf: `""`, |
| inVal: addr(""), |
| want: addr(""), |
| wantErr: EU(errSkipMutation).withType(0, reflect.PointerTo(stringType)), |
| }, { |
| name: jsontest.Name("Functions/String/V2/WrappedSkipError"), |
| opts: []Options{ |
| WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { |
| return fmt.Errorf("wrap: %w", SkipFunc) |
| })), |
| }, |
| inBuf: `""`, |
| inVal: addr(""), |
| want: addr(""), |
| wantErr: EU(fmt.Errorf("wrap: %w", SkipFunc)).withType(0, reflect.PointerTo(stringType)), |
| }, { |
| 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/Inlined"), |
| opts: []Options{ |
| WithUnmarshalers(JoinUnmarshalers( |
| UnmarshalFunc(func([]byte, *structInlinedL1) 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(structInlined), |
| want: addr(structInlined{ |
| X: structInlinedL1{ |
| X: &structInlinedL2{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 SkipFunc |
| })), |
| }, |
| 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 SkipFunc |
| })), |
| }, |
| 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 SkipFunc |
| })), |
| }, |
| 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 SkipFunc |
| }), |
| 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"), |
| 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", |
| }), |
| 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 SkipFunc |
| } |
| 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 SkipFunc |
| } |
| } |
| 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 SkipFunc |
| } |
| } |
| 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, |
| ) |
| }()), |
| }, |
| }, { |
| 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 SkipFunc |
| }), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| 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"), |
| inBuf: `{"D1":45296078090012,"D2":"12h34m56.078090012s"}`, |
| opts: []Options{jsonflags.FormatDurationAsNano | 1}, |
| 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"), |
| 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"), |
| 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/UnixString/InvalidNumber"), |
| 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"), |
| 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"), |
| 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"), |
| 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 { |
| 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"}}, |
| } |
| 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) |
| } |
| } |
| }) |
| } |
| } |
| |
| 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 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 SkipFunc |
| })), // 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), // should be ignored |
| WithUnmarshalers(nil), // should override |
| ); 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( |
| jsontext.AllowInvalidUTF8(false), // should be ignored |
| 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 SkipFunc |
| })), // 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") |
| } |
| } |
| |
| 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 SkipFunc |
| })), // 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), // should be ignored |
| 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( |
| jsontext.AllowInvalidUTF8(false), // should be ignored |
| 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 SkipFunc |
| })), // 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") |
| } |
| } |
| |
| // 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())) |
| } |