aboutsummaryrefslogtreecommitdiff
path: root/src/encoding/json/jsontext/decode_test.go
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-04-11 14:19:51 -0700
committerGopher Robot <gobot@golang.org>2025-04-18 08:24:07 -0700
commit0e17905793cb5e0acc323a0cdf3733199d93976a (patch)
treefec117ceb6b56866e6c51e6acd72901cf91717ce /src/encoding/json/jsontext/decode_test.go
parentc889004615b40535ebd5054cbcf2deebdb3a299a (diff)
downloadgo-0e17905793cb5e0acc323a0cdf3733199d93976a.tar.xz
encoding/json: add json/v2 with GOEXPERIMENT=jsonv2 guard
This imports the proposed new v2 JSON API implemented in github.com/go-json-experiment/json as of commit d3c622f1b874954c355e60c8e6b6baa5f60d2fed. When GOEXPERIMENT=jsonv2 is set, the encoding/json/v2 and encoding/jsontext packages are visible, the encoding/json package is implemented in terms of encoding/json/v2, and the encoding/json package include various additional APIs. (See #71497 for details.) When GOEXPERIMENT=jsonv2 is not set, the new API is not present and the encoding/json package is unchanged. The experimental API is not bound by the Go compatibility promise and is expected to evolve as updates are made to the json/v2 proposal. The contents of encoding/json/internal/jsontest/testdata are compressed with zstd v1.5.7 with the -19 option. Fixes #71845 For #71497 Change-Id: Ib8c94e5f0586b6aaa22833190b41cf6ef59f4f01 Reviewed-on: https://go-review.googlesource.com/c/go/+/665796 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Joseph Tsai <joetsai@digital-static.net> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Diffstat (limited to 'src/encoding/json/jsontext/decode_test.go')
-rw-r--r--src/encoding/json/jsontext/decode_test.go1267
1 files changed, 1267 insertions, 0 deletions
diff --git a/src/encoding/json/jsontext/decode_test.go b/src/encoding/json/jsontext/decode_test.go
new file mode 100644
index 0000000000..67580e6f4f
--- /dev/null
+++ b/src/encoding/json/jsontext/decode_test.go
@@ -0,0 +1,1267 @@
+// 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 jsontext
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "path"
+ "reflect"
+ "slices"
+ "strings"
+ "testing"
+ "testing/iotest"
+
+ "encoding/json/internal/jsonflags"
+ "encoding/json/internal/jsontest"
+ "encoding/json/internal/jsonwire"
+)
+
+// equalTokens reports whether to sequences of tokens formats the same way.
+func equalTokens(xs, ys []Token) bool {
+ if len(xs) != len(ys) {
+ return false
+ }
+ for i := range xs {
+ if !(reflect.DeepEqual(xs[i], ys[i]) || xs[i].String() == ys[i].String()) {
+ return false
+ }
+ }
+ return true
+}
+
+// TestDecoder tests whether we can parse JSON with either tokens or raw values.
+func TestDecoder(t *testing.T) {
+ for _, td := range coderTestdata {
+ for _, typeName := range []string{"Token", "Value", "TokenDelims"} {
+ t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) {
+ testDecoder(t, td.name.Where, typeName, td)
+ })
+ }
+ }
+}
+func testDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) {
+ dec := NewDecoder(bytes.NewBufferString(td.in))
+ switch typeName {
+ case "Token":
+ var tokens []Token
+ var pointers []Pointer
+ for {
+ tok, err := dec.ReadToken()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ t.Fatalf("%s: Decoder.ReadToken error: %v", where, err)
+ }
+ tokens = append(tokens, tok.Clone())
+ if td.pointers != nil {
+ pointers = append(pointers, dec.StackPointer())
+ }
+ }
+ if !equalTokens(tokens, td.tokens) {
+ t.Fatalf("%s: tokens mismatch:\ngot %v\nwant %v", where, tokens, td.tokens)
+ }
+ if !slices.Equal(pointers, td.pointers) {
+ t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers)
+ }
+ case "Value":
+ val, err := dec.ReadValue()
+ if err != nil {
+ t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
+ }
+ got := string(val)
+ want := strings.TrimSpace(td.in)
+ if got != want {
+ t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want)
+ }
+ case "TokenDelims":
+ // Use ReadToken for object/array delimiters, ReadValue otherwise.
+ var tokens []Token
+ loop:
+ for {
+ switch dec.PeekKind() {
+ case '{', '}', '[', ']':
+ tok, err := dec.ReadToken()
+ if err != nil {
+ if err == io.EOF {
+ break loop
+ }
+ t.Fatalf("%s: Decoder.ReadToken error: %v", where, err)
+ }
+ tokens = append(tokens, tok.Clone())
+ default:
+ val, err := dec.ReadValue()
+ if err != nil {
+ if err == io.EOF {
+ break loop
+ }
+ t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
+ }
+ tokens = append(tokens, rawToken(string(val)))
+ }
+ }
+ if !equalTokens(tokens, td.tokens) {
+ t.Fatalf("%s: tokens mismatch:\ngot %v\nwant %v", where, tokens, td.tokens)
+ }
+ }
+}
+
+// TestFaultyDecoder tests that temporary I/O errors are not fatal.
+func TestFaultyDecoder(t *testing.T) {
+ for _, td := range coderTestdata {
+ for _, typeName := range []string{"Token", "Value"} {
+ t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) {
+ testFaultyDecoder(t, td.name.Where, typeName, td)
+ })
+ }
+ }
+}
+func testFaultyDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) {
+ b := &FaultyBuffer{
+ B: []byte(td.in),
+ MaxBytes: 1,
+ MayError: io.ErrNoProgress,
+ }
+
+ // Read all the tokens.
+ // If the underlying io.Reader is faulty, then Read may return
+ // an error without changing the internal state machine.
+ // In other words, I/O errors occur before syntactic errors.
+ dec := NewDecoder(b)
+ switch typeName {
+ case "Token":
+ var tokens []Token
+ for {
+ tok, err := dec.ReadToken()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ if !errors.Is(err, io.ErrNoProgress) {
+ t.Fatalf("%s: %d: Decoder.ReadToken error: %v", where, len(tokens), err)
+ }
+ continue
+ }
+ tokens = append(tokens, tok.Clone())
+ }
+ if !equalTokens(tokens, td.tokens) {
+ t.Fatalf("%s: tokens mismatch:\ngot %s\nwant %s", where, tokens, td.tokens)
+ }
+ case "Value":
+ for {
+ val, err := dec.ReadValue()
+ if err != nil {
+ if err == io.EOF {
+ break
+ }
+ if !errors.Is(err, io.ErrNoProgress) {
+ t.Fatalf("%s: Decoder.ReadValue error: %v", where, err)
+ }
+ continue
+ }
+ got := string(val)
+ want := strings.TrimSpace(td.in)
+ if got != want {
+ t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want)
+ }
+ }
+ }
+}
+
+type decoderMethodCall struct {
+ wantKind Kind
+ wantOut tokOrVal
+ wantErr error
+ wantPointer Pointer
+}
+
+var decoderErrorTestdata = []struct {
+ name jsontest.CaseName
+ opts []Options
+ in string
+ calls []decoderMethodCall
+ wantOffset int
+}{{
+ name: jsontest.Name("InvalidStart"),
+ in: ` #`,
+ calls: []decoderMethodCall{
+ {'#', zeroToken, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""},
+ {'#', zeroValue, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""},
+ },
+}, {
+ name: jsontest.Name("StreamN0"),
+ in: ` `,
+ calls: []decoderMethodCall{
+ {0, zeroToken, io.EOF, ""},
+ {0, zeroValue, io.EOF, ""},
+ },
+}, {
+ name: jsontest.Name("StreamN1"),
+ in: ` null `,
+ calls: []decoderMethodCall{
+ {'n', Null, nil, ""},
+ {0, zeroToken, io.EOF, ""},
+ {0, zeroValue, io.EOF, ""},
+ },
+ wantOffset: len(` null`),
+}, {
+ name: jsontest.Name("StreamN2"),
+ in: ` nullnull `,
+ calls: []decoderMethodCall{
+ {'n', Null, nil, ""},
+ {'n', Null, nil, ""},
+ {0, zeroToken, io.EOF, ""},
+ {0, zeroValue, io.EOF, ""},
+ },
+ wantOffset: len(` nullnull`),
+}, {
+ name: jsontest.Name("StreamN2/ExtraComma"), // stream is whitespace delimited, not comma delimited
+ in: ` null , null `,
+ calls: []decoderMethodCall{
+ {'n', Null, nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""},
+ },
+ wantOffset: len(` null`),
+}, {
+ name: jsontest.Name("TruncatedNull"),
+ in: `nul`,
+ calls: []decoderMethodCall{
+ {'n', zeroToken, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""},
+ {'n', zeroValue, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("InvalidNull"),
+ in: `nulL`,
+ calls: []decoderMethodCall{
+ {'n', zeroToken, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""},
+ {'n', zeroValue, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("TruncatedFalse"),
+ in: `fals`,
+ calls: []decoderMethodCall{
+ {'f', zeroToken, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""},
+ {'f', zeroValue, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("InvalidFalse"),
+ in: `falsE`,
+ calls: []decoderMethodCall{
+ {'f', zeroToken, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""},
+ {'f', zeroValue, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("TruncatedTrue"),
+ in: `tru`,
+ calls: []decoderMethodCall{
+ {'t', zeroToken, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""},
+ {'t', zeroValue, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("InvalidTrue"),
+ in: `truE`,
+ calls: []decoderMethodCall{
+ {'t', zeroToken, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""},
+ {'t', zeroValue, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("TruncatedString"),
+ in: `"start`,
+ calls: []decoderMethodCall{
+ {'"', zeroToken, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""},
+ {'"', zeroValue, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("InvalidString"),
+ in: `"ok` + "\x00",
+ calls: []decoderMethodCall{
+ {'"', zeroToken, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""},
+ {'"', zeroValue, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"),
+ opts: []Options{AllowInvalidUTF8(true)},
+ in: "\"living\xde\xad\xbe\xef\"",
+ calls: []decoderMethodCall{
+ {'"', rawToken("\"living\xde\xad\xbe\xef\""), nil, ""},
+ },
+ wantOffset: len("\"living\xde\xad\xbe\xef\""),
+}, {
+ name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"),
+ opts: []Options{AllowInvalidUTF8(true)},
+ in: "\"living\xde\xad\xbe\xef\"",
+ calls: []decoderMethodCall{
+ {'"', Value("\"living\xde\xad\xbe\xef\""), nil, ""},
+ },
+ wantOffset: len("\"living\xde\xad\xbe\xef\""),
+}, {
+ name: jsontest.Name("InvalidString/RejectInvalidUTF8"),
+ opts: []Options{AllowInvalidUTF8(false)},
+ in: "\"living\xde\xad\xbe\xef\"",
+ calls: []decoderMethodCall{
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""},
+ },
+}, {
+ name: jsontest.Name("TruncatedNumber"),
+ in: `0.`,
+ calls: []decoderMethodCall{
+ {'0', zeroToken, E(io.ErrUnexpectedEOF), ""},
+ {'0', zeroValue, E(io.ErrUnexpectedEOF), ""},
+ },
+}, {
+ name: jsontest.Name("InvalidNumber"),
+ in: `0.e`,
+ calls: []decoderMethodCall{
+ {'0', zeroToken, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""},
+ {'0', zeroValue, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("TruncatedObject/AfterStart"),
+ in: `{`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
+ {'{', BeginObject, nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""},
+ },
+ wantOffset: len(`{`),
+}, {
+ name: jsontest.Name("TruncatedObject/AfterName"),
+ in: `{"0"`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("0"), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""},
+ },
+ wantOffset: len(`{"0"`),
+}, {
+ name: jsontest.Name("TruncatedObject/AfterColon"),
+ in: `{"0":`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("0"), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""},
+ },
+ wantOffset: len(`{"0"`),
+}, {
+ name: jsontest.Name("TruncatedObject/AfterValue"),
+ in: `{"0":0`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("0"), nil, ""},
+ {'0', Uint(0), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""},
+ },
+ wantOffset: len(`{"0":0`),
+}, {
+ name: jsontest.Name("TruncatedObject/AfterComma"),
+ in: `{"0":0,`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("0"), nil, ""},
+ {'0', Uint(0), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""},
+ },
+ wantOffset: len(`{"0":0`),
+}, {
+ name: jsontest.Name("InvalidObject/MissingColon"),
+ in: ` { "fizz" "buzz" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ {0, zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ },
+ wantOffset: len(` { "fizz"`),
+}, {
+ name: jsontest.Name("InvalidObject/MissingColon/GotComma"),
+ in: ` { "fizz" , "buzz" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ },
+ wantOffset: len(` { "fizz"`),
+}, {
+ name: jsontest.Name("InvalidObject/MissingColon/GotHash"),
+ in: ` { "fizz" # "buzz" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ {0, zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""},
+ },
+ wantOffset: len(` { "fizz"`),
+}, {
+ name: jsontest.Name("InvalidObject/MissingComma"),
+ in: ` { "fizz" : "buzz" "gazz" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {'"', String("buzz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ },
+ wantOffset: len(` { "fizz" : "buzz"`),
+}, {
+ name: jsontest.Name("InvalidObject/MissingComma/GotColon"),
+ in: ` { "fizz" : "buzz" : "gazz" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {'"', String("buzz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ },
+ wantOffset: len(` { "fizz" : "buzz"`),
+}, {
+ name: jsontest.Name("InvalidObject/MissingComma/GotHash"),
+ in: ` { "fizz" : "buzz" # "gazz" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {'"', String("buzz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""},
+ },
+ wantOffset: len(` { "fizz" : "buzz"`),
+}, {
+ name: jsontest.Name("InvalidObject/ExtraComma/AfterStart"),
+ in: ` { , } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/ExtraComma/AfterValue"),
+ in: ` { "fizz" : "buzz" , } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {'"', String("buzz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""},
+ },
+ wantOffset: len(` { "fizz" : "buzz"`),
+}, {
+ name: jsontest.Name("InvalidObject/InvalidName/GotNull"),
+ in: ` { null : null } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("n", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'n', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
+ {'n', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/InvalidName/GotFalse"),
+ in: ` { false : false } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("f", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'f', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
+ {'f', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/InvalidName/GotTrue"),
+ in: ` { true : true } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("t", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'t', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
+ {'t', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/InvalidName/GotNumber"),
+ in: ` { 0 : 0 } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("0", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'0', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
+ {'0', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/InvalidName/GotObject"),
+ in: ` { {} : {} } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("{", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'{', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
+ {'{', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/InvalidName/GotArray"),
+ in: ` { [] : [] } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("[", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'[', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""},
+ {'[', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("InvalidObject/MismatchingDelim"),
+ in: ` { ] `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError("]", "at start of string (expecting '\"')").withPos(` { `, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {']', zeroToken, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""},
+ {']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("ValidObject/InvalidValue"),
+ in: ` { } `,
+ calls: []decoderMethodCall{
+ {'{', BeginObject, nil, ""},
+ {'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(" { ", ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("ValidObject/UniqueNames"),
+ in: `{"0":0,"1":1} `,
+ calls: []decoderMethodCall{
+ {'{', BeginObject, nil, ""},
+ {'"', String("0"), nil, ""},
+ {'0', Uint(0), nil, ""},
+ {'"', String("1"), nil, ""},
+ {'0', Uint(1), nil, ""},
+ {'}', EndObject, nil, ""},
+ },
+ wantOffset: len(`{"0":0,"1":1}`),
+}, {
+ name: jsontest.Name("ValidObject/DuplicateNames"),
+ opts: []Options{AllowDuplicateNames(true)},
+ in: `{"0":0,"0":0} `,
+ calls: []decoderMethodCall{
+ {'{', BeginObject, nil, ""},
+ {'"', String("0"), nil, ""},
+ {'0', Uint(0), nil, ""},
+ {'"', String("0"), nil, ""},
+ {'0', Uint(0), nil, ""},
+ {'}', EndObject, nil, ""},
+ },
+ wantOffset: len(`{"0":0,"0":0}`),
+}, {
+ name: jsontest.Name("InvalidObject/DuplicateNames"),
+ in: `{"X":{},"Y":{},"X":{}} `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("X"), nil, ""},
+ {'{', BeginObject, nil, ""},
+ {'}', EndObject, nil, ""},
+ {'"', String("Y"), nil, ""},
+ {'{', BeginObject, nil, ""},
+ {'}', EndObject, nil, ""},
+ {'"', zeroToken, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"},
+ {'"', zeroValue, E(ErrDuplicateName).withPos(`{"0":{},"Y":{},`, "/X"), "/Y"},
+ },
+ wantOffset: len(`{"0":{},"1":{}`),
+}, {
+ name: jsontest.Name("TruncatedArray/AfterStart"),
+ in: `[`,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""},
+ {'[', BeginArray, nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[", ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""},
+ },
+ wantOffset: len(`[`),
+}, {
+ name: jsontest.Name("TruncatedArray/AfterValue"),
+ in: `[0`,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""},
+ {'[', BeginArray, nil, ""},
+ {'0', Uint(0), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""},
+ },
+ wantOffset: len(`[0`),
+}, {
+ name: jsontest.Name("TruncatedArray/AfterComma"),
+ in: `[0,`,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""},
+ {'[', BeginArray, nil, ""},
+ {'0', Uint(0), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""},
+ },
+ wantOffset: len(`[0`),
+}, {
+ name: jsontest.Name("InvalidArray/MissingComma"),
+ in: ` [ "fizz" "buzz" ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String("fizz"), nil, ""},
+ {0, zeroToken, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
+ {0, zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""},
+ },
+ wantOffset: len(` [ "fizz"`),
+}, {
+ name: jsontest.Name("InvalidArray/MismatchingDelim"),
+ in: ` [ } `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""},
+ {'[', BeginArray, nil, ""},
+ {'}', zeroToken, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""},
+ {'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""},
+ },
+ wantOffset: len(` [`),
+}, {
+ name: jsontest.Name("ValidArray/InvalidValue"),
+ in: ` [ ] `,
+ calls: []decoderMethodCall{
+ {'[', BeginArray, nil, ""},
+ {']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(" [ ", "/0"), ""},
+ },
+ wantOffset: len(` [`),
+}, {
+ name: jsontest.Name("InvalidDelim/AfterTopLevel"),
+ in: `"",`,
+ calls: []decoderMethodCall{
+ {'"', String(""), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""},
+ },
+ wantOffset: len(`""`),
+}, {
+ name: jsontest.Name("InvalidDelim/AfterBeginObject"),
+ in: `{:`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError(":", `at start of string (expecting '"')`).withPos(`{`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {0, zeroToken, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""},
+ },
+ wantOffset: len(`{`),
+}, {
+ name: jsontest.Name("InvalidDelim/AfterObjectName"),
+ in: `{"",`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String(""), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""},
+ {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""},
+ },
+ wantOffset: len(`{""`),
+}, {
+ name: jsontest.Name("ValidDelim/AfterObjectName"),
+ in: `{"":`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String(""), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""},
+ },
+ wantOffset: len(`{""`),
+}, {
+ name: jsontest.Name("InvalidDelim/AfterObjectValue"),
+ in: `{"":"":`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String(""), nil, ""},
+ {'"', String(""), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""},
+ },
+ wantOffset: len(`{"":""`),
+}, {
+ name: jsontest.Name("ValidDelim/AfterObjectValue"),
+ in: `{"":"",`,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String(""), nil, ""},
+ {'"', String(""), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""},
+ },
+ wantOffset: len(`{"":""`),
+}, {
+ name: jsontest.Name("InvalidDelim/AfterBeginArray"),
+ in: `[,`,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, "/0"), ""},
+ {'[', BeginArray, nil, ""},
+ {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""},
+ },
+ wantOffset: len(`[`),
+}, {
+ name: jsontest.Name("InvalidDelim/AfterArrayValue"),
+ in: `["":`,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String(""), nil, ""},
+ {0, zeroToken, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""},
+ {0, zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""},
+ },
+ wantOffset: len(`[""`),
+}, {
+ name: jsontest.Name("ValidDelim/AfterArrayValue"),
+ in: `["",`,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String(""), nil, ""},
+ {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""},
+ {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""},
+ },
+ wantOffset: len(`[""`),
+}, {
+ name: jsontest.Name("ErrorPosition"),
+ in: ` "a` + "\xff" + `0" `,
+ calls: []decoderMethodCall{
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""},
+ },
+}, {
+ name: jsontest.Name("ErrorPosition/0"),
+ in: ` [ "a` + "\xff" + `1" ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""},
+ },
+ wantOffset: len(` [`),
+}, {
+ name: jsontest.Name("ErrorPosition/1"),
+ in: ` [ "a1" , "b` + "\xff" + `1" ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String("a1"), nil, ""},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""},
+ },
+ wantOffset: len(` [ "a1"`),
+}, {
+ name: jsontest.Name("ErrorPosition/0/0"),
+ in: ` [ [ "a` + "\xff" + `2" ] ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
+ {'[', BeginArray, nil, ""},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
+ {'[', BeginArray, nil, "/0"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""},
+ },
+ wantOffset: len(` [ [`),
+}, {
+ name: jsontest.Name("ErrorPosition/1/0"),
+ in: ` [ "a1" , [ "a` + "\xff" + `2" ] ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String("a1"), nil, "/0"},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/0"},
+ {'[', BeginArray, nil, "/1"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"},
+ },
+ wantOffset: len(` [ "a1" , [`),
+}, {
+ name: jsontest.Name("ErrorPosition/0/1"),
+ in: ` [ [ "a2" , "b` + "\xff" + `2" ] ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""},
+ {'[', BeginArray, nil, ""},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""},
+ {'[', BeginArray, nil, "/0"},
+ {'"', String("a2"), nil, "/0/0"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"},
+ },
+ wantOffset: len(` [ [ "a2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/1/1"),
+ in: ` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String("a1"), nil, "/0"},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""},
+ {'[', BeginArray, nil, "/1"},
+ {'"', String("a2"), nil, "/1/0"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"},
+ },
+ wantOffset: len(` [ "a1" , [ "a2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/a1-"),
+ in: ` { "a` + "\xff" + `1" : "b1" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""},
+ },
+ wantOffset: len(` {`),
+}, {
+ name: jsontest.Name("ErrorPosition/a1"),
+ in: ` { "a1" : "b` + "\xff" + `1" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""},
+ },
+ wantOffset: len(` { "a1"`),
+}, {
+ name: jsontest.Name("ErrorPosition/c1-"),
+ in: ` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'"', String("b1"), nil, "/a1"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"},
+ },
+ wantOffset: len(` { "a1" : "b1"`),
+}, {
+ name: jsontest.Name("ErrorPosition/c1"),
+ in: ` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'"', String("b1"), nil, "/a1"},
+ {'"', String("c1"), nil, "/c1"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"},
+ },
+ wantOffset: len(` { "a1" : "b1" , "c1"`),
+}, {
+ name: jsontest.Name("ErrorPosition/a1/a2-"),
+ in: ` { "a1" : { "a` + "\xff" + `2" : "b2" } } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""},
+ {'{', BeginObject, nil, "/a1"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"},
+ },
+ wantOffset: len(` { "a1" : {`),
+}, {
+ name: jsontest.Name("ErrorPosition/a1/a2"),
+ in: ` { "a1" : { "a2" : "b` + "\xff" + `2" } } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""},
+ {'{', BeginObject, nil, "/a1"},
+ {'"', String("a2"), nil, "/a1/a2"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"},
+ },
+ wantOffset: len(` { "a1" : { "a2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/a1/c2-"),
+ in: ` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'{', BeginObject, nil, "/a1"},
+ {'"', String("a2"), nil, "/a1/a2"},
+ {'"', String("b2"), nil, "/a1/a2"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"},
+ },
+ wantOffset: len(` { "a1" : { "a2" : "b2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/a1/c2"),
+ in: ` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a2"), nil, "/a1/a2"},
+ {'"', String("b2"), nil, "/a1/a2"},
+ {'"', String("c2"), nil, "/a1/c2"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"},
+ },
+ wantOffset: len(` { "a1" : { "a2" : "b2" , "c2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/1/a2"),
+ in: ` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String("a1"), nil, "/0"},
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""},
+ {'{', BeginObject, nil, "/1"},
+ {'"', String("a2"), nil, "/1/a2"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"},
+ },
+ wantOffset: len(` [ "a1" , { "a2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/c1/1"),
+ in: ` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `,
+ calls: []decoderMethodCall{
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""},
+ {'{', BeginObject, nil, ""},
+ {'"', String("a1"), nil, "/a1"},
+ {'"', String("b1"), nil, "/a1"},
+ {'"', String("c1"), nil, "/c1"},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""},
+ {'[', BeginArray, nil, "/c1"},
+ {'"', String("a2"), nil, "/c1/0"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"},
+ },
+ wantOffset: len(` { "a1" : "b1" , "c1" : [ "a2"`),
+}, {
+ name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"),
+ in: ` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `,
+ calls: []decoderMethodCall{
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
+ {'[', BeginArray, nil, ""},
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
+ {'{', BeginObject, nil, "/0"},
+ {'"', String("a1"), nil, "/0/a1"},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
+ {'[', BeginArray, nil, ""},
+ {'"', String("a2"), nil, "/0/a1/0"},
+ {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
+ {'{', BeginObject, nil, "/0/a1/1"},
+ {'"', String("a3"), nil, "/0/a1/1/a3"},
+ {'"', String("b3"), nil, "/0/a1/1/a3"},
+ {'"', String("c3"), nil, "/0/a1/1/c3"},
+ {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""},
+ {'[', BeginArray, nil, "/0/a1/1/c3"},
+ {'"', String("a4"), nil, "/0/a1/1/c3/0"},
+ {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
+ {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"},
+ },
+ wantOffset: len(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4"`),
+}}
+
+// TestDecoderErrors test that Decoder errors occur when we expect and
+// leaves the Decoder in a consistent state.
+func TestDecoderErrors(t *testing.T) {
+ for _, td := range decoderErrorTestdata {
+ t.Run(path.Join(td.name.Name), func(t *testing.T) {
+ testDecoderErrors(t, td.name.Where, td.opts, td.in, td.calls, td.wantOffset)
+ })
+ }
+}
+func testDecoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, in string, calls []decoderMethodCall, wantOffset int) {
+ src := bytes.NewBufferString(in)
+ dec := NewDecoder(src, opts...)
+ for i, call := range calls {
+ gotKind := dec.PeekKind()
+ if gotKind != call.wantKind {
+ t.Fatalf("%s: %d: Decoder.PeekKind = %v, want %v", where, i, gotKind, call.wantKind)
+ }
+
+ var gotErr error
+ switch wantOut := call.wantOut.(type) {
+ case Token:
+ var gotOut Token
+ gotOut, gotErr = dec.ReadToken()
+ if gotOut.String() != wantOut.String() {
+ t.Fatalf("%s: %d: Decoder.ReadToken = %v, want %v", where, i, gotOut, wantOut)
+ }
+ case Value:
+ var gotOut Value
+ gotOut, gotErr = dec.ReadValue()
+ if string(gotOut) != string(wantOut) {
+ t.Fatalf("%s: %d: Decoder.ReadValue = %s, want %s", where, i, gotOut, wantOut)
+ }
+ }
+ if !equalError(gotErr, call.wantErr) {
+ t.Fatalf("%s: %d: error mismatch:\ngot %v\nwant %v", where, i, gotErr, call.wantErr)
+ }
+ if call.wantPointer != "" {
+ gotPointer := dec.StackPointer()
+ if gotPointer != call.wantPointer {
+ t.Fatalf("%s: %d: Decoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer)
+ }
+ }
+ }
+ gotOffset := int(dec.InputOffset())
+ if gotOffset != wantOffset {
+ t.Fatalf("%s: Decoder.InputOffset = %v, want %v", where, gotOffset, wantOffset)
+ }
+ gotUnread := string(dec.s.unreadBuffer()) // should be a prefix of wantUnread
+ wantUnread := in[wantOffset:]
+ if !strings.HasPrefix(wantUnread, gotUnread) {
+ t.Fatalf("%s: Decoder.UnreadBuffer = %v, want %v", where, gotUnread, wantUnread)
+ }
+}
+
+// TestBufferDecoder tests that we detect misuses of bytes.Buffer with Decoder.
+func TestBufferDecoder(t *testing.T) {
+ bb := bytes.NewBufferString("[null, false, true]")
+ dec := NewDecoder(bb)
+ var err error
+ for {
+ if _, err = dec.ReadToken(); err != nil {
+ break
+ }
+ bb.WriteByte(' ') // not allowed to write to the buffer while reading
+ }
+ want := &ioError{action: "read", err: errBufferWriteAfterNext}
+ if !equalError(err, want) {
+ t.Fatalf("error mismatch: got %v, want %v", err, want)
+ }
+}
+
+var resumableDecoderTestdata = []string{
+ `0`,
+ `123456789`,
+ `0.0`,
+ `0.123456789`,
+ `0e0`,
+ `0e+0`,
+ `0e123456789`,
+ `0e+123456789`,
+ `123456789.123456789e+123456789`,
+ `-0`,
+ `-123456789`,
+ `-0.0`,
+ `-0.123456789`,
+ `-0e0`,
+ `-0e-0`,
+ `-0e123456789`,
+ `-0e-123456789`,
+ `-123456789.123456789e-123456789`,
+
+ `""`,
+ `"a"`,
+ `"ab"`,
+ `"abc"`,
+ `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`,
+ `"\"\\\/\b\f\n\r\t"`,
+ `"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`,
+ `"\ud800\udead"`,
+ "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"",
+ `"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`,
+}
+
+// TestResumableDecoder tests that resume logic for parsing a
+// JSON string and number properly works across every possible split point.
+func TestResumableDecoder(t *testing.T) {
+ for _, want := range resumableDecoderTestdata {
+ t.Run("", func(t *testing.T) {
+ dec := NewDecoder(iotest.OneByteReader(strings.NewReader(want)))
+ got, err := dec.ReadValue()
+ if err != nil {
+ t.Fatalf("Decoder.ReadValue error: %v", err)
+ }
+ if string(got) != want {
+ t.Fatalf("Decoder.ReadValue = %s, want %s", got, want)
+ }
+ })
+ }
+}
+
+// TestBlockingDecoder verifies that JSON values except numbers can be
+// synchronously sent and received on a blocking pipe without a deadlock.
+// Numbers are the exception since termination cannot be determined until
+// either the pipe ends or a non-numeric character is encountered.
+func TestBlockingDecoder(t *testing.T) {
+ values := []string{"null", "false", "true", `""`, `{}`, `[]`}
+
+ r, w := net.Pipe()
+ defer r.Close()
+ defer w.Close()
+
+ enc := NewEncoder(w, jsonflags.OmitTopLevelNewline|1)
+ dec := NewDecoder(r)
+
+ errCh := make(chan error)
+
+ // Test synchronous ReadToken calls.
+ for _, want := range values {
+ go func() {
+ errCh <- enc.WriteValue(Value(want))
+ }()
+
+ tok, err := dec.ReadToken()
+ if err != nil {
+ t.Fatalf("Decoder.ReadToken error: %v", err)
+ }
+ got := tok.String()
+ switch tok.Kind() {
+ case '"':
+ got = `"` + got + `"`
+ case '{', '[':
+ tok, err := dec.ReadToken()
+ if err != nil {
+ t.Fatalf("Decoder.ReadToken error: %v", err)
+ }
+ got += tok.String()
+ }
+ if got != want {
+ t.Fatalf("ReadTokens = %s, want %s", got, want)
+ }
+
+ if err := <-errCh; err != nil {
+ t.Fatalf("Encoder.WriteValue error: %v", err)
+ }
+ }
+
+ // Test synchronous ReadValue calls.
+ for _, want := range values {
+ go func() {
+ errCh <- enc.WriteValue(Value(want))
+ }()
+
+ got, err := dec.ReadValue()
+ if err != nil {
+ t.Fatalf("Decoder.ReadValue error: %v", err)
+ }
+ if string(got) != want {
+ t.Fatalf("ReadValue = %s, want %s", got, want)
+ }
+
+ if err := <-errCh; err != nil {
+ t.Fatalf("Encoder.WriteValue error: %v", err)
+ }
+ }
+}
+
+func TestPeekableDecoder(t *testing.T) {
+ type operation any // PeekKind | ReadToken | ReadValue | BufferWrite
+ type PeekKind struct {
+ want Kind
+ }
+ type ReadToken struct {
+ wantKind Kind
+ wantErr error
+ }
+ type ReadValue struct {
+ wantKind Kind
+ wantErr error
+ }
+ type WriteString struct {
+ in string
+ }
+ ops := []operation{
+ PeekKind{0},
+ WriteString{"[ "},
+ ReadToken{0, io.EOF}, // previous error from PeekKind is cached once
+ ReadToken{'[', nil},
+
+ PeekKind{0},
+ WriteString{"] "},
+ ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ", "")}, // previous error from PeekKind is cached once
+ ReadValue{0, newInvalidCharacterError("]", "at start of value").withPos("[ ", "/0")},
+ ReadToken{']', nil},
+
+ WriteString{"[ "},
+ ReadToken{'[', nil},
+
+ WriteString{" null "},
+ PeekKind{'n'},
+ PeekKind{'n'},
+ ReadToken{'n', nil},
+
+ WriteString{", "},
+ PeekKind{0},
+ WriteString{"fal"},
+ PeekKind{'f'},
+ ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , fal", "/1")},
+ WriteString{"se "},
+ ReadValue{'f', nil},
+
+ PeekKind{0},
+ WriteString{" , "},
+ PeekKind{0},
+ WriteString{` "" `},
+ ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , false , ", "")}, // previous error from PeekKind is cached once
+ ReadValue{'"', nil},
+
+ WriteString{" , 0"},
+ PeekKind{'0'},
+ ReadToken{'0', nil},
+
+ WriteString{" , {} , []"},
+ PeekKind{'{'},
+ ReadValue{'{', nil},
+ ReadValue{'[', nil},
+
+ WriteString{"]"},
+ ReadToken{']', nil},
+ }
+
+ bb := struct{ *bytes.Buffer }{new(bytes.Buffer)}
+ d := NewDecoder(bb)
+ for i, op := range ops {
+ switch op := op.(type) {
+ case PeekKind:
+ if got := d.PeekKind(); got != op.want {
+ t.Fatalf("%d: Decoder.PeekKind() = %v, want %v", i, got, op.want)
+ }
+ case ReadToken:
+ gotTok, gotErr := d.ReadToken()
+ gotKind := gotTok.Kind()
+ if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) {
+ t.Fatalf("%d: Decoder.ReadToken() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr)
+ }
+ case ReadValue:
+ gotVal, gotErr := d.ReadValue()
+ gotKind := gotVal.Kind()
+ if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) {
+ t.Fatalf("%d: Decoder.ReadValue() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr)
+ }
+ case WriteString:
+ bb.WriteString(op.in)
+ default:
+ panic(fmt.Sprintf("unknown operation: %T", op))
+ }
+ }
+}