From 18d087e00953bf2b01f94d616cd9ce6101d2b9e6 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Tue, 8 Oct 2024 15:37:02 -0400 Subject: [PATCH] sync go1.23 changes from encoding/json --- go.mod | 2 +- internal/golang/encoding/json/bench_test.go | 215 ++- internal/golang/encoding/json/decode.go | 274 +-- internal/golang/encoding/json/decode_test.go | 1657 +++++++++-------- internal/golang/encoding/json/encode.go | 508 ++--- internal/golang/encoding/json/encode_test.go | 654 +++---- internal/golang/encoding/json/fold.go | 150 +- internal/golang/encoding/json/fold_test.go | 138 +- internal/golang/encoding/json/indent.go | 119 +- internal/golang/encoding/json/number_test.go | 15 - internal/golang/encoding/json/scanner.go | 4 +- internal/golang/encoding/json/scanner_test.go | 223 +-- internal/golang/encoding/json/stream.go | 48 +- internal/golang/encoding/json/stream_test.go | 440 +++-- internal/golang/encoding/json/tagkey_test.go | 83 +- internal/golang/encoding/json/tags_test.go | 2 +- 16 files changed, 2207 insertions(+), 2325 deletions(-) diff --git a/go.mod b/go.mod index acef668..e01939b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module sigs.k8s.io/json -go 1.18 +go 1.21 diff --git a/internal/golang/encoding/json/bench_test.go b/internal/golang/encoding/json/bench_test.go index 635050b..032114c 100644 --- a/internal/golang/encoding/json/bench_test.go +++ b/internal/golang/encoding/json/bench_test.go @@ -14,9 +14,11 @@ import ( "bytes" "compress/gzip" "fmt" + "internal/testenv" "io" "os" "reflect" + "regexp" "runtime" "strings" "sync" @@ -91,7 +93,37 @@ func BenchmarkCodeEncoder(b *testing.B) { enc := NewEncoder(io.Discard) for pb.Next() { if err := enc.Encode(&codeStruct); err != nil { - b.Fatal("Encode:", err) + b.Fatalf("Encode error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeEncoderError(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + b.RunParallel(func(pb *testing.PB) { + enc := NewEncoder(io.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatalf("Encode error: %v", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("Marshal error: got nil, want non-nil") } } }) @@ -108,7 +140,36 @@ func BenchmarkCodeMarshal(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := Marshal(&codeStruct); err != nil { - b.Fatal("Marshal:", err) + b.Fatalf("Marshal error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeMarshalError(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(&codeStruct); err != nil { + b.Fatalf("Marshal error: %v", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("Marshal error: got nil, want non-nil") } } }) @@ -127,7 +188,37 @@ func benchMarshalBytes(n int) func(*testing.B) { return func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err := Marshal(v); err != nil { - b.Fatal("Marshal:", err) + b.Fatalf("Marshal error: %v", err) + } + } + } +} + +func benchMarshalBytesError(n int) func(*testing.B) { + sample := []byte("hello world") + // Use a struct pointer, to avoid an allocation when passing it as an + // interface parameter to Marshal. + v := &struct { + Bytes []byte + }{ + bytes.Repeat(sample, (n/len(sample))+1)[:n], + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Marshal(v); err != nil { + b.Fatalf("Marshal error: %v", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("Marshal error: got nil, want non-nil") } } } @@ -144,6 +235,33 @@ func BenchmarkMarshalBytes(b *testing.B) { b.Run("4096", benchMarshalBytes(4096)) } +func BenchmarkMarshalBytesError(b *testing.B) { + b.ReportAllocs() + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytesError(32)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytesError(256)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytesError(4096)) +} + +func BenchmarkMarshalMap(b *testing.B) { + b.ReportAllocs() + m := map[string]int{ + "key3": 3, + "key2": 2, + "key1": 1, + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(m); err != nil { + b.Fatal("Marshal:", err) + } + } + }) +} + func BenchmarkCodeDecoder(b *testing.B) { b.ReportAllocs() if codeJSON == nil { @@ -162,7 +280,7 @@ func BenchmarkCodeDecoder(b *testing.B) { buf.WriteByte('\n') buf.WriteByte('\n') if err := dec.Decode(&r); err != nil { - b.Fatal("Decode:", err) + b.Fatalf("Decode error: %v", err) } } }) @@ -179,7 +297,7 @@ func BenchmarkUnicodeDecoder(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { if err := dec.Decode(&out); err != nil { - b.Fatal("Decode:", err) + b.Fatalf("Decode error: %v", err) } r.Seek(0, 0) } @@ -193,7 +311,7 @@ func BenchmarkDecoderStream(b *testing.B) { buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") var x any if err := dec.Decode(&x); err != nil { - b.Fatal("Decode:", err) + b.Fatalf("Decode error: %v", err) } ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" b.StartTimer() @@ -202,8 +320,11 @@ func BenchmarkDecoderStream(b *testing.B) { buf.WriteString(ones) } x = nil - if err := dec.Decode(&x); err != nil || x != 1.0 { - b.Fatalf("Decode: %v after %d", err, i) + switch err := dec.Decode(&x); { + case err != nil: + b.Fatalf("Decode error: %v", err) + case x != 1.0: + b.Fatalf("Decode: got %v want 1.0", i) } } } @@ -219,7 +340,7 @@ func BenchmarkCodeUnmarshal(b *testing.B) { for pb.Next() { var r codeResponse if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -237,7 +358,7 @@ func BenchmarkCodeUnmarshalReuse(b *testing.B) { var r codeResponse for pb.Next() { if err := Unmarshal(codeJSON, &r); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -251,7 +372,7 @@ func BenchmarkUnmarshalString(b *testing.B) { var s string for pb.Next() { if err := Unmarshal(data, &s); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -264,7 +385,7 @@ func BenchmarkUnmarshalFloat64(b *testing.B) { var f float64 for pb.Next() { if err := Unmarshal(data, &f); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -277,7 +398,20 @@ func BenchmarkUnmarshalInt64(b *testing.B) { var x int64 for pb.Next() { if err := Unmarshal(data, &x); err != nil { - b.Fatal("Unmarshal:", err) + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkUnmarshalMap(b *testing.B) { + b.ReportAllocs() + data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`) + b.RunParallel(func(pb *testing.PB) { + x := make(map[string]string, 3) + for pb.Next() { + if err := Unmarshal(data, &x); err != nil { + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -290,7 +424,7 @@ func BenchmarkIssue10335(b *testing.B) { var s struct{} for pb.Next() { if err := Unmarshal(j, &s); err != nil { - b.Fatal(err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -306,7 +440,7 @@ func BenchmarkIssue34127(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := Marshal(&j); err != nil { - b.Fatal(err) + b.Fatalf("Marshal error: %v", err) } } }) @@ -319,7 +453,7 @@ func BenchmarkUnmapped(b *testing.B) { var s struct{} for pb.Next() { if err := Unmarshal(j, &s); err != nil { - b.Fatal(err) + b.Fatalf("Unmarshal error: %v", err) } } }) @@ -328,12 +462,14 @@ func BenchmarkUnmapped(b *testing.B) { func BenchmarkTypeFieldsCache(b *testing.B) { b.ReportAllocs() var maxTypes int = 1e6 - maxTypes = 1e3 // restrict cache sizes on builders + if testenv.Builder() != "" { + maxTypes = 1e3 // restrict cache sizes on builders + } // Dynamically generate many new types. types := make([]reflect.Type, maxTypes) fs := []reflect.StructField{{ - Type: reflect.TypeOf(""), + Type: reflect.TypeFor[string](), Index: []int{0}, }} for i := range types { @@ -400,8 +536,49 @@ func BenchmarkEncodeMarshaler(b *testing.B) { for pb.Next() { if err := enc.Encode(&m); err != nil { - b.Fatal("Encode:", err) + b.Fatalf("Encode error: %v", err) + } + } + }) +} + +func BenchmarkEncoderEncode(b *testing.B) { + b.ReportAllocs() + type T struct { + X, Y string + } + v := &T{"foo", "bar"} + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := NewEncoder(io.Discard).Encode(v); err != nil { + b.Fatalf("Encode error: %v", err) } } }) } + +func BenchmarkNumberIsValid(b *testing.B) { + s := "-61657.61667E+61673" + for i := 0; i < b.N; i++ { + isValidNumber(s) + } +} + +func BenchmarkNumberIsValidRegexp(b *testing.B) { + var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`) + s := "-61657.61667E+61673" + for i := 0; i < b.N; i++ { + jsonNumberRegexp.MatchString(s) + } +} + +func BenchmarkUnmarshalNumber(b *testing.B) { + b.ReportAllocs() + data := []byte(`"-61657.61667E+61673"`) + var number Number + for i := 0; i < b.N; i++ { + if err := Unmarshal(data, &number); err != nil { + b.Fatal("Unmarshal:", err) + } + } +} diff --git a/internal/golang/encoding/json/decode.go b/internal/golang/encoding/json/decode.go index 6a13cf2..f820570 100644 --- a/internal/golang/encoding/json/decode.go +++ b/internal/golang/encoding/json/decode.go @@ -17,14 +17,15 @@ import ( "unicode" "unicode/utf16" "unicode/utf8" + _ "unsafe" // for linkname ) // Unmarshal parses the JSON-encoded data and stores the result // in the value pointed to by v. If v is nil or not a pointer, -// Unmarshal returns an InvalidUnmarshalError. +// Unmarshal returns an [InvalidUnmarshalError]. // // Unmarshal uses the inverse of the encodings that -// Marshal uses, allocating maps, slices, and pointers as necessary, +// [Marshal] uses, allocating maps, slices, and pointers as necessary, // with the following additional rules: // // To unmarshal JSON into a pointer, Unmarshal first handles the case of @@ -33,28 +34,28 @@ import ( // the value pointed at by the pointer. If the pointer is nil, Unmarshal // allocates a new value for it to point to. // -// To unmarshal JSON into a value implementing the Unmarshaler interface, -// Unmarshal calls that value's UnmarshalJSON method, including +// To unmarshal JSON into a value implementing [Unmarshaler], +// Unmarshal calls that value's [Unmarshaler.UnmarshalJSON] method, including // when the input is a JSON null. -// Otherwise, if the value implements encoding.TextUnmarshaler -// and the input is a JSON quoted string, Unmarshal calls that value's -// UnmarshalText method with the unquoted form of the string. +// Otherwise, if the value implements [encoding.TextUnmarshaler] +// and the input is a JSON quoted string, Unmarshal calls +// [encoding.TextUnmarshaler.UnmarshalText] with the unquoted form of the string. // // To unmarshal JSON into a struct, Unmarshal matches incoming object -// keys to the keys used by Marshal (either the struct field name or its tag), +// keys to the keys used by [Marshal] (either the struct field name or its tag), // preferring an exact match but also accepting a case-insensitive match. By // default, object keys which don't have a corresponding struct field are -// ignored (see Decoder.DisallowUnknownFields for an alternative). +// ignored (see [Decoder.DisallowUnknownFields] for an alternative). // // To unmarshal JSON into an interface value, // Unmarshal stores one of these in the interface value: // -// bool, for JSON booleans -// float64, for JSON numbers -// string, for JSON strings -// []interface{}, for JSON arrays -// map[string]interface{}, for JSON objects -// nil for JSON null +// - bool, for JSON booleans +// - float64, for JSON numbers +// - string, for JSON strings +// - []interface{}, for JSON arrays +// - map[string]interface{}, for JSON objects +// - nil for JSON null // // To unmarshal a JSON array into a slice, Unmarshal resets the slice length // to zero and then appends each element to the slice. @@ -72,16 +73,15 @@ import ( // use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal // reuses the existing map, keeping existing entries. Unmarshal then stores // key-value pairs from the JSON object into the map. The map's key type must -// either be any string type, an integer, implement json.Unmarshaler, or -// implement encoding.TextUnmarshaler. +// either be any string type, an integer, or implement [encoding.TextUnmarshaler]. // -// If the JSON-encoded data contain a syntax error, Unmarshal returns a SyntaxError. +// If the JSON-encoded data contain a syntax error, Unmarshal returns a [SyntaxError]. // // If a JSON value is not appropriate for a given target type, // or if a JSON number overflows the target type, Unmarshal // skips that field and completes the unmarshaling as best it can. // If no more serious errors are encountered, Unmarshal returns -// an UnmarshalTypeError describing the earliest such error. In any +// an [UnmarshalTypeError] describing the earliest such error. In any // case, it's not guaranteed that all the remaining fields following // the problematic one will be unmarshaled into the target object. // @@ -94,16 +94,11 @@ import ( // invalid UTF-16 surrogate pairs are not treated as an error. // Instead, they are replaced by the Unicode replacement // character U+FFFD. -func Unmarshal(data []byte, v any, opts ...UnmarshalOpt) error { +func Unmarshal(data []byte, v any) error { // Check for well-formedness. // Avoids filling out half a data structure // before discovering a JSON syntax error. var d decodeState - - for _, opt := range opts { - opt(&d) - } - err := checkValid(data, &d.scan) if err != nil { return err @@ -119,13 +114,12 @@ func Unmarshal(data []byte, v any, opts ...UnmarshalOpt) error { // a JSON value. UnmarshalJSON must copy the JSON data // if it wishes to retain the data after returning. // -// By convention, to approximate the behavior of Unmarshal itself, +// By convention, to approximate the behavior of [Unmarshal] itself, // Unmarshalers implement UnmarshalJSON([]byte("null")) as a no-op. type Unmarshaler interface { UnmarshalJSON([]byte) error } -/* // An UnmarshalTypeError describes a JSON value that was // not appropriate for a value of a specific Go type. type UnmarshalTypeError struct { @@ -157,8 +151,8 @@ func (e *UnmarshalFieldError) Error() string { return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() } -// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. -// (The argument to Unmarshal must be a non-nil pointer.) +// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal]. +// (The argument to [Unmarshal] must be a non-nil pointer.) type InvalidUnmarshalError struct { Type reflect.Type } @@ -173,7 +167,6 @@ func (e *InvalidUnmarshalError) Error() string { } return "json: Unmarshal(nil " + e.Type.String() + ")" } -*/ func (d *decodeState) unmarshal(v any) error { rv := reflect.ValueOf(v) @@ -189,16 +182,9 @@ func (d *decodeState) unmarshal(v any) error { if err != nil { return d.addErrorContext(err) } - if d.savedError != nil { - return d.savedError - } - if len(d.savedStrictErrors) > 0 { - return &UnmarshalStrictError{Errors: d.savedStrictErrors} - } - return nil + return d.savedError } -/* // A Number represents a JSON number literal. type Number string @@ -214,7 +200,6 @@ func (n Number) Float64() (float64, error) { func (n Number) Int64() (int64, error) { return strconv.ParseInt(string(n), 10, 64) } -*/ // An errorContext provides context for type errors during decoding. type errorContext struct { @@ -232,16 +217,6 @@ type decodeState struct { savedError error useNumber bool disallowUnknownFields bool - - savedStrictErrors []error - seenStrictErrors map[strictError]struct{} - strictFieldStack []string - - caseSensitive bool - - preserveInts bool - - disallowDuplicateFields bool } // readIndex returns the position of the last byte read. @@ -263,8 +238,6 @@ func (d *decodeState) init(data []byte) *decodeState { // Reuse the allocated space for the FieldStack slice. d.errorContext.FieldStack = d.errorContext.FieldStack[:0] } - // Reuse the allocated space for the strict FieldStack slice. - d.strictFieldStack = d.strictFieldStack[:0] return d } @@ -559,12 +532,6 @@ func (d *decodeState) array(v reflect.Value) error { break } - origStrictFieldStackLen := len(d.strictFieldStack) - defer func() { - // Reset to original length and reuse the allocated space for the strict FieldStack slice. - d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] - }() - i := 0 for { // Look ahead for ] - can only happen on first iteration. @@ -573,24 +540,16 @@ func (d *decodeState) array(v reflect.Value) error { break } - // Get element of array, growing if necessary. + // Expand slice length, growing the slice if necessary. if v.Kind() == reflect.Slice { - // Grow slice if necessary if i >= v.Cap() { - newcap := v.Cap() + v.Cap()/2 - if newcap < 4 { - newcap = 4 - } - newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) - reflect.Copy(newv, v) - v.Set(newv) + v.Grow(1) } if i >= v.Len() { v.SetLen(i + 1) } } - d.appendStrictFieldStackIndex(i) if i < v.Len() { // Decode into element. if err := d.value(v.Index(i)); err != nil { @@ -602,8 +561,6 @@ func (d *decodeState) array(v reflect.Value) error { return err } } - // Reset to original length and reuse the allocated space for the strict FieldStack slice. - d.strictFieldStack = d.strictFieldStack[:origStrictFieldStackLen] i++ // Next token must be , or ]. @@ -620,13 +577,11 @@ func (d *decodeState) array(v reflect.Value) error { if i < v.Len() { if v.Kind() == reflect.Array { - // Array. Zero the rest. - z := reflect.Zero(v.Type().Elem()) for ; i < v.Len(); i++ { - v.Index(i).Set(z) + v.Index(i).SetZero() // zero remainder of array } } else { - v.SetLen(i) + v.SetLen(i) // truncate the slice } } if i == 0 && v.Kind() == reflect.Slice { @@ -636,7 +591,7 @@ func (d *decodeState) array(v reflect.Value) error { } var nullLiteral = []byte("null") -var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +var textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() // object consumes an object from d.data[d.off-1:], decoding into v. // The first byte ('{') of the object has been read already. @@ -664,7 +619,6 @@ func (d *decodeState) object(v reflect.Value) error { } var fields structFields - var checkDuplicateField func(fieldNameIndex int, fieldName string) // Check type of target: // struct or @@ -688,51 +642,8 @@ func (d *decodeState) object(v reflect.Value) error { if v.IsNil() { v.Set(reflect.MakeMap(t)) } - - if d.disallowDuplicateFields { - var seenKeys map[string]struct{} - checkDuplicateField = func(fieldNameIndex int, fieldName string) { - if seenKeys == nil { - seenKeys = map[string]struct{}{} - } - if _, seen := seenKeys[fieldName]; seen { - d.saveStrictError(d.newFieldError(duplicateStrictErrType, fieldName)) - } else { - seenKeys[fieldName] = struct{}{} - } - } - } - case reflect.Struct: fields = cachedTypeFields(t) - - if d.disallowDuplicateFields { - if len(fields.list) <= 64 { - // bitset by field index for structs with <= 64 fields - var seenKeys uint64 - checkDuplicateField = func(fieldNameIndex int, fieldName string) { - if seenKeys&(1<` + " [\u2028 \u2029]" - const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` - b, err := Marshal(input) + const want = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` + got, err := Marshal(input) if err != nil { t.Fatalf("Marshal error: %v", err) } - if s := string(b); s != expected { - t.Errorf("Encoding of [%s]:\n got [%s]\nwant [%s]", input, s, expected) + if string(got) != want { + t.Errorf("Marshal(%#q):\n\tgot: %s\n\twant: %s", input, got, want) } } -// WrongString is a struct that's misusing the ,string modifier. -type WrongString struct { - Message string `json:"result,string"` -} - -type wrongStringTest struct { - in, err string -} - -var wrongStringTests = []wrongStringTest{ - {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, - {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, - {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, - {`{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, - {`{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, - {`{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, -} - // If people misuse the ,string modifier, the error message should be // helpful, telling the user that they're doing it wrong. func TestErrorMessageFromMisusedString(t *testing.T) { - for n, tt := range wrongStringTests { - r := strings.NewReader(tt.in) - var s WrongString - err := NewDecoder(r).Decode(&s) - got := fmt.Sprintf("%v", err) - if got != tt.err { - t.Errorf("%d. got err = %q, want %q", n, got, tt.err) - } + // WrongString is a struct that's misusing the ,string modifier. + type WrongString struct { + Message string `json:"result,string"` } -} - -func noSpace(c rune) rune { - if isSpace(byte(c)) { //only used for ascii - return -1 + tests := []struct { + CaseName + in, err string + }{ + {Name(""), `{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, + {Name(""), `{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, + {Name(""), `{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, + {Name(""), `{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, + {Name(""), `{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, + {Name(""), `{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + r := strings.NewReader(tt.in) + var s WrongString + err := NewDecoder(r).Decode(&s) + got := fmt.Sprintf("%v", err) + if got != tt.err { + t.Errorf("%s: Decode error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.err) + } + }) } - return c } type All struct { @@ -1546,7 +1590,7 @@ var allValueIndent = `{ "PInterface": null }` -var allValueCompact = strings.Map(noSpace, allValueIndent) +var allValueCompact = stripWhitespace(allValueIndent) var pallValueIndent = `{ "Bool": false, @@ -1635,7 +1679,7 @@ var pallValueIndent = `{ "PInterface": 5.2 }` -var pallValueCompact = strings.Map(noSpace, pallValueIndent) +var pallValueCompact = stripWhitespace(pallValueIndent) func TestRefUnmarshal(t *testing.T) { type S struct { @@ -1656,10 +1700,10 @@ func TestRefUnmarshal(t *testing.T) { var got S if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if !reflect.DeepEqual(got, want) { - t.Errorf("got %+v, want %+v", got, want) + t.Errorf("Unmarsha:\n\tgot: %+v\n\twant: %+v", got, want) } } @@ -1672,13 +1716,12 @@ func TestEmptyString(t *testing.T) { } data := `{"Number1":"1", "Number2":""}` dec := NewDecoder(strings.NewReader(data)) - var t2 T2 - err := dec.Decode(&t2) - if err == nil { - t.Fatal("Decode: did not return error") - } - if t2.Number1 != 1 { - t.Fatal("Decode: did not set Number1") + var got T2 + switch err := dec.Decode(&got); { + case err == nil: + t.Fatalf("Decode error: got nil, want non-nil") + case got.Number1 != 1: + t.Fatalf("Decode: got.Number1 = %d, want 1", got.Number1) } } @@ -1695,12 +1738,13 @@ func TestNullString(t *testing.T) { s.B = 1 s.C = new(int) *s.C = 2 - err := Unmarshal(data, &s) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if s.B != 1 || s.C != nil { - t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) + switch err := Unmarshal(data, &s); { + case err != nil: + t.Fatalf("Unmarshal error: %v", err) + case s.B != 1: + t.Fatalf("Unmarshal: s.B = %d, want 1", s.B) + case s.C != nil: + t.Fatalf("Unmarshal: s.C = %d, want non-nil", s.C) } } @@ -1716,37 +1760,38 @@ func intpp(x *int) **int { return pp } -var interfaceSetTests = []struct { - pre any - json string - post any -}{ - {"foo", `"bar"`, "bar"}, - {"foo", `2`, 2.0}, - {"foo", `true`, true}, - {"foo", `null`, nil}, - - {nil, `null`, nil}, - {new(int), `null`, nil}, - {(*int)(nil), `null`, nil}, - {new(*int), `null`, new(*int)}, - {(**int)(nil), `null`, nil}, - {intp(1), `null`, nil}, - {intpp(nil), `null`, intpp(nil)}, - {intpp(intp(1)), `null`, intpp(nil)}, -} - func TestInterfaceSet(t *testing.T) { - for _, tt := range interfaceSetTests { - b := struct{ X any }{tt.pre} - blob := `{"X":` + tt.json + `}` - if err := Unmarshal([]byte(blob), &b); err != nil { - t.Errorf("Unmarshal %#q: %v", blob, err) - continue - } - if !reflect.DeepEqual(b.X, tt.post) { - t.Errorf("Unmarshal %#q into %#v: X=%#v, want %#v", blob, tt.pre, b.X, tt.post) - } + tests := []struct { + CaseName + pre any + json string + post any + }{ + {Name(""), "foo", `"bar"`, "bar"}, + {Name(""), "foo", `2`, 2.0}, + {Name(""), "foo", `true`, true}, + {Name(""), "foo", `null`, nil}, + + {Name(""), nil, `null`, nil}, + {Name(""), new(int), `null`, nil}, + {Name(""), (*int)(nil), `null`, nil}, + {Name(""), new(*int), `null`, new(*int)}, + {Name(""), (**int)(nil), `null`, nil}, + {Name(""), intp(1), `null`, nil}, + {Name(""), intpp(nil), `null`, intpp(nil)}, + {Name(""), intpp(intp(1)), `null`, intpp(nil)}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + b := struct{ X any }{tt.pre} + blob := `{"X":` + tt.json + `}` + if err := Unmarshal([]byte(blob), &b); err != nil { + t.Fatalf("%s: Unmarshal(%#q) error: %v", tt.Where, blob, err) + } + if !reflect.DeepEqual(b.X, tt.post) { + t.Errorf("%s: Unmarshal(%#q):\n\tpre.X: %#v\n\tgot.X: %#v\n\twant.X: %#v", tt.Where, blob, tt.pre, b.X, tt.post) + } + }) } } @@ -1924,24 +1969,18 @@ func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { func TestStringKind(t *testing.T) { type stringKind string - - var m1, m2 map[stringKind]int - m1 = map[stringKind]int{ - "foo": 42, - } - - data, err := Marshal(m1) + want := map[stringKind]int{"foo": 42} + data, err := Marshal(want) if err != nil { - t.Errorf("Unexpected error marshaling: %v", err) + t.Fatalf("Marshal error: %v", err) } - - err = Unmarshal(data, &m2) + var got map[stringKind]int + err = Unmarshal(data, &got) if err != nil { - t.Errorf("Unexpected error unmarshaling: %v", err) + t.Fatalf("Unmarshal error: %v", err) } - - if !reflect.DeepEqual(m1, m2) { - t.Error("Items should be equal after encoding and then decoding") + if !reflect.DeepEqual(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) } } @@ -1950,20 +1989,18 @@ func TestStringKind(t *testing.T) { // Issue 8962. func TestByteKind(t *testing.T) { type byteKind []byte - - a := byteKind("hello") - - data, err := Marshal(a) + want := byteKind("hello") + data, err := Marshal(want) if err != nil { - t.Error(err) + t.Fatalf("Marshal error: %v", err) } - var b byteKind - err = Unmarshal(data, &b) + var got byteKind + err = Unmarshal(data, &got) if err != nil { - t.Fatal(err) + t.Fatalf("Unmarshal error: %v", err) } - if !reflect.DeepEqual(a, b) { - t.Errorf("expected %v == %v", a, b) + if !slices.Equal(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) } } @@ -1971,63 +2008,68 @@ func TestByteKind(t *testing.T) { // Issue 12921. func TestSliceOfCustomByte(t *testing.T) { type Uint8 uint8 - - a := []Uint8("hello") - - data, err := Marshal(a) + want := []Uint8("hello") + data, err := Marshal(want) if err != nil { - t.Fatal(err) + t.Fatalf("Marshal error: %v", err) } - var b []Uint8 - err = Unmarshal(data, &b) + var got []Uint8 + err = Unmarshal(data, &got) if err != nil { - t.Fatal(err) + t.Fatalf("Unmarshal error: %v", err) } - if !reflect.DeepEqual(a, b) { - t.Fatalf("expected %v == %v", a, b) + if !slices.Equal(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) } } -var decodeTypeErrorTests = []struct { - dest any - src string -}{ - {new(string), `{"user": "name"}`}, // issue 4628. - {new(error), `{}`}, // issue 4222 - {new(error), `[]`}, - {new(error), `""`}, - {new(error), `123`}, - {new(error), `true`}, -} - func TestUnmarshalTypeError(t *testing.T) { - for _, item := range decodeTypeErrorTests { - err := Unmarshal([]byte(item.src), item.dest) - if _, ok := err.(*UnmarshalTypeError); !ok { - t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", - item.src, item.dest, err) - } + tests := []struct { + CaseName + dest any + in string + }{ + {Name(""), new(string), `{"user": "name"}`}, // issue 4628. + {Name(""), new(error), `{}`}, // issue 4222 + {Name(""), new(error), `[]`}, + {Name(""), new(error), `""`}, + {Name(""), new(error), `123`}, + {Name(""), new(error), `true`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), tt.dest) + if _, ok := err.(*UnmarshalTypeError); !ok { + t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %T\n\twant: %T", + tt.Where, tt.in, tt.dest, err, new(UnmarshalTypeError)) + } + }) } } -var unmarshalSyntaxTests = []string{ - "tru", - "fals", - "nul", - "123e", - `"hello`, - `[1,2,3`, - `{"key":1`, - `{"key":1,`, -} - func TestUnmarshalSyntax(t *testing.T) { var x any - for _, src := range unmarshalSyntaxTests { - err := Unmarshal([]byte(src), &x) - if _, ok := err.(*SyntaxError); !ok { - t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) - } + tests := []struct { + CaseName + in string + }{ + {Name(""), "tru"}, + {Name(""), "fals"}, + {Name(""), "nul"}, + {Name(""), "123e"}, + {Name(""), `"hello`}, + {Name(""), `[1,2,3`}, + {Name(""), `{"key":1`}, + {Name(""), `{"key":1,`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), &x) + if _, ok := err.(*SyntaxError); !ok { + t.Errorf("%s: Unmarshal(%#q, any):\n\tgot: %T\n\twant: %T", + tt.Where, tt.in, err, new(SyntaxError)) + } + }) } } @@ -2048,10 +2090,10 @@ func TestUnmarshalUnexported(t *testing.T) { out := &unexportedFields{} err := Unmarshal([]byte(input), out) if err != nil { - t.Errorf("got error %v, expected nil", err) + t.Errorf("Unmarshal error: %v", err) } if !reflect.DeepEqual(out, want) { - t.Errorf("got %q, want %q", out, want) + t.Errorf("Unmarshal:\n\tgot: %+v\n\twant: %+v", out, want) } } @@ -2073,12 +2115,11 @@ func (t *Time3339) UnmarshalJSON(b []byte) error { func TestUnmarshalJSONLiteralError(t *testing.T) { var t3 Time3339 - err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) - if err == nil { - t.Fatalf("expected error; got time %v", time.Time(t3)) - } - if !strings.Contains(err.Error(), "range") { - t.Errorf("got err = %v; want out of range error", err) + switch err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3); { + case err == nil: + t.Fatalf("Unmarshal error: got nil, want non-nil") + case !strings.Contains(err.Error(), "range"): + t.Errorf("Unmarshal error:\n\tgot: %v\n\twant: out of range", err) } } @@ -2091,7 +2132,7 @@ func TestSkipArrayObjects(t *testing.T) { err := Unmarshal([]byte(json), &dest) if err != nil { - t.Errorf("got error %q, want nil", err) + t.Errorf("Unmarshal error: %v", err) } } @@ -2100,99 +2141,102 @@ func TestSkipArrayObjects(t *testing.T) { // Issues 4900 and 8837, among others. func TestPrefilled(t *testing.T) { // Values here change, cannot reuse table across runs. - var prefillTests = []struct { + tests := []struct { + CaseName in string ptr any out any - }{ - { - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, - { - in: `{"X": 1, "Y": 2}`, - ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, - out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, - }, - { - in: `[2]`, - ptr: &[]int{1}, - out: &[]int{2}, - }, - { - in: `[2, 3]`, - ptr: &[]int{1}, - out: &[]int{2, 3}, - }, - { - in: `[2, 3]`, - ptr: &[...]int{1}, - out: &[...]int{2}, - }, - { - in: `[3]`, - ptr: &[...]int{1, 2}, - out: &[...]int{3, 0}, - }, - } - - for _, tt := range prefillTests { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) - } + }{{ + CaseName: Name(""), + in: `{"X": 1, "Y": 2}`, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + }, { + CaseName: Name(""), + in: `{"X": 1, "Y": 2}`, + ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, + out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, + }, { + CaseName: Name(""), + in: `[2]`, + ptr: &[]int{1}, + out: &[]int{2}, + }, { + CaseName: Name(""), + in: `[2, 3]`, + ptr: &[]int{1}, + out: &[]int{2, 3}, + }, { + CaseName: Name(""), + in: `[2, 3]`, + ptr: &[...]int{1}, + out: &[...]int{2}, + }, { + CaseName: Name(""), + in: `[3]`, + ptr: &[...]int{1, 2}, + out: &[...]int{3, 0}, + }} + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + ptrstr := fmt.Sprintf("%v", tt.ptr) + err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here + if err != nil { + t.Errorf("%s: Unmarshal error: %v", tt.Where, err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %v\n\twant: %v", tt.Where, tt.in, ptrstr, tt.ptr, tt.out) + } + }) } } -var invalidUnmarshalTests = []struct { - v any - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, -} - func TestInvalidUnmarshal(t *testing.T) { buf := []byte(`{"a":"1"}`) - for _, tt := range invalidUnmarshalTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } + tests := []struct { + CaseName + v any + want string + }{ + {Name(""), nil, "json: Unmarshal(nil)"}, + {Name(""), struct{}{}, "json: Unmarshal(non-pointer struct {})"}, + {Name(""), (*int)(nil), "json: Unmarshal(nil *int)"}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal(buf, tt.v) + if err == nil { + t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) + } + if got := err.Error(); got != tt.want { + t.Errorf("%s: Unmarshal error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) + } + }) } } -var invalidUnmarshalTextTests = []struct { - v any - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, - {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, -} - func TestInvalidUnmarshalText(t *testing.T) { buf := []byte(`123`) - for _, tt := range invalidUnmarshalTextTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } + tests := []struct { + CaseName + v any + want string + }{ + {Name(""), nil, "json: Unmarshal(nil)"}, + {Name(""), struct{}{}, "json: Unmarshal(non-pointer struct {})"}, + {Name(""), (*int)(nil), "json: Unmarshal(nil *int)"}, + {Name(""), new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal(buf, tt.v) + if err == nil { + t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) + } + if got := err.Error(); got != tt.want { + t.Errorf("%s: Unmarshal error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.want) + } + }) } } @@ -2211,12 +2255,12 @@ func TestInvalidStringOption(t *testing.T) { data, err := Marshal(item) if err != nil { - t.Fatalf("Marshal: %v", err) + t.Fatalf("Marshal error: %v", err) } err = Unmarshal(data, &item) if err != nil { - t.Fatalf("Unmarshal: %v", err) + t.Fatalf("Unmarshal error: %v", err) } } @@ -2275,43 +2319,50 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { ) tests := []struct { + CaseName in string ptr any out any err error }{{ // Error since we cannot set S1.embed1, but still able to set S1.R. - in: `{"R":2,"Q":1}`, - ptr: new(S1), - out: &S1{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S1), + out: &S1{R: 2}, + err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), }, { // The top level Q field takes precedence. - in: `{"Q":1}`, - ptr: new(S2), - out: &S2{Q: 1}, + CaseName: Name(""), + in: `{"Q":1}`, + ptr: new(S2), + out: &S2{Q: 1}, }, { // No issue with non-pointer variant. - in: `{"R":2,"Q":1}`, - ptr: new(S3), - out: &S3{embed1: embed1{Q: 1}, R: 2}, + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S3), + out: &S3{embed1: embed1{Q: 1}, R: 2}, }, { // No error since both embedded structs have field R, which annihilate each other. // Thus, no attempt is made at setting S4.embed1. - in: `{"R":2}`, - ptr: new(S4), - out: new(S4), + CaseName: Name(""), + in: `{"R":2}`, + ptr: new(S4), + out: new(S4), }, { // Error since we cannot set S5.embed1, but still able to set S5.R. - in: `{"R":2,"Q":1}`, - ptr: new(S5), - out: &S5{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S5), + out: &S5{R: 2}, + err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), }, { // Issue 24152, ensure decodeState.indirect does not panic. - in: `{"embed1": {"Q": 1}}`, - ptr: new(S6), - out: &S6{embed1{1}}, + CaseName: Name(""), + in: `{"embed1": {"Q": 1}}`, + ptr: new(S6), + out: &S6{embed1{1}}, }, { // Issue 24153, check that we can still set forwarded fields even in // the presence of a name conflict. @@ -2325,64 +2376,74 @@ func TestUnmarshalEmbeddedUnexported(t *testing.T) { // it should be impossible for an external package to set either Q. // // It is probably okay for a future reflect change to break this. - in: `{"embed1": {"Q": 1}, "Q": 2}`, - ptr: new(S7), - out: &S7{embed1{1}, embed2{2}}, + CaseName: Name(""), + in: `{"embed1": {"Q": 1}, "Q": 2}`, + ptr: new(S7), + out: &S7{embed1{1}, embed2{2}}, }, { // Issue 24153, similar to the S7 case. - in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, - ptr: new(S8), - out: &S8{embed1{1}, embed2{2}, 3}, + CaseName: Name(""), + in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, + ptr: new(S8), + out: &S8{embed1{1}, embed2{2}, 3}, }, { // Issue 228145, similar to the cases above. - in: `{"embed": {}}`, - ptr: new(S9), - out: &S9{}, + CaseName: Name(""), + in: `{"embed": {}}`, + ptr: new(S9), + out: &S9{}, }} - - for i, tt := range tests { - err := Unmarshal([]byte(tt.in), tt.ptr) - if !equalError(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("#%d: mismatch\ngot: %#+v\nwant: %#+v", i, tt.ptr, tt.out) - } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), tt.ptr) + if !equalError(err, tt.err) { + t.Errorf("%s: Unmarshal error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("%s: Unmarshal:\n\tgot: %#+v\n\twant: %#+v", tt.Where, tt.ptr, tt.out) + } + }) } } func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { tests := []struct { + CaseName in string err error }{{ - in: `1 false null :`, - err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, + CaseName: Name(""), + in: `1 false null :`, + err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, }, { - in: `1 [] [,]`, - err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, + CaseName: Name(""), + in: `1 [] [,]`, + err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, }, { - in: `1 [] [true:]`, - err: &SyntaxError{"invalid character ':' after array element", 11}, + CaseName: Name(""), + in: `1 [] [true:]`, + err: &SyntaxError{"invalid character ':' after array element", 11}, }, { - in: `1 {} {"x"=}`, - err: &SyntaxError{"invalid character '=' after object key", 14}, + CaseName: Name(""), + in: `1 {} {"x"=}`, + err: &SyntaxError{"invalid character '=' after object key", 14}, }, { - in: `falsetruenul#`, - err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, + CaseName: Name(""), + in: `falsetruenul#`, + err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, }} - for i, tt := range tests { - dec := NewDecoder(strings.NewReader(tt.in)) - var err error - for { - var v any - if err = dec.Decode(&v); err != nil { - break + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + dec := NewDecoder(strings.NewReader(tt.in)) + var err error + for err == nil { + var v any + err = dec.Decode(&v) } - } - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) - } + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("%s: Decode error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } + }) } } @@ -2408,7 +2469,7 @@ func TestUnmarshalRecursivePointer(t *testing.T) { data := []byte(`{"a": "b"}`) if err := Unmarshal(data, v); err != nil { - t.Fatal(err) + t.Fatalf("Unmarshal error: %v", err) } } @@ -2424,11 +2485,11 @@ func (m *textUnmarshalerString) UnmarshalText(text []byte) error { func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { var p map[textUnmarshalerString]string if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if _, ok := p["foo"]; !ok { - t.Errorf(`Key "foo" does not exist in map: %v`, p) + t.Errorf(`key "foo" missing in map: %v`, p) } } @@ -2436,28 +2497,28 @@ func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { // See golang.org/issues/38105. var p map[textUnmarshalerString]string if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + t.Fatalf("Unmarshal error: %v", err) } if _, ok := p["开源"]; !ok { - t.Errorf(`Key "开源" does not exist in map: %v`, p) + t.Errorf(`key "开源" missing in map: %v`, p) } // See golang.org/issues/38126. type T struct { F1 string `json:"F1,string"` } - t1 := T{"aaa\tbbb"} + wantT := T{"aaa\tbbb"} - b, err := Marshal(t1) + b, err := Marshal(wantT) if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) + t.Fatalf("Marshal error: %v", err) } - var t2 T - if err := Unmarshal(b, &t2); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + var gotT T + if err := Unmarshal(b, &gotT); err != nil { + t.Fatalf("Unmarshal error: %v", err) } - if t1 != t2 { - t.Errorf("Marshal and Unmarshal roundtrip mismatch: want %q got %q", t1, t2) + if gotT != wantT { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) } // See golang.org/issues/39555. @@ -2465,107 +2526,93 @@ func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { encoded, err := Marshal(input) if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) + t.Fatalf("Marshal error: %v", err) } var got map[textUnmarshalerString]string if err := Unmarshal(encoded, &got); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) + t.Fatalf("Unmarshal error: %v", err) } want := map[textUnmarshalerString]string{"foo": "", `"`: ""} - if !reflect.DeepEqual(want, got) { - t.Fatalf("Unexpected roundtrip result:\nwant: %q\ngot: %q", want, got) + if !reflect.DeepEqual(got, want) { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) } } func TestUnmarshalMaxDepth(t *testing.T) { - testcases := []struct { - name string + tests := []struct { + CaseName data string errMaxDepth bool - }{ - { - name: "ArrayUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, - errMaxDepth: false, - }, - { - name: "ArrayOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, - errMaxDepth: true, - }, - { - name: "ArrayOverStackDepth", - data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, - errMaxDepth: false, - }, - { - name: "ObjectOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectOverStackDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, - errMaxDepth: true, - }, - } + }{{ + CaseName: Name("ArrayUnderMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, + errMaxDepth: false, + }, { + CaseName: Name("ArrayOverMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ArrayOverStackDepth"), + data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ObjectUnderMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, + errMaxDepth: false, + }, { + CaseName: Name("ObjectOverMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ObjectOverStackDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, + errMaxDepth: true, + }} targets := []struct { - name string + CaseName newValue func() any - }{ - { - name: "unstructured", - newValue: func() any { - var v any - return &v - }, + }{{ + CaseName: Name("unstructured"), + newValue: func() any { + var v any + return &v }, - { - name: "typed named field", - newValue: func() any { - v := struct { - A any `json:"a"` - }{} - return &v - }, + }, { + CaseName: Name("typed named field"), + newValue: func() any { + v := struct { + A any `json:"a"` + }{} + return &v }, - { - name: "typed missing field", - newValue: func() any { - v := struct { - B any `json:"b"` - }{} - return &v - }, + }, { + CaseName: Name("typed missing field"), + newValue: func() any { + v := struct { + B any `json:"b"` + }{} + return &v }, - { - name: "custom unmarshaler", - newValue: func() any { - v := unmarshaler{} - return &v - }, + }, { + CaseName: Name("custom unmarshaler"), + newValue: func() any { + v := unmarshaler{} + return &v }, - } + }} - for _, tc := range testcases { + for _, tt := range tests { for _, target := range targets { - t.Run(target.name+"-"+tc.name, func(t *testing.T) { - err := Unmarshal([]byte(tc.data), target.newValue()) - if !tc.errMaxDepth { + t.Run(target.Name+"-"+tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.data), target.newValue()) + if !tt.errMaxDepth { if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("%s: %s: Unmarshal error: %v", tt.Where, target.Where, err) } } else { - if err == nil { - t.Errorf("expected error containing 'exceeded max depth', got none") - } else if !strings.Contains(err.Error(), "exceeded max depth") { - t.Errorf("expected error containing 'exceeded max depth', got: %v", err) + if err == nil || !strings.Contains(err.Error(), "exceeded max depth") { + t.Errorf("%s: %s: Unmarshal error:\n\tgot: %v\n\twant: exceeded max depth", tt.Where, target.Where, err) } } }) diff --git a/internal/golang/encoding/json/encode.go b/internal/golang/encoding/json/encode.go index 5b67251..7bee1a6 100644 --- a/internal/golang/encoding/json/encode.go +++ b/internal/golang/encoding/json/encode.go @@ -12,45 +12,48 @@ package json import ( "bytes" + "cmp" "encoding" "encoding/base64" "fmt" "math" "reflect" - "sort" + "slices" "strconv" "strings" "sync" "unicode" "unicode/utf8" + _ "unsafe" // for linkname ) // Marshal returns the JSON encoding of v. // // Marshal traverses the value v recursively. -// If an encountered value implements the Marshaler interface -// and is not a nil pointer, Marshal calls its MarshalJSON method -// to produce JSON. If no MarshalJSON method is present but the -// value implements encoding.TextMarshaler instead, Marshal calls -// its MarshalText method and encodes the result as a JSON string. +// If an encountered value implements [Marshaler] +// and is not a nil pointer, Marshal calls [Marshaler.MarshalJSON] +// to produce JSON. If no [Marshaler.MarshalJSON] method is present but the +// value implements [encoding.TextMarshaler] instead, Marshal calls +// [encoding.TextMarshaler.MarshalText] and encodes the result as a JSON string. // The nil pointer exception is not strictly necessary // but mimics a similar, necessary exception in the behavior of -// UnmarshalJSON. +// [Unmarshaler.UnmarshalJSON]. // // Otherwise, Marshal uses the following type-dependent default encodings: // // Boolean values encode as JSON booleans. // -// Floating point, integer, and Number values encode as JSON numbers. +// Floating point, integer, and [Number] values encode as JSON numbers. +// NaN and +/-Inf values will return an [UnsupportedValueError]. // // String values encode as JSON strings coerced to valid UTF-8, // replacing invalid bytes with the Unicode replacement rune. // So that the JSON will be safe to embed inside HTML