diff --git a/arshal_any.go b/arshal_any.go index 5ff4bf9..605e606 100644 --- a/arshal_any.go +++ b/arshal_any.go @@ -54,7 +54,11 @@ func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error // for any possible nested value. // Duplicate names must be rejected since this does not implement merging. func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error) { - switch k := dec.PeekKind(); k { + k, err := dec.PeekKind() + if err != nil { + return nil, err + } + switch k { case '{': return unmarshalObjectAny(dec, uo) case '[': @@ -180,7 +184,7 @@ func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string] export.Decoder(dec).Tokens.Last.DisableNamespace() } var errUnmarshal error - for dec.PeekKind() != '}' { + for dec.More() { tok, err := dec.ReadToken() if err != nil { return obj, err @@ -264,7 +268,7 @@ func unmarshalArrayAny(dec *jsontext.Decoder, uo *jsonopts.Struct) ([]any, error } arr := []any{} var errUnmarshal error - for dec.PeekKind() != ']' { + for dec.More() { val, err := unmarshalValueAny(dec, uo) arr = append(arr, val) if err != nil { diff --git a/arshal_default.go b/arshal_default.go index a6777ff..bfc7b14 100644 --- a/arshal_default.go +++ b/arshal_default.go @@ -363,7 +363,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { return newInvalidFormatError(dec, t, uo) } } else if uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && - (va.Kind() == reflect.Array || dec.PeekKind() == '[') { + (va.Kind() == reflect.Array || xd.PeekKind() == '[') { return unmarshalArray(dec, va, uo) } var flags jsonwire.ValueFlags @@ -926,7 +926,7 @@ func makeMapArshaler(t reflect.Type) *arshaler { } var errUnmarshal error - for dec.PeekKind() != '}' { + for dec.More() { // Unmarshal the map entry key. k.SetZero() err := unmarshalKey(dec, k, uo) @@ -1214,7 +1214,7 @@ func makeStructArshaler(t reflect.Type) *arshaler { var seenIdxs uintSet xd.Tokens.Last.DisableNamespace() var errUnmarshal error - for dec.PeekKind() != '}' { + for dec.More() { // Process the object member name. var flags jsonwire.ValueFlags val, err := xd.ReadValue(&flags) @@ -1482,7 +1482,7 @@ func makeSliceArshaler(t reflect.Type) *arshaler { } var i int var errUnmarshal error - for dec.PeekKind() != ']' { + for dec.More() { if i == cap { va.Value.Grow(1) cap = va.Cap() @@ -1578,7 +1578,7 @@ func makeArrayArshaler(t reflect.Type) *arshaler { } var i int var errUnmarshal error - for dec.PeekKind() != ']' { + for dec.More() { if i >= n { if err := dec.SkipValue(); err != nil { return err @@ -1648,7 +1648,10 @@ func makePointerArshaler(t reflect.Type) *arshaler { } fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { // NOTE: Struct.Format is forwarded to underlying unmarshal. - if dec.PeekKind() == 'n' { + switch k, err := dec.PeekKind(); { + case err != nil: + return err + case k == 'n': if _, err := dec.ReadToken(); err != nil { return err } @@ -1744,6 +1747,10 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { return newInvalidFormatError(dec, t, uo) } + k, err := dec.PeekKind() + if err != nil { + return err + } if uo.Flags.Get(jsonflags.MergeWithLegacySemantics) && !va.IsNil() { // Legacy merge behavior is difficult to explain. // In general, it only merges for non-nil pointer kinds. @@ -1752,7 +1759,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { // (rather than setting the interface value itself to nil). e := va.Elem() if e.Kind() == reflect.Pointer && !e.IsNil() { - if dec.PeekKind() == 'n' && e.Elem().Kind() == reflect.Pointer { + if k == 'n' && e.Elem().Kind() == reflect.Pointer { if _, err := dec.ReadToken(); err != nil { return err } @@ -1763,7 +1770,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { va.SetZero() } } - if dec.PeekKind() == 'n' { + if k == 'n' { if _, err := dec.ReadToken(); err != nil { return err } @@ -1789,7 +1796,10 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { return err } - k := dec.PeekKind() + k, err := dec.PeekKind() + if err != nil { + return err + } if !isAnyType(t) { return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errNilInterface) } @@ -1827,7 +1837,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler { if uo.Unmarshalers != nil { unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, v.Type()) } - err := unmarshal(dec, v, uo) + err = unmarshal(dec, v, uo) va.Set(v.Value) return err } diff --git a/arshal_inlined.go b/arshal_inlined.go index 6f7fcf2..fa2a3e1 100644 --- a/arshal_inlined.go +++ b/arshal_inlined.go @@ -69,7 +69,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j if tok.Kind() != '{' { return newMarshalErrorBefore(enc, v.Type(), errRawInlinedNotObject) } - for dec.PeekKind() != '}' { + for dec.More() { // Parse the JSON object name. var flags jsonwire.ValueFlags val, err := xd.ReadValue(&flags) diff --git a/arshal_test.go b/arshal_test.go index 8379e22..0ca8a23 100644 --- a/arshal_test.go +++ b/arshal_test.go @@ -7138,7 +7138,7 @@ func TestUnmarshal(t *testing.T) { name: jsontest.Name("Structs/Invalid/NestedErrUnexpectedEOF"), inBuf: `{"Pointer":`, inVal: addr(structAll{}), - want: addr(structAll{Pointer: new(structAll)}), + want: addr(structAll{}), wantErr: &jsontext.SyntacticError{ByteOffset: len64(`{"Pointer":`), JSONPointer: "/Pointer", Err: io.ErrUnexpectedEOF}, }, { name: jsontest.Name("Structs/Invalid/Conflicting"), diff --git a/bench_test.go b/bench_test.go index db1abbc..8321da9 100644 --- a/bench_test.go +++ b/bench_test.go @@ -563,7 +563,7 @@ func runSlowStreamingDecode(t testing.TB, typeName string, data []byte) { dec := jsontext.NewDecoder(iotest.OneByteReader(bytes.NewReader(data))) switch typeName { case "Token": - for dec.PeekKind() > 0 { + for dec.More() { if _, err := dec.ReadToken(); err != nil { t.Fatalf("Decoder.ReadToken error: %v", err) } diff --git a/example_orderedobject_test.go b/example_orderedobject_test.go index 38812c0..54a52cc 100644 --- a/example_orderedobject_test.go +++ b/example_orderedobject_test.go @@ -50,13 +50,13 @@ func (obj *OrderedObject[V]) MarshalJSONTo(enc *jsontext.Encoder, opts json.Opti // UnmarshalJSONFrom decodes a JSON object from dec into obj. func (obj *OrderedObject[V]) UnmarshalJSONFrom(dec *jsontext.Decoder, opts json.Options) error { - if k := dec.PeekKind(); k != '{' { - return fmt.Errorf("expected object start, but encountered %v", k) - } - if _, err := dec.ReadToken(); err != nil { + switch tok, err := dec.ReadToken(); { + case err != nil: return err + case tok.Kind() != '{': + return fmt.Errorf("expected object start, but encountered %v", tok.Kind()) } - for dec.PeekKind() != '}' { + for dec.More() { *obj = append(*obj, ObjectMember[V]{}) member := &(*obj)[len(*obj)-1] if err := json.UnmarshalDecode(dec, &member.Name, opts); err != nil { diff --git a/example_test.go b/example_test.go index 6dc71d2..53bf4c4 100644 --- a/example_test.go +++ b/example_test.go @@ -607,7 +607,10 @@ func ExampleWithUnmarshalers_rawNumber() { json.UnmarshalFromFunc(func(dec *jsontext.Decoder, val *any, opts json.Options) error { // If the next value to be decoded is a JSON number, // then provide a concrete Go type to unmarshal into. - if dec.PeekKind() == '0' { + switch k, err := dec.PeekKind(); { + case err != nil: + return err + case k == '0': *val = jsontext.Value(nil) } // Return SkipFunc to fallback on default unmarshal behavior. diff --git a/inline_test.go b/inline_test.go index 28c37df..c6351d2 100644 --- a/inline_test.go +++ b/inline_test.go @@ -45,6 +45,7 @@ func TestInline(t *testing.T) { }, "./jsontext": { "encoderState.NeedFlush": true, + "Decoder.More": true, "Decoder.ReadToken": true, // thin wrapper over decoderState.ReadToken "Decoder.ReadValue": true, // thin wrapper over decoderState.ReadValue "Encoder.WriteToken": true, // thin wrapper over encoderState.WriteToken diff --git a/jsontext/coder_test.go b/jsontext/coder_test.go index 8c34721..e391196 100644 --- a/jsontext/coder_test.go +++ b/jsontext/coder_test.go @@ -458,7 +458,7 @@ func testCoderInterleaved(t *testing.T, where jsontest.CasePos, modeName string, tickTock := modeName == "TokenFirst" for { if modeName == "TokenDelims" { - switch dec.PeekKind() { + switch dec.s.PeekKind() { case '{', '}', '[', ']': tickTock = true // as token default: @@ -482,7 +482,7 @@ func testCoderInterleaved(t *testing.T, where jsontest.CasePos, modeName string, // It is a syntactic error to call ReadValue // at the end of an object or array. // Retry as a ReadToken call. - expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' + expectError := dec.s.PeekKind() == '}' || dec.s.PeekKind() == ']' if expectError { if !errors.As(err, new(*SyntacticError)) { t.Fatalf("%s: Decoder.ReadToken error is %T, want %T", where, err, new(SyntacticError)) @@ -707,7 +707,7 @@ func TestCoderMaxDepth(t *testing.T) { checkReadToken(t, '{', nil) checkReadToken(t, '"', nil) } - checkReadToken(t, 0, wantErr) + checkReadToken(t, invalidKind, wantErr) }) }) diff --git a/jsontext/decode.go b/jsontext/decode.go index 6f14095..71410ff 100644 --- a/jsontext/decode.go +++ b/jsontext/decode.go @@ -285,10 +285,18 @@ func (d *decodeBuffer) PreviousTokenOrValue() []byte { } // PeekKind retrieves the next token kind, but does not advance the read offset. -// It returns 0 if there are no more tokens. -func (d *Decoder) PeekKind() Kind { - return d.s.PeekKind() +func (d *Decoder) PeekKind() (k Kind, err error) { + k = d.s.PeekKind() + if err = d.s.peekErr; err != nil { + d.s.peekPos, d.s.peekErr = 0, nil // clear the cached error + } + return k, err } + +// PeekKind is like Decoder.PeekKind, but stores an out-of-band error, +// which can retrieved by a call to PeekKind, ReadToken, or ReadValue. +// An error-less return allows for ergonomic iteration with Decoder.More. +// It returns 0 only for [io.EOF]. func (d *decoderState) PeekKind() Kind { // Check whether we have a cached peek result. if d.peekPos > 0 { @@ -307,6 +315,9 @@ func (d *decoderState) PeekKind() Kind { err = io.EOF // EOF possibly if no Tokens present after top-level value } d.peekPos, d.peekErr = -1, wrapSyntacticError(d, err, pos, 0) + if err == io.EOF { + return 0 + } return invalidKind } } @@ -339,6 +350,13 @@ func (d *decoderState) PeekKind() Kind { return next } +// More reports whether there is another element in the +// current array or object being parsed. +func (d *Decoder) More() bool { + k := d.s.PeekKind() + return k > 0 && k != ']' && k != '}' +} + // checkDelimBeforeIOError checks whether the delim is even valid // before returning an IO error, which occurs after the delim. func (d *decoderState) checkDelimBeforeIOError(delim byte, err error) error { diff --git a/jsontext/decode_test.go b/jsontext/decode_test.go index 80f235d..f6df574 100644 --- a/jsontext/decode_test.go +++ b/jsontext/decode_test.go @@ -85,7 +85,7 @@ func testDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coder var tokens []Token loop: for { - switch dec.PeekKind() { + switch dec.s.PeekKind() { case '{', '}', '[', ']': tok, err := dec.ReadToken() if err != nil { @@ -225,8 +225,8 @@ var decoderErrorTestdata = []struct { 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 `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""}, }, wantOffset: len(` null`), }, { @@ -329,8 +329,8 @@ var decoderErrorTestdata = []struct { calls: []decoderMethodCall{ {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, {'{', ObjectStart, nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, }, wantOffset: len(`{`), }, { @@ -340,8 +340,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, {'{', ObjectStart, nil, ""}, {'"', String("0"), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, }, wantOffset: len(`{"0"`), }, { @@ -351,8 +351,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, {'{', ObjectStart, nil, ""}, {'"', String("0"), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, }, wantOffset: len(`{"0"`), }, { @@ -363,8 +363,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, nil, ""}, {'"', String("0"), nil, ""}, {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, }, wantOffset: len(`{"0":0`), }, { @@ -375,8 +375,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, nil, ""}, {'"', String("0"), nil, ""}, {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, }, wantOffset: len(`{"0":0`), }, { @@ -386,8 +386,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, {'{', ObjectStart, nil, ""}, {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {0, zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {invalidKind, zeroToken, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {invalidKind, zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, }, wantOffset: len(` { "fizz"`), }, { @@ -397,8 +397,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, {'{', ObjectStart, nil, ""}, {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, }, wantOffset: len(` { "fizz"`), }, { @@ -408,8 +408,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, {'{', ObjectStart, nil, ""}, {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, - {0, zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {invalidKind, zeroToken, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {invalidKind, zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, }, wantOffset: len(` { "fizz"`), }, { @@ -420,8 +420,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, 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" `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, }, wantOffset: len(` { "fizz" : "buzz"`), }, { @@ -432,8 +432,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, 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" `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, }, wantOffset: len(` { "fizz" : "buzz"`), }, { @@ -444,8 +444,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, 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" `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, }, wantOffset: len(` { "fizz" : "buzz"`), }, { @@ -454,8 +454,8 @@ var decoderErrorTestdata = []struct { calls: []decoderMethodCall{ {'{', zeroValue, newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, {'{', ObjectStart, nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, }, wantOffset: len(` {`), }, { @@ -466,8 +466,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, 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" `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""}, }, wantOffset: len(` { "fizz" : "buzz"`), }, { @@ -595,8 +595,8 @@ var decoderErrorTestdata = []struct { calls: []decoderMethodCall{ {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, {'[', ArrayStart, nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, }, wantOffset: len(`[`), }, { @@ -606,8 +606,8 @@ var decoderErrorTestdata = []struct { {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, {'[', ArrayStart, nil, ""}, {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, }, wantOffset: len(`[0`), }, { @@ -617,8 +617,8 @@ var decoderErrorTestdata = []struct { {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, {'[', ArrayStart, nil, ""}, {'0', Uint(0), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, }, wantOffset: len(`[0`), }, { @@ -628,8 +628,8 @@ var decoderErrorTestdata = []struct { {'[', zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, {'[', ArrayStart, nil, ""}, {'"', String("fizz"), nil, ""}, - {0, zeroToken, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, - {0, zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, }, wantOffset: len(` [ "fizz"`), }, { @@ -655,8 +655,8 @@ var decoderErrorTestdata = []struct { in: `"",`, calls: []decoderMethodCall{ {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, }, wantOffset: len(`""`), }, { @@ -665,8 +665,8 @@ var decoderErrorTestdata = []struct { calls: []decoderMethodCall{ {'{', zeroValue, newInvalidCharacterError(":", `at start of string (expecting '"')`).withPos(`{`, ""), ""}, {'{', ObjectStart, nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, }, wantOffset: len(`{`), }, { @@ -676,8 +676,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, {'{', ObjectStart, nil, ""}, {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, - {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, }, wantOffset: len(`{""`), }, { @@ -687,8 +687,8 @@ var decoderErrorTestdata = []struct { {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, {'{', ObjectStart, nil, ""}, {'"', String(""), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, }, wantOffset: len(`{""`), }, { @@ -699,8 +699,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, nil, ""}, {'"', String(""), nil, ""}, {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, }, wantOffset: len(`{"":""`), }, { @@ -711,8 +711,8 @@ var decoderErrorTestdata = []struct { {'{', ObjectStart, nil, ""}, {'"', String(""), nil, ""}, {'"', String(""), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, }, wantOffset: len(`{"":""`), }, { @@ -721,8 +721,8 @@ var decoderErrorTestdata = []struct { calls: []decoderMethodCall{ {'[', zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, "/0"), ""}, {'[', ArrayStart, nil, ""}, - {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, }, wantOffset: len(`[`), }, { @@ -732,8 +732,8 @@ var decoderErrorTestdata = []struct { {'[', zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, {'[', ArrayStart, nil, ""}, {'"', String(""), nil, ""}, - {0, zeroToken, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, - {0, zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, + {invalidKind, zeroToken, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, + {invalidKind, zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, }, wantOffset: len(`[""`), }, { @@ -743,8 +743,8 @@ var decoderErrorTestdata = []struct { {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, {'[', ArrayStart, nil, ""}, {'"', String(""), nil, ""}, - {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, - {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, + {invalidKind, zeroToken, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, + {invalidKind, zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, }, wantOffset: len(`[""`), }, { @@ -999,7 +999,7 @@ func testDecoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, in src := bytes.NewBufferString(in) dec := NewDecoder(src, opts...) for i, call := range calls { - gotKind := dec.PeekKind() + gotKind := dec.s.PeekKind() if gotKind != call.wantKind { t.Fatalf("%s: %d: Decoder.PeekKind = %v, want %v", where, i, gotKind, call.wantKind) } @@ -1191,13 +1191,13 @@ func TestPeekableDecoder(t *testing.T) { ops := []operation{ PeekKind{0}, WriteString{"[ "}, - ReadToken{0, io.EOF}, // previous error from PeekKind is cached once + ReadToken{invalidKind, io.EOF}, // previous error from PeekKind is cached once ReadToken{'[', nil}, - PeekKind{0}, + PeekKind{invalidKind}, WriteString{"] "}, - ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ", "")}, // previous error from PeekKind is cached once - ReadValue{0, newInvalidCharacterError("]", "at start of value").withPos("[ ", "/0")}, + ReadValue{invalidKind, E(io.ErrUnexpectedEOF).withPos("[ ", "")}, // previous error from PeekKind is cached once + ReadValue{invalidKind, newInvalidCharacterError("]", "at start of value").withPos("[ ", "/0")}, ReadToken{']', nil}, WriteString{"[ "}, @@ -1209,18 +1209,18 @@ func TestPeekableDecoder(t *testing.T) { ReadToken{'n', nil}, WriteString{", "}, - PeekKind{0}, + PeekKind{invalidKind}, WriteString{"fal"}, PeekKind{'f'}, - ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , fal", "/1")}, + ReadValue{invalidKind, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , fal", "/1")}, WriteString{"se "}, ReadValue{'f', nil}, - PeekKind{0}, + PeekKind{invalidKind}, WriteString{" , "}, - PeekKind{0}, + PeekKind{invalidKind}, WriteString{` "" `}, - ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , false , ", "")}, // previous error from PeekKind is cached once + ReadValue{invalidKind, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , false , ", "")}, // previous error from PeekKind is cached once ReadValue{'"', nil}, WriteString{" , 0"}, @@ -1241,7 +1241,7 @@ func TestPeekableDecoder(t *testing.T) { for i, op := range ops { switch op := op.(type) { case PeekKind: - if got := d.PeekKind(); got != op.want { + if got := d.s.PeekKind(); got != op.want { t.Fatalf("%d: Decoder.PeekKind() = %v, want %v", i, got, op.want) } case ReadToken: diff --git a/jsontext/fuzz_test.go b/jsontext/fuzz_test.go index 055eed4..efd3d9e 100644 --- a/jsontext/fuzz_test.go +++ b/jsontext/fuzz_test.go @@ -51,7 +51,7 @@ func FuzzCoder(f *testing.F) { } else { val, err := dec.ReadValue() if err != nil { - expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' + expectError := dec.s.PeekKind() == '}' || dec.s.PeekKind() == ']' if expectError && errors.As(err, new(*SyntacticError)) { continue } @@ -83,14 +83,14 @@ func FuzzCoder(f *testing.F) { // Encoded output and original input must decode to the same thing. var got, want []Token - for dec := NewDecoder(bytes.NewReader(b)); dec.PeekKind() > 0; { + for dec := NewDecoder(bytes.NewReader(b)); dec.s.PeekKind() > 0; { tok, err := dec.ReadToken() if err != nil { t.Fatalf("Decoder.ReadToken error: %v", err) } got = append(got, tok.Clone()) } - for dec := NewDecoder(dst); dec.PeekKind() > 0; { + for dec := NewDecoder(dst); dec.s.PeekKind() > 0; { tok, err := dec.ReadToken() if err != nil { t.Fatalf("Decoder.ReadToken error: %v", err) diff --git a/jsontext/token.go b/jsontext/token.go index b389fc0..532474a 100644 --- a/jsontext/token.go +++ b/jsontext/token.go @@ -488,7 +488,7 @@ func (t Token) Kind() Kind { // but may be non-zero due to invalid JSON data. type Kind byte -const invalidKind Kind = 0 +const invalidKind Kind = 0xff // String prints the kind in a humanly readable fashion. func (k Kind) String() string { @@ -521,5 +521,8 @@ func (k Kind) normalize() Kind { if k == '-' || ('0' <= k && k <= '9') { return '0' } + if k == 0 { + return invalidKind // reserve 0 for indicating proper EOF + } return k } diff --git a/jsontext/token_test.go b/jsontext/token_test.go index 2180b6a..627a754 100644 --- a/jsontext/token_test.go +++ b/jsontext/token_test.go @@ -43,7 +43,7 @@ func TestTokenAccessors(t *testing.T) { in Token want token }{ - {Token{}, token{String: ""}}, + {Token{}, token{String: "", Kind: invalidKind}}, {Null, token{String: "null", Kind: 'n'}}, {False, token{Bool: false, String: "false", Kind: 'f'}}, {True, token{Bool: true, String: "true", Kind: 't'}}, diff --git a/jsontext/value.go b/jsontext/value.go index cde3978..e7ed8b1 100644 --- a/jsontext/value.go +++ b/jsontext/value.go @@ -317,7 +317,7 @@ func mustReorderObjectsFromDecoder(d *Decoder, scratch *[]byte) { isSorted := true beforeBody := d.InputOffset() // offset after '{' - for d.PeekKind() != '}' { + for d.More() { beforeName := d.InputOffset() var flags jsonwire.ValueFlags name, _ := d.s.ReadValue(&flags) @@ -381,7 +381,7 @@ func mustReorderObjectsFromDecoder(d *Decoder, scratch *[]byte) { *scratch = sorted } case '[': - for d.PeekKind() != ']' { + for d.More() { mustReorderObjectsFromDecoder(d, scratch) } d.ReadToken() diff --git a/v1/stream.go b/v1/stream.go index 03c12e1..3546b27 100644 --- a/v1/stream.go +++ b/v1/stream.go @@ -217,8 +217,7 @@ func (dec *Decoder) Token() (Token, error) { // More reports whether there is another element in the // current array or object being parsed. func (dec *Decoder) More() bool { - k := dec.dec.PeekKind() - return k > 0 && k != ']' && k != '}' + return dec.dec.More() } // InputOffset returns the input stream byte offset of the current decoder position.