blob: 75093345a3b93eb6f6a41cfec64750bf78d3c74d [file] [log] [blame]
// 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()))
}