Update dependencies

This commit is contained in:
bluepython508
2025-04-09 01:00:12 +01:00
parent f0641ffd6e
commit 5a9cfc022c
882 changed files with 68930 additions and 24201 deletions

View File

@@ -125,16 +125,16 @@ func NewDecoder(r io.Reader, opts ...Options) *Decoder {
// Reset resets a decoder such that it is reading afresh from r and
// configured with the provided options. Reset must not be called on an
// a Decoder passed to the [encoding/json/v2.UnmarshalerV2.UnmarshalJSONV2] method
// or the [encoding/json/v2.UnmarshalFuncV2] function.
// a Decoder passed to the [encoding/json/v2.UnmarshalerFrom.UnmarshalJSONFrom] method
// or the [encoding/json/v2.UnmarshalFromFunc] function.
func (d *Decoder) Reset(r io.Reader, opts ...Options) {
switch {
case d == nil:
panic("jsontext: invalid nil Decoder")
case r == nil:
panic("jsontext: invalid nil io.Writer")
panic("jsontext: invalid nil io.Reader")
case d.s.Flags.Get(jsonflags.WithinArshalCall):
panic("jsontext: cannot reset Decoder passed to json.UnmarshalerV2")
panic("jsontext: cannot reset Decoder passed to json.UnmarshalerFrom")
}
d.s.reset(nil, r, opts...)
}
@@ -142,8 +142,21 @@ func (d *Decoder) Reset(r io.Reader, opts ...Options) {
func (d *decoderState) reset(b []byte, r io.Reader, opts ...Options) {
d.state.reset()
d.decodeBuffer = decodeBuffer{buf: b, rd: r}
d.Struct = jsonopts.Struct{}
d.Struct.Join(opts...)
opts2 := jsonopts.Struct{} // avoid mutating d.Struct in case it is part of opts
opts2.Join(opts...)
d.Struct = opts2
}
// Options returns the options used to construct the encoder and
// may additionally contain semantic options passed to a
// [encoding/json/v2.UnmarshalDecode] call.
//
// If operating within
// a [encoding/json/v2.UnmarshalerFrom.UnmarshalJSONFrom] method call or
// a [encoding/json/v2.UnmarshalFromFunc] function call,
// then the returned options are only valid within the call.
func (d *Decoder) Options() Options {
return &d.s.Struct
}
var errBufferWriteAfterNext = errors.New("invalid bytes.Buffer.Write call after calling bytes.Buffer.Next")
@@ -255,23 +268,40 @@ func (d *decodeBuffer) needMore(pos int) bool {
return pos == len(d.buf)
}
// injectSyntacticErrorWithPosition wraps a SyntacticError with the position,
// otherwise it returns the error as is.
// It takes a position relative to the start of the start of d.buf.
func (d *decodeBuffer) injectSyntacticErrorWithPosition(err error, pos int) error {
if serr, ok := err.(*SyntacticError); ok {
return serr.withOffset(d.baseOffset + int64(pos))
}
return err
}
func (d *decodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) }
func (d *decodeBuffer) previousOffsetStart() int64 { return d.baseOffset + int64(d.prevStart) }
func (d *decodeBuffer) previousOffsetEnd() int64 { return d.baseOffset + int64(d.prevEnd) }
func (d *decodeBuffer) PreviousBuffer() []byte { return d.buf[d.prevStart:d.prevEnd] }
func (d *decodeBuffer) previousBuffer() []byte { return d.buf[d.prevStart:d.prevEnd] }
func (d *decodeBuffer) unreadBuffer() []byte { return d.buf[d.prevEnd:len(d.buf)] }
// PreviousTokenOrValue returns the previously read token or value
// unless it has been invalidated by a call to PeekKind.
// If a token is just a delimiter, then this returns a 1-byte buffer.
// This method is used for error reporting at the semantic layer.
func (d *decodeBuffer) PreviousTokenOrValue() []byte {
b := d.previousBuffer()
// If peek was called, then the previous token or buffer is invalidated.
if d.peekPos > 0 || len(b) > 0 && b[0] == invalidateBufferByte {
return nil
}
// ReadToken does not preserve the buffer for null, bools, or delimiters.
// Manually re-construct that buffer.
if len(b) == 0 {
b = d.buf[:d.prevEnd] // entirety of the previous buffer
for _, tok := range []string{"null", "false", "true", "{", "}", "[", "]"} {
if len(b) >= len(tok) && string(b[len(b)-len(tok):]) == tok {
return b[len(b)-len(tok):]
}
}
}
return b
}
// PeekKind retrieves the next token kind, but does not advance the read offset.
// It returns 0 if there are no more tokens.
//
// It returns 0 if an error occurs. Any such error is cached until
// the next read call and it is the caller's responsibility to eventually
// follow up a PeekKind call with a read call.
func (d *Decoder) PeekKind() Kind {
return d.s.PeekKind()
}
@@ -292,7 +322,7 @@ func (d *decoderState) PeekKind() Kind {
if err == io.ErrUnexpectedEOF && d.Tokens.Depth() == 1 {
err = io.EOF // EOF possibly if no Tokens present after top-level value
}
d.peekPos, d.peekErr = -1, err
d.peekPos, d.peekErr = -1, wrapSyntacticError(d, err, pos, 0)
return invalidKind
}
}
@@ -305,6 +335,7 @@ func (d *decoderState) PeekKind() Kind {
pos += jsonwire.ConsumeWhitespace(d.buf[pos:])
if d.needMore(pos) {
if pos, err = d.consumeWhitespace(pos); err != nil {
err = wrapSyntacticError(d, err, pos, 0)
d.peekPos, d.peekErr = -1, d.checkDelimBeforeIOError(delim, err)
return invalidKind
}
@@ -339,12 +370,33 @@ func (d *decoderState) checkDelimBeforeIOError(delim byte, err error) error {
return err
}
// CountNextDelimWhitespace counts the number of upcoming bytes of
// delimiter or whitespace characters.
// This method is used for error reporting at the semantic layer.
func (d *decoderState) CountNextDelimWhitespace() int {
d.PeekKind() // populate unreadBuffer
return len(d.unreadBuffer()) - len(bytes.TrimLeft(d.unreadBuffer(), ",: \n\r\t"))
}
// checkDelim checks whether delim is valid for the given next kind.
func (d *decoderState) checkDelim(delim byte, next Kind) error {
where := "at start of value"
switch d.Tokens.needDelim(next) {
case delim:
return nil
case ':':
where = "after object name (expecting ':')"
case ',':
if d.Tokens.Last.isObject() {
where = "after object value (expecting ',' or '}')"
} else {
where = "after array element (expecting ',' or ']')"
}
}
pos := d.prevEnd // restore position to right after leading whitespace
pos += jsonwire.ConsumeWhitespace(d.buf[pos:])
err := d.Tokens.checkDelim(delim, next)
return d.injectSyntacticErrorWithPosition(err, pos)
err := jsonwire.NewInvalidCharacterError(d.buf[pos:], where)
return wrapSyntacticError(d, err, pos, 0)
}
// SkipValue is semantically equivalent to calling [Decoder.ReadValue] and discarding
@@ -377,6 +429,30 @@ func (d *decoderState) SkipValue() error {
}
}
// SkipValueRemainder skips the remainder of a value
// after reading a '{' or '[' token.
func (d *decoderState) SkipValueRemainder() error {
if d.Tokens.Depth()-1 > 0 && d.Tokens.Last.Length() == 0 {
for n := d.Tokens.Depth(); d.Tokens.Depth() >= n; {
if _, err := d.ReadToken(); err != nil {
return err
}
}
}
return nil
}
// SkipUntil skips all tokens until the state machine
// is at or past the specified depth and length.
func (d *decoderState) SkipUntil(depth int, length int64) error {
for d.Tokens.Depth() > depth || (d.Tokens.Depth() == depth && d.Tokens.Last.Length() < length) {
if _, err := d.ReadToken(); err != nil {
return err
}
}
return nil
}
// ReadToken reads the next [Token], advancing the read offset.
// The returned token is only valid until the next Peek, Read, or Skip call.
// It returns [io.EOF] if there are no more tokens.
@@ -408,7 +484,7 @@ func (d *decoderState) ReadToken() (Token, error) {
if err == io.ErrUnexpectedEOF && d.Tokens.Depth() == 1 {
err = io.EOF // EOF possibly if no Tokens present after top-level value
}
return Token{}, err
return Token{}, wrapSyntacticError(d, err, pos, 0)
}
}
@@ -420,6 +496,7 @@ func (d *decoderState) ReadToken() (Token, error) {
pos += jsonwire.ConsumeWhitespace(d.buf[pos:])
if d.needMore(pos) {
if pos, err = d.consumeWhitespace(pos); err != nil {
err = wrapSyntacticError(d, err, pos, 0)
return Token{}, d.checkDelimBeforeIOError(delim, err)
}
}
@@ -437,13 +514,13 @@ func (d *decoderState) ReadToken() (Token, error) {
if jsonwire.ConsumeNull(d.buf[pos:]) == 0 {
pos, err = d.consumeLiteral(pos, "null")
if err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
} else {
pos += len("null")
}
if err = d.Tokens.appendLiteral(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos-len("null")) // report position at start of literal
return Token{}, wrapSyntacticError(d, err, pos-len("null"), +1) // report position at start of literal
}
d.prevStart, d.prevEnd = pos, pos
return Null, nil
@@ -452,13 +529,13 @@ func (d *decoderState) ReadToken() (Token, error) {
if jsonwire.ConsumeFalse(d.buf[pos:]) == 0 {
pos, err = d.consumeLiteral(pos, "false")
if err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
} else {
pos += len("false")
}
if err = d.Tokens.appendLiteral(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos-len("false")) // report position at start of literal
return Token{}, wrapSyntacticError(d, err, pos-len("false"), +1) // report position at start of literal
}
d.prevStart, d.prevEnd = pos, pos
return False, nil
@@ -467,13 +544,13 @@ func (d *decoderState) ReadToken() (Token, error) {
if jsonwire.ConsumeTrue(d.buf[pos:]) == 0 {
pos, err = d.consumeLiteral(pos, "true")
if err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
} else {
pos += len("true")
}
if err = d.Tokens.appendLiteral(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos-len("true")) // report position at start of literal
return Token{}, wrapSyntacticError(d, err, pos-len("true"), +1) // report position at start of literal
}
d.prevStart, d.prevEnd = pos, pos
return True, nil
@@ -486,23 +563,25 @@ func (d *decoderState) ReadToken() (Token, error) {
newAbsPos := d.baseOffset + int64(pos)
n = int(newAbsPos - oldAbsPos)
if err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
} else {
pos += n
}
if !d.Flags.Get(jsonflags.AllowDuplicateNames) && d.Tokens.Last.NeedObjectName() {
if !d.Tokens.Last.isValidNamespace() {
return Token{}, errInvalidNamespace
}
if d.Tokens.Last.isActiveNamespace() && !d.Namespaces.Last().insertQuoted(d.buf[pos-n:pos], flags.IsVerbatim()) {
err = newDuplicateNameError(d.buf[pos-n : pos])
return Token{}, d.injectSyntacticErrorWithPosition(err, pos-n) // report position at start of string
if d.Tokens.Last.NeedObjectName() {
if !d.Flags.Get(jsonflags.AllowDuplicateNames) {
if !d.Tokens.Last.isValidNamespace() {
return Token{}, wrapSyntacticError(d, errInvalidNamespace, pos-n, +1)
}
if d.Tokens.Last.isActiveNamespace() && !d.Namespaces.Last().insertQuoted(d.buf[pos-n:pos], flags.IsVerbatim()) {
err = wrapWithObjectName(ErrDuplicateName, d.buf[pos-n:pos])
return Token{}, wrapSyntacticError(d, err, pos-n, +1) // report position at start of string
}
}
d.Names.ReplaceLastQuotedOffset(pos - n) // only replace if insertQuoted succeeds
}
if err = d.Tokens.appendString(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos-n) // report position at start of string
return Token{}, wrapSyntacticError(d, err, pos-n, +1) // report position at start of string
}
d.prevStart, d.prevEnd = pos-n, pos
return Token{raw: &d.decodeBuffer, num: uint64(d.previousOffsetStart())}, nil
@@ -516,60 +595,60 @@ func (d *decoderState) ReadToken() (Token, error) {
newAbsPos := d.baseOffset + int64(pos)
n = int(newAbsPos - oldAbsPos)
if err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
} else {
pos += n
}
if err = d.Tokens.appendNumber(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos-n) // report position at start of number
return Token{}, wrapSyntacticError(d, err, pos-n, +1) // report position at start of number
}
d.prevStart, d.prevEnd = pos-n, pos
return Token{raw: &d.decodeBuffer, num: uint64(d.previousOffsetStart())}, nil
case '{':
if err = d.Tokens.pushObject(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
d.Names.push()
if !d.Flags.Get(jsonflags.AllowDuplicateNames) {
d.Names.push()
d.Namespaces.push()
}
pos += 1
d.prevStart, d.prevEnd = pos, pos
return ObjectStart, nil
return BeginObject, nil
case '}':
if err = d.Tokens.popObject(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
d.Names.pop()
if !d.Flags.Get(jsonflags.AllowDuplicateNames) {
d.Names.pop()
d.Namespaces.pop()
}
pos += 1
d.prevStart, d.prevEnd = pos, pos
return ObjectEnd, nil
return EndObject, nil
case '[':
if err = d.Tokens.pushArray(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
pos += 1
d.prevStart, d.prevEnd = pos, pos
return ArrayStart, nil
return BeginArray, nil
case ']':
if err = d.Tokens.popArray(); err != nil {
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
pos += 1
d.prevStart, d.prevEnd = pos, pos
return ArrayEnd, nil
return EndArray, nil
default:
err = newInvalidCharacterError(d.buf[pos:], "at start of token")
return Token{}, d.injectSyntacticErrorWithPosition(err, pos)
err = jsonwire.NewInvalidCharacterError(d.buf[pos:], "at start of value")
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
}
@@ -612,7 +691,7 @@ func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) {
if err == io.ErrUnexpectedEOF && d.Tokens.Depth() == 1 {
err = io.EOF // EOF possibly if no Tokens present after top-level value
}
return nil, err
return nil, wrapSyntacticError(d, err, pos, 0)
}
}
@@ -624,6 +703,7 @@ func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) {
pos += jsonwire.ConsumeWhitespace(d.buf[pos:])
if d.needMore(pos) {
if pos, err = d.consumeWhitespace(pos); err != nil {
err = wrapSyntacticError(d, err, pos, 0)
return nil, d.checkDelimBeforeIOError(delim, err)
}
}
@@ -640,20 +720,22 @@ func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) {
newAbsPos := d.baseOffset + int64(pos)
n := int(newAbsPos - oldAbsPos)
if err != nil {
return nil, d.injectSyntacticErrorWithPosition(err, pos)
return nil, wrapSyntacticError(d, err, pos, +1)
}
switch next {
case 'n', 't', 'f':
err = d.Tokens.appendLiteral()
case '"':
if !d.Flags.Get(jsonflags.AllowDuplicateNames) && d.Tokens.Last.NeedObjectName() {
if !d.Tokens.Last.isValidNamespace() {
err = errInvalidNamespace
break
}
if d.Tokens.Last.isActiveNamespace() && !d.Namespaces.Last().insertQuoted(d.buf[pos-n:pos], flags.IsVerbatim()) {
err = newDuplicateNameError(d.buf[pos-n : pos])
break
if d.Tokens.Last.NeedObjectName() {
if !d.Flags.Get(jsonflags.AllowDuplicateNames) {
if !d.Tokens.Last.isValidNamespace() {
err = errInvalidNamespace
break
}
if d.Tokens.Last.isActiveNamespace() && !d.Namespaces.Last().insertQuoted(d.buf[pos-n:pos], flags.IsVerbatim()) {
err = wrapWithObjectName(ErrDuplicateName, d.buf[pos-n:pos])
break
}
}
d.Names.ReplaceLastQuotedOffset(pos - n) // only replace if insertQuoted succeeds
}
@@ -676,19 +758,36 @@ func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) {
}
}
if err != nil {
return nil, d.injectSyntacticErrorWithPosition(err, pos-n) // report position at start of value
return nil, wrapSyntacticError(d, err, pos-n, +1) // report position at start of value
}
d.prevEnd = pos
d.prevStart = pos - n
return d.buf[pos-n : pos : pos], nil
}
// CheckNextValue checks whether the next value is syntactically valid,
// but does not advance the read offset.
func (d *decoderState) CheckNextValue() error {
d.PeekKind() // populates d.peekPos and d.peekErr
pos, err := d.peekPos, d.peekErr
d.peekPos, d.peekErr = 0, nil
if err != nil {
return err
}
var flags jsonwire.ValueFlags
if pos, err := d.consumeValue(&flags, pos, d.Tokens.Depth()); err != nil {
return wrapSyntacticError(d, err, pos, +1)
}
return nil
}
// CheckEOF verifies that the input has no more data.
func (d *decoderState) CheckEOF() error {
switch pos, err := d.consumeWhitespace(d.prevEnd); err {
case nil:
err := newInvalidCharacterError(d.buf[pos:], "after top-level value")
return d.injectSyntacticErrorWithPosition(err, pos)
err := jsonwire.NewInvalidCharacterError(d.buf[pos:], "after top-level value")
return wrapSyntacticError(d, err, pos, 0)
case io.ErrUnexpectedEOF:
return nil
default:
@@ -763,14 +862,17 @@ func (d *decoderState) consumeValue(flags *jsonwire.ValueFlags, pos, depth int)
case '[':
return d.consumeArray(flags, pos, depth)
default:
return pos, newInvalidCharacterError(d.buf[pos:], "at start of value")
if (d.Tokens.Last.isObject() && next == ']') || (d.Tokens.Last.isArray() && next == '}') {
return pos, errMismatchDelim
}
return pos, jsonwire.NewInvalidCharacterError(d.buf[pos:], "at start of value")
}
if err == io.ErrUnexpectedEOF {
absPos := d.baseOffset + int64(pos)
err = d.fetch() // will mutate d.buf and invalidate pos
pos = int(absPos - d.baseOffset)
if err != nil {
return pos, err
return pos + n, err
}
continue
}
@@ -788,7 +890,7 @@ func (d *decoderState) consumeLiteral(pos int, lit string) (newPos int, err erro
err = d.fetch() // will mutate d.buf and invalidate pos
pos = int(absPos - d.baseOffset)
if err != nil {
return pos, err
return pos + n, err
}
continue
}
@@ -807,7 +909,7 @@ func (d *decoderState) consumeString(flags *jsonwire.ValueFlags, pos int) (newPo
err = d.fetch() // will mutate d.buf and invalidate pos
pos = int(absPos - d.baseOffset)
if err != nil {
return pos, err
return pos + n, err
}
continue
}
@@ -894,19 +996,21 @@ func (d *decoderState) consumeObject(flags *jsonwire.ValueFlags, pos, depth int)
} else {
pos += n
}
if !d.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(d.buf[pos-n:pos], flags2.IsVerbatim()) {
return pos - n, newDuplicateNameError(d.buf[pos-n : pos])
quotedName := d.buf[pos-n : pos]
if !d.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(quotedName, flags2.IsVerbatim()) {
return pos - n, wrapWithObjectName(ErrDuplicateName, quotedName)
}
// Handle after name.
pos += jsonwire.ConsumeWhitespace(d.buf[pos:])
if d.needMore(pos) {
if pos, err = d.consumeWhitespace(pos); err != nil {
return pos, err
return pos, wrapWithObjectName(err, quotedName)
}
}
if d.buf[pos] != ':' {
return pos, newInvalidCharacterError(d.buf[pos:], "after object name (expecting ':')")
err := jsonwire.NewInvalidCharacterError(d.buf[pos:], "after object name (expecting ':')")
return pos, wrapWithObjectName(err, quotedName)
}
pos++
@@ -914,12 +1018,12 @@ func (d *decoderState) consumeObject(flags *jsonwire.ValueFlags, pos, depth int)
pos += jsonwire.ConsumeWhitespace(d.buf[pos:])
if d.needMore(pos) {
if pos, err = d.consumeWhitespace(pos); err != nil {
return pos, err
return pos, wrapWithObjectName(err, quotedName)
}
}
pos, err = d.consumeValue(flags, pos, depth)
if err != nil {
return pos, err
return pos, wrapWithObjectName(err, quotedName)
}
// Handle after value.
@@ -937,7 +1041,7 @@ func (d *decoderState) consumeObject(flags *jsonwire.ValueFlags, pos, depth int)
pos++
return pos, nil
default:
return pos, newInvalidCharacterError(d.buf[pos:], "after object value (expecting ',' or '}')")
return pos, jsonwire.NewInvalidCharacterError(d.buf[pos:], "after object value (expecting ',' or '}')")
}
}
}
@@ -965,6 +1069,7 @@ func (d *decoderState) consumeArray(flags *jsonwire.ValueFlags, pos, depth int)
return pos, nil
}
var idx int64
depth++
for {
// Handle before value.
@@ -976,7 +1081,7 @@ func (d *decoderState) consumeArray(flags *jsonwire.ValueFlags, pos, depth int)
}
pos, err = d.consumeValue(flags, pos, depth)
if err != nil {
return pos, err
return pos, wrapWithArrayIndex(err, idx)
}
// Handle after value.
@@ -989,12 +1094,13 @@ func (d *decoderState) consumeArray(flags *jsonwire.ValueFlags, pos, depth int)
switch d.buf[pos] {
case ',':
pos++
idx++
continue
case ']':
pos++
return pos, nil
default:
return pos, newInvalidCharacterError(d.buf[pos:], "after array value (expecting ',' or ']')")
return pos, jsonwire.NewInvalidCharacterError(d.buf[pos:], "after array element (expecting ',' or ']')")
}
}
}
@@ -1017,8 +1123,8 @@ func (d *Decoder) UnreadBuffer() []byte {
// StackDepth returns the depth of the state machine for read JSON data.
// Each level on the stack represents a nested JSON object or array.
// It is incremented whenever an [ObjectStart] or [ArrayStart] token is encountered
// and decremented whenever an [ObjectEnd] or [ArrayEnd] token is encountered.
// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered
// and decremented whenever an [EndObject] or [EndArray] token is encountered.
// The depth is zero-indexed, where zero represents the top-level JSON value.
func (d *Decoder) StackDepth() int {
// NOTE: Keep in sync with Encoder.StackDepth.
@@ -1037,7 +1143,7 @@ func (d *Decoder) StackDepth() int {
// Each name and value in a JSON object is counted separately,
// so the effective number of members would be half the length.
// A complete JSON object must have an even length.
func (d *Decoder) StackIndex(i int) (Kind, int) {
func (d *Decoder) StackIndex(i int) (Kind, int64) {
// NOTE: Keep in sync with Encoder.StackIndex.
switch s := d.s.Tokens.index(i); {
case i > 0 && s.isObject():
@@ -1050,9 +1156,11 @@ func (d *Decoder) StackIndex(i int) (Kind, int) {
}
// StackPointer returns a JSON Pointer (RFC 6901) to the most recently read value.
// Object names are only present if [AllowDuplicateNames] is false, otherwise
// object members are represented using their index within the object.
func (d *Decoder) StackPointer() string {
d.s.Names.copyQuotedBuffer(d.s.buf)
return string(d.s.appendStackPointer(nil))
func (d *Decoder) StackPointer() Pointer {
return Pointer(d.s.AppendStackPointer(nil, -1))
}
func (d *decoderState) AppendStackPointer(b []byte, where int) []byte {
d.Names.copyQuotedBuffer(d.buf)
return d.state.appendStackPointer(b, where)
}

View File

@@ -25,19 +25,19 @@ import (
//
// can be composed with the following calls (ignoring errors for brevity):
//
// e.WriteToken(ObjectStart) // {
// e.WriteToken(BeginObject) // {
// e.WriteToken(String("name")) // "name"
// e.WriteToken(String("value")) // "value"
// e.WriteValue(Value(`"array"`)) // "array"
// e.WriteToken(ArrayStart) // [
// e.WriteToken(BeginArray) // [
// e.WriteToken(Null) // null
// e.WriteToken(False) // false
// e.WriteValue(Value("true")) // true
// e.WriteToken(Float(3.14159)) // 3.14159
// e.WriteToken(ArrayEnd) // ]
// e.WriteToken(EndArray) // ]
// e.WriteValue(Value(`"object"`)) // "object"
// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}
// e.WriteToken(ObjectEnd) // }
// e.WriteToken(EndObject) // }
//
// The above is one of many possible sequence of calls and
// may not represent the most sensible method to call for any given token/value.
@@ -94,8 +94,8 @@ func NewEncoder(w io.Writer, opts ...Options) *Encoder {
// Reset resets an encoder such that it is writing afresh to w and
// configured with the provided options. Reset must not be called on
// a Encoder passed to the [encoding/json/v2.MarshalerV2.MarshalJSONV2] method
// or the [encoding/json/v2.MarshalFuncV2] function.
// a Encoder passed to the [encoding/json/v2.MarshalerTo.MarshalJSONTo] method
// or the [encoding/json/v2.MarshalToFunc] function.
func (e *Encoder) Reset(w io.Writer, opts ...Options) {
switch {
case e == nil:
@@ -103,7 +103,7 @@ func (e *Encoder) Reset(w io.Writer, opts ...Options) {
case w == nil:
panic("jsontext: invalid nil io.Writer")
case e.s.Flags.Get(jsonflags.WithinArshalCall):
panic("jsontext: cannot reset Encoder passed to json.MarshalerV2")
panic("jsontext: cannot reset Encoder passed to json.MarshalerTo")
}
e.s.reset(nil, w, opts...)
}
@@ -114,13 +114,35 @@ func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) {
if bb, ok := w.(*bytes.Buffer); ok && bb != nil {
e.Buf = bb.Bytes()[bb.Len():] // alias the unused buffer of bb
}
e.Struct = jsonopts.Struct{}
e.Struct.Join(opts...)
if e.Flags.Get(jsonflags.Expand) && !e.Flags.Has(jsonflags.Indent) {
e.Indent = "\t"
opts2 := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts
opts2.Join(opts...)
e.Struct = opts2
if e.Flags.Get(jsonflags.Multiline) {
if !e.Flags.Has(jsonflags.SpaceAfterColon) {
e.Flags.Set(jsonflags.SpaceAfterColon | 1)
}
if !e.Flags.Has(jsonflags.SpaceAfterComma) {
e.Flags.Set(jsonflags.SpaceAfterComma | 0)
}
if !e.Flags.Has(jsonflags.Indent) {
e.Flags.Set(jsonflags.Indent | 1)
e.Indent = "\t"
}
}
}
// Options returns the options used to construct the decoder and
// may additionally contain semantic options passed to a
// [encoding/json/v2.MarshalEncode] call.
//
// If operating within
// a [encoding/json/v2.MarshalerTo.MarshalJSONTo] method call or
// a [encoding/json/v2.MarshalToFunc] function call,
// then the returned options are only valid within the call.
func (e *Encoder) Options() Options {
return &e.s.Struct
}
// NeedFlush determines whether to flush at this point.
func (e *encoderState) NeedFlush() bool {
// NOTE: This function is carefully written to be inlinable.
@@ -200,17 +222,7 @@ func (e *encoderState) Flush() error {
return nil
}
// injectSyntacticErrorWithPosition wraps a SyntacticError with the position,
// otherwise it returns the error as is.
// It takes a position relative to the start of the start of e.buf.
func (e *encodeBuffer) injectSyntacticErrorWithPosition(err error, pos int) error {
if serr, ok := err.(*SyntacticError); ok {
return serr.withOffset(e.baseOffset + int64(pos))
}
return err
}
func (d *encodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) }
func (e *encodeBuffer) previousOffsetEnd() int64 { return e.baseOffset + int64(len(e.Buf)) }
func (e *encodeBuffer) unflushedBuffer() []byte { return e.Buf }
@@ -219,7 +231,7 @@ func (e *encodeBuffer) unflushedBuffer() []byte { return e.Buf }
func (e *encoderState) avoidFlush() bool {
switch {
case e.Tokens.Last.Length() == 0:
// Never flush after ObjectStart or ArrayStart since we don't know yet
// Never flush after BeginObject or BeginArray since we don't know yet
// if the object or array will end up being empty.
return true
case e.Tokens.Last.needObjectValue():
@@ -286,11 +298,11 @@ func (e *encoderState) UnwriteEmptyObjectMember(prevName *string) bool {
if e.Tokens.Last.isActiveNamespace() {
e.Namespaces.Last().removeLast()
}
e.Names.clearLast()
if prevName != nil {
e.Names.copyQuotedBuffer(e.Buf) // required by objectNameStack.replaceLastUnquotedName
e.Names.replaceLastUnquotedName(*prevName)
}
}
e.Names.clearLast()
if prevName != nil {
e.Names.copyQuotedBuffer(e.Buf) // required by objectNameStack.replaceLastUnquotedName
e.Names.replaceLastUnquotedName(*prevName)
}
return true
}
@@ -314,8 +326,8 @@ func (e *encoderState) UnwriteOnlyObjectMemberName() string {
if e.Tokens.Last.isActiveNamespace() {
e.Namespaces.Last().removeLast()
}
e.Names.clearLast()
}
e.Names.clearLast()
return name
}
@@ -337,7 +349,7 @@ func (e *encoderState) WriteToken(t Token) error {
// Append any delimiters or optional whitespace.
b = e.Tokens.MayAppendDelim(b, k)
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.AnyWhitespace) {
b = e.appendWhitespace(b, k)
}
pos := len(b) // offset before the token
@@ -358,20 +370,22 @@ func (e *encoderState) WriteToken(t Token) error {
if b, err = t.appendString(b, &e.Flags); err != nil {
break
}
if !e.Flags.Get(jsonflags.AllowDuplicateNames) && e.Tokens.Last.NeedObjectName() {
if !e.Tokens.Last.isValidNamespace() {
err = errInvalidNamespace
break
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) {
err = newDuplicateNameError(b[pos:])
break
if e.Tokens.Last.NeedObjectName() {
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
if !e.Tokens.Last.isValidNamespace() {
err = errInvalidNamespace
break
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) {
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
break
}
}
e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds
}
err = e.Tokens.appendString()
case '0':
if b, err = t.appendNumber(b, e.Flags.Get(jsonflags.CanonicalizeNumbers)); err != nil {
if b, err = t.appendNumber(b, &e.Flags); err != nil {
break
}
err = e.Tokens.appendNumber()
@@ -380,8 +394,8 @@ func (e *encoderState) WriteToken(t Token) error {
if err = e.Tokens.pushObject(); err != nil {
break
}
e.Names.push()
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
e.Names.push()
e.Namespaces.push()
}
case '}':
@@ -389,8 +403,8 @@ func (e *encoderState) WriteToken(t Token) error {
if err = e.Tokens.popObject(); err != nil {
break
}
e.Names.pop()
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
e.Names.pop()
e.Namespaces.pop()
}
case '[':
@@ -400,10 +414,10 @@ func (e *encoderState) WriteToken(t Token) error {
b = append(b, ']')
err = e.Tokens.popArray()
default:
err = &SyntacticError{str: "invalid json.Token"}
err = errInvalidToken
}
if err != nil {
return e.injectSyntacticErrorWithPosition(err, pos)
return wrapSyntacticError(e, err, pos, +1)
}
// Finish off the buffer and store it back into e.
@@ -428,7 +442,7 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
// Append any delimiters or optional whitespace.
b = e.Tokens.MayAppendDelim(b, k)
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.AnyWhitespace) {
b = e.appendWhitespace(b, k)
}
pos := len(b) // offset before the token
@@ -453,30 +467,32 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
b, err = jsonwire.AppendQuote(b[:pos], string(b2), &e.Flags)
e.unusedCache = b2[:0]
if err != nil {
return e.injectSyntacticErrorWithPosition(err, pos)
return wrapSyntacticError(e, err, pos, +1)
}
}
// Update the state machine.
if !e.Flags.Get(jsonflags.AllowDuplicateNames) && e.Tokens.Last.NeedObjectName() {
if !e.Tokens.Last.isValidNamespace() {
return errInvalidNamespace
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) {
err := newDuplicateNameError(b[pos:])
return e.injectSyntacticErrorWithPosition(err, pos)
if e.Tokens.Last.NeedObjectName() {
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
if !e.Tokens.Last.isValidNamespace() {
return wrapSyntacticError(e, err, pos, +1)
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) {
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
return wrapSyntacticError(e, err, pos, +1)
}
}
e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds
}
if err := e.Tokens.appendString(); err != nil {
return e.injectSyntacticErrorWithPosition(err, pos)
return wrapSyntacticError(e, err, pos, +1)
}
case '0':
if b, err = appendFn(b); err != nil {
return err
}
if err := e.Tokens.appendNumber(); err != nil {
return e.injectSyntacticErrorWithPosition(err, pos)
return wrapSyntacticError(e, err, pos, +1)
}
default:
panic("BUG: invalid kind")
@@ -513,7 +529,7 @@ func (e *encoderState) WriteValue(v Value) error {
// Append any delimiters or optional whitespace.
b = e.Tokens.MayAppendDelim(b, k)
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.AnyWhitespace) {
b = e.appendWhitespace(b, k)
}
pos := len(b) // offset before the value
@@ -523,13 +539,13 @@ func (e *encoderState) WriteValue(v Value) error {
n += jsonwire.ConsumeWhitespace(v[n:])
b, m, err := e.reformatValue(b, v[n:], e.Tokens.Depth())
if err != nil {
return e.injectSyntacticErrorWithPosition(err, pos+n+m)
return wrapSyntacticError(e, err, pos+n+m, +1)
}
n += m
n += jsonwire.ConsumeWhitespace(v[n:])
if len(v) > n {
err = newInvalidCharacterError(v[n:], "after top-level value")
return e.injectSyntacticErrorWithPosition(err, pos+n)
err = jsonwire.NewInvalidCharacterError(v[n:], "after top-level value")
return wrapSyntacticError(e, err, pos+n, 0)
}
// Append the kind to the state machine.
@@ -537,14 +553,16 @@ func (e *encoderState) WriteValue(v Value) error {
case 'n', 'f', 't':
err = e.Tokens.appendLiteral()
case '"':
if !e.Flags.Get(jsonflags.AllowDuplicateNames) && e.Tokens.Last.NeedObjectName() {
if !e.Tokens.Last.isValidNamespace() {
err = errInvalidNamespace
break
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) {
err = newDuplicateNameError(b[pos:])
break
if e.Tokens.Last.NeedObjectName() {
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
if !e.Tokens.Last.isValidNamespace() {
err = errInvalidNamespace
break
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) {
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
break
}
}
e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds
}
@@ -558,6 +576,9 @@ func (e *encoderState) WriteValue(v Value) error {
if err = e.Tokens.popObject(); err != nil {
panic("BUG: popObject should never fail immediately after pushObject: " + err.Error())
}
if e.Flags.Get(jsonflags.ReorderRawObjects) {
mustReorderObjects(b[pos:])
}
case '[':
if err = e.Tokens.pushArray(); err != nil {
break
@@ -565,9 +586,12 @@ func (e *encoderState) WriteValue(v Value) error {
if err = e.Tokens.popArray(); err != nil {
panic("BUG: popArray should never fail immediately after pushArray: " + err.Error())
}
if e.Flags.Get(jsonflags.ReorderRawObjects) {
mustReorderObjects(b[pos:])
}
}
if err != nil {
return e.injectSyntacticErrorWithPosition(err, pos)
return wrapSyntacticError(e, err, pos, +1)
}
// Finish off the buffer and store it back into e.
@@ -578,13 +602,47 @@ func (e *encoderState) WriteValue(v Value) error {
return nil
}
// CountNextDelimWhitespace counts the number of bytes of delimiter and
// whitespace bytes assuming the upcoming token is a JSON value.
// This method is used for error reporting at the semantic layer.
func (e *encoderState) CountNextDelimWhitespace() (n int) {
const next = Kind('"') // arbitrary kind as next JSON value
delim := e.Tokens.needDelim(next)
if delim > 0 {
n += len(",") | len(":")
}
if delim == ':' {
if e.Flags.Get(jsonflags.SpaceAfterColon) {
n += len(" ")
}
} else {
if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) {
n += len(" ")
}
if e.Flags.Get(jsonflags.Multiline) {
if m := e.Tokens.NeedIndent(next); m > 0 {
n += len("\n") + len(e.IndentPrefix) + (m-1)*len(e.Indent)
}
}
}
return n
}
// appendWhitespace appends whitespace that immediately precedes the next token.
func (e *encoderState) appendWhitespace(b []byte, next Kind) []byte {
if e.Tokens.needDelim(next) == ':' {
return append(b, ' ')
if delim := e.Tokens.needDelim(next); delim == ':' {
if e.Flags.Get(jsonflags.SpaceAfterColon) {
b = append(b, ' ')
}
} else {
return e.AppendIndent(b, e.Tokens.NeedIndent(next))
if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) {
b = append(b, ' ')
}
if e.Flags.Get(jsonflags.Multiline) {
b = e.AppendIndent(b, e.Tokens.NeedIndent(next))
}
}
return b
}
// AppendIndent appends the appropriate number of indentation characters
@@ -630,22 +688,22 @@ func (e *encoderState) reformatValue(dst []byte, src Value, depth int) ([]byte,
return append(dst, "true"...), len("true"), nil
case '"':
if n := jsonwire.ConsumeSimpleString(src); n > 0 {
dst, src = append(dst, src[:n]...), src[n:] // copy simple strings verbatim
dst = append(dst, src[:n]...) // copy simple strings verbatim
return dst, n, nil
}
return jsonwire.ReformatString(dst, src, &e.Flags)
case '0':
if n := jsonwire.ConsumeSimpleNumber(src); n > 0 && !e.Flags.Get(jsonflags.CanonicalizeNumbers) {
dst, src = append(dst, src[:n]...), src[n:] // copy simple numbers verbatim
dst = append(dst, src[:n]...) // copy simple numbers verbatim
return dst, n, nil
}
return jsonwire.ReformatNumber(dst, src, e.Flags.Get(jsonflags.CanonicalizeNumbers))
return jsonwire.ReformatNumber(dst, src, &e.Flags)
case '{':
return e.reformatObject(dst, src, depth)
case '[':
return e.reformatArray(dst, src, depth)
default:
return dst, 0, newInvalidCharacterError(src, "at start of value")
return dst, 0, jsonwire.NewInvalidCharacterError(src, "at start of value")
}
}
@@ -683,7 +741,7 @@ func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte,
depth++
for {
// Append optional newline and indentation.
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.Multiline) {
dst = e.AppendIndent(dst, depth)
}
@@ -693,7 +751,8 @@ func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte,
return dst, n, io.ErrUnexpectedEOF
}
m := jsonwire.ConsumeSimpleString(src[n:])
if m > 0 {
isVerbatim := m > 0
if isVerbatim {
dst = append(dst, src[n:n+m]...)
} else {
dst, m, err = jsonwire.ReformatString(dst, src[n:], &e.Flags)
@@ -701,34 +760,35 @@ func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte,
return dst, n + m, err
}
}
// TODO: Specify whether the name is verbatim or not.
if !e.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(src[n:n+m], false) {
return dst, n, newDuplicateNameError(src[n : n+m])
quotedName := src[n : n+m]
if !e.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(quotedName, isVerbatim) {
return dst, n, wrapWithObjectName(ErrDuplicateName, quotedName)
}
n += m
// Append colon.
n += jsonwire.ConsumeWhitespace(src[n:])
if uint(len(src)) <= uint(n) {
return dst, n, io.ErrUnexpectedEOF
return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName)
}
if src[n] != ':' {
return dst, n, newInvalidCharacterError(src[n:], "after object name (expecting ':')")
err = jsonwire.NewInvalidCharacterError(src[n:], "after object name (expecting ':')")
return dst, n, wrapWithObjectName(err, quotedName)
}
dst = append(dst, ':')
n += len(":")
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.SpaceAfterColon) {
dst = append(dst, ' ')
}
// Append object value.
n += jsonwire.ConsumeWhitespace(src[n:])
if uint(len(src)) <= uint(n) {
return dst, n, io.ErrUnexpectedEOF
return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName)
}
dst, m, err = e.reformatValue(dst, src[n:], depth)
if err != nil {
return dst, n + m, err
return dst, n + m, wrapWithObjectName(err, quotedName)
}
n += m
@@ -740,17 +800,20 @@ func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte,
switch src[n] {
case ',':
dst = append(dst, ',')
if e.Flags.Get(jsonflags.SpaceAfterComma) {
dst = append(dst, ' ')
}
n += len(",")
continue
case '}':
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.Multiline) {
dst = e.AppendIndent(dst, depth-1)
}
dst = append(dst, '}')
n += len("}")
return dst, n, nil
default:
return dst, n, newInvalidCharacterError(src[n:], "after object value (expecting ',' or '}')")
return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "after object value (expecting ',' or '}')")
}
}
}
@@ -779,11 +842,12 @@ func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte,
return dst, n, nil
}
var idx int64
var err error
depth++
for {
// Append optional newline and indentation.
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.Multiline) {
dst = e.AppendIndent(dst, depth)
}
@@ -795,7 +859,7 @@ func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte,
var m int
dst, m, err = e.reformatValue(dst, src[n:], depth)
if err != nil {
return dst, n + m, err
return dst, n + m, wrapWithArrayIndex(err, idx)
}
n += m
@@ -807,17 +871,21 @@ func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte,
switch src[n] {
case ',':
dst = append(dst, ',')
if e.Flags.Get(jsonflags.SpaceAfterComma) {
dst = append(dst, ' ')
}
n += len(",")
idx++
continue
case ']':
if e.Flags.Get(jsonflags.Expand) {
if e.Flags.Get(jsonflags.Multiline) {
dst = e.AppendIndent(dst, depth-1)
}
dst = append(dst, ']')
n += len("]")
return dst, n, nil
default:
return dst, n, newInvalidCharacterError(src[n:], "after array value (expecting ',' or ']')")
return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "after array value (expecting ',' or ']')")
}
}
}
@@ -859,8 +927,8 @@ func (e *Encoder) UnusedBuffer() []byte {
// StackDepth returns the depth of the state machine for written JSON data.
// Each level on the stack represents a nested JSON object or array.
// It is incremented whenever an [ObjectStart] or [ArrayStart] token is encountered
// and decremented whenever an [ObjectEnd] or [ArrayEnd] token is encountered.
// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered
// and decremented whenever an [EndObject] or [EndArray] token is encountered.
// The depth is zero-indexed, where zero represents the top-level JSON value.
func (e *Encoder) StackDepth() int {
// NOTE: Keep in sync with Decoder.StackDepth.
@@ -879,7 +947,7 @@ func (e *Encoder) StackDepth() int {
// Each name and value in a JSON object is counted separately,
// so the effective number of members would be half the length.
// A complete JSON object must have an even length.
func (e *Encoder) StackIndex(i int) (Kind, int) {
func (e *Encoder) StackIndex(i int) (Kind, int64) {
// NOTE: Keep in sync with Decoder.StackIndex.
switch s := e.s.Tokens.index(i); {
case i > 0 && s.isObject():
@@ -892,9 +960,11 @@ func (e *Encoder) StackIndex(i int) (Kind, int) {
}
// StackPointer returns a JSON Pointer (RFC 6901) to the most recently written value.
// Object names are only present if [AllowDuplicateNames] is false, otherwise
// object members are represented using their index within the object.
func (e *Encoder) StackPointer() string {
e.s.Names.copyQuotedBuffer(e.s.Buf)
return string(e.s.appendStackPointer(nil))
func (e *Encoder) StackPointer() Pointer {
return Pointer(e.s.AppendStackPointer(nil, -1))
}
func (e *encoderState) AppendStackPointer(b []byte, where int) []byte {
e.Names.copyQuotedBuffer(e.Buf)
return e.state.appendStackPointer(b, where)
}

View File

@@ -5,6 +5,10 @@
package jsontext
import (
"bytes"
"io"
"strconv"
"github.com/go-json-experiment/json/internal/jsonwire"
)
@@ -32,29 +36,145 @@ type SyntacticError struct {
// ByteOffset indicates that an error occurred after this byte offset.
ByteOffset int64
str string
// JSONPointer indicates that an error occurred within this JSON value
// as indicated using the JSON Pointer notation (see RFC 6901).
JSONPointer Pointer
// Err is the underlying error.
Err error
}
// wrapSyntacticError wraps an error and annotates it with a precise location
// using the provided [encoderState] or [decoderState].
// If err is an [ioError] or [io.EOF], then it is not wrapped.
//
// It takes a relative offset pos that can be resolved into
// an absolute offset using state.offsetAt.
//
// It takes a where that specify how the JSON pointer is derived.
// If the underlying error is a [pointerSuffixError],
// then the suffix is appended to the derived pointer.
func wrapSyntacticError(state interface {
offsetAt(pos int) int64
AppendStackPointer(b []byte, where int) []byte
}, err error, pos, where int) error {
if _, ok := err.(*ioError); err == io.EOF || ok {
return err
}
offset := state.offsetAt(pos)
ptr := state.AppendStackPointer(nil, where)
if serr, ok := err.(*pointerSuffixError); ok {
ptr = serr.appendPointer(ptr)
err = serr.error
}
if d, ok := state.(*decoderState); ok && err == errMismatchDelim {
where := "at start of value"
if len(d.Tokens.Stack) > 0 && d.Tokens.Last.Length() > 0 {
switch {
case d.Tokens.Last.isArray():
where = "after array element (expecting ',' or ']')"
ptr = []byte(Pointer(ptr).Parent()) // problem is with parent array
case d.Tokens.Last.isObject():
where = "after object value (expecting ',' or '}')"
ptr = []byte(Pointer(ptr).Parent()) // problem is with parent object
}
}
err = jsonwire.NewInvalidCharacterError(d.buf[pos:], where)
}
return &SyntacticError{ByteOffset: offset, JSONPointer: Pointer(ptr), Err: err}
}
func (e *SyntacticError) Error() string {
return errorPrefix + e.str
}
func (e *SyntacticError) withOffset(pos int64) error {
return &SyntacticError{ByteOffset: pos, str: e.str}
pointer := e.JSONPointer
offset := e.ByteOffset
b := []byte(errorPrefix)
if e.Err != nil {
b = append(b, e.Err.Error()...)
if e.Err == ErrDuplicateName {
b = strconv.AppendQuote(append(b, ' '), pointer.LastToken())
pointer = pointer.Parent()
offset = 0 // not useful to print offset for duplicate names
}
} else {
b = append(b, "syntactic error"...)
}
if pointer != "" {
b = strconv.AppendQuote(append(b, " within "...), jsonwire.TruncatePointer(string(pointer), 100))
}
if offset > 0 {
b = strconv.AppendInt(append(b, " after offset "...), offset, 10)
}
return string(b)
}
func newDuplicateNameError[Bytes ~[]byte | ~string](quoted Bytes) *SyntacticError {
return &SyntacticError{str: "duplicate name " + string(quoted) + " in object"}
func (e *SyntacticError) Unwrap() error {
return e.Err
}
func newInvalidCharacterError[Bytes ~[]byte | ~string](prefix Bytes, where string) *SyntacticError {
what := jsonwire.QuoteRune(prefix)
return &SyntacticError{str: "invalid character " + what + " " + where}
// pointerSuffixError represents a JSON pointer suffix to be appended
// to [SyntacticError.JSONPointer]. It is an internal error type
// used within this package and does not appear in the public API.
//
// This type is primarily used to annotate errors in Encoder.WriteValue
// and Decoder.ReadValue with precise positions.
// At the time WriteValue or ReadValue is called, a JSON pointer to the
// upcoming value can be constructed using the Encoder/Decoder state.
// However, tracking pointers within values during normal operation
// would incur a performance penalty in the error-free case.
//
// To provide precise error locations without this overhead,
// the error is wrapped with object names or array indices
// as the call stack is popped when an error occurs.
// Since this happens in reverse order, pointerSuffixError holds
// the pointer in reverse and is only later reversed when appending to
// the pointer prefix.
//
// For example, if the encoder is at "/alpha/bravo/charlie"
// and an error occurs in WriteValue at "/xray/yankee/zulu", then
// the final pointer should be "/alpha/bravo/charlie/xray/yankee/zulu".
//
// As pointerSuffixError is populated during the error return path,
// it first contains "/zulu", then "/zulu/yankee",
// and finally "/zulu/yankee/xray".
// These tokens are reversed and concatenated to "/alpha/bravo/charlie"
// to form the full pointer.
type pointerSuffixError struct {
error
// reversePointer is a JSON pointer, but with each token in reverse order.
reversePointer []byte
}
// TODO: Error types between "json", "jsontext", and "jsonwire" is a mess.
// Clean this up.
func init() {
// Inject behavior in "jsonwire" so that it can produce SyntacticError types.
jsonwire.NewError = func(s string) error { return &SyntacticError{str: s} }
jsonwire.ErrInvalidUTF8 = &SyntacticError{str: jsonwire.ErrInvalidUTF8.Error()}
// wrapWithObjectName wraps err with a JSON object name access,
// which must be a valid quoted JSON string.
func wrapWithObjectName(err error, quotedName []byte) error {
serr, _ := err.(*pointerSuffixError)
if serr == nil {
serr = &pointerSuffixError{error: err}
}
name := jsonwire.UnquoteMayCopy(quotedName, false)
serr.reversePointer = appendEscapePointerName(append(serr.reversePointer, '/'), name)
return serr
}
// wrapWithArrayIndex wraps err with a JSON array index access.
func wrapWithArrayIndex(err error, index int64) error {
serr, _ := err.(*pointerSuffixError)
if serr == nil {
serr = &pointerSuffixError{error: err}
}
serr.reversePointer = strconv.AppendUint(append(serr.reversePointer, '/'), uint64(index), 10)
return serr
}
// appendPointer appends the path encoded in e to the end of pointer.
func (e *pointerSuffixError) appendPointer(pointer []byte) []byte {
// Copy each token in reversePointer to the end of pointer in reverse order.
// Double reversal means that the appended suffix is now in forward order.
bi, bo := e.reversePointer, pointer
for len(bi) > 0 {
i := bytes.LastIndexByte(bi, '/')
bi, bo = bi[:i], append(bo, bi[i:]...)
}
return bo
}

View File

@@ -69,15 +69,7 @@ func (export) PutStreamingDecoder(d *Decoder) {
putStreamingDecoder(d)
}
func (export) NewDuplicateNameError(quoted []byte, pos int64) error {
return newDuplicateNameError(quoted).withOffset(pos)
}
func (export) NewInvalidCharacterError(prefix, where string, pos int64) error {
return newInvalidCharacterError(prefix, where).withOffset(pos)
}
func (export) NewMissingNameError(pos int64) error {
return errMissingName.withOffset(pos)
}
func (export) NewInvalidUTF8Error(pos int64) error {
return errInvalidUTF8.withOffset(pos)
func (export) IsIOError(err error) bool {
_, ok := err.(*ioError)
return ok
}

View File

@@ -17,6 +17,25 @@ import (
// Each function takes in a variadic list of options, where properties
// set in latter options override the value of previously set properties.
//
// There is a single Options type, which is used with both encoding and decoding.
// Some options affect both operations, while others only affect one operation:
//
// - [AllowDuplicateNames] affects encoding and decoding
// - [AllowInvalidUTF8] affects encoding and decoding
// - [EscapeForHTML] affects encoding only
// - [EscapeForJS] affects encoding only
// - [PreserveRawStrings] affects encoding only
// - [CanonicalizeRawInts] affects encoding only
// - [CanonicalizeRawFloats] affects encoding only
// - [ReorderRawObjects] affects encoding only
// - [SpaceAfterColon] affects encoding only
// - [SpaceAfterComma] affects encoding only
// - [Multiline] affects encoding only
// - [WithIndent] affects encoding only
// - [WithIndentPrefix] affects encoding only
//
// Options that do not affect a particular operation are ignored.
//
// The Options type is identical to [encoding/json.Options] and
// [encoding/json/v2.Options]. Options from the other packages may
// be passed to functionality in this package, but are ignored.
@@ -78,20 +97,122 @@ func EscapeForJS(v bool) Options {
}
}
// Expand specifies that the JSON output should be expanded,
// where every JSON object member or JSON array element
// appears on a new, indented line according to the nesting depth.
// If an indent is not already specified, then it defaults to using "\t".
//
// If set to false, then the output is compact,
// where no whitespace is emitted between JSON values.
// PreserveRawStrings specifies that when encoding a raw JSON string in a
// [Token] or [Value], pre-escaped sequences
// in a JSON string are preserved to the output.
// However, raw strings still respect [EscapeForHTML] and [EscapeForJS]
// such that the relevant characters are escaped.
// If [AllowInvalidUTF8] is enabled, bytes of invalid UTF-8
// are preserved to the output.
//
// This only affects encoding and is ignored when decoding.
func Expand(v bool) Options {
func PreserveRawStrings(v bool) Options {
if v {
return jsonflags.Expand | 1
return jsonflags.PreserveRawStrings | 1
} else {
return jsonflags.Expand | 0
return jsonflags.PreserveRawStrings | 0
}
}
// CanonicalizeRawInts specifies that when encoding a raw JSON
// integer number (i.e., a number without a fraction and exponent) in a
// [Token] or [Value], the number is canonicalized
// according to RFC 8785, section 3.2.2.3. As a special case,
// the number -0 is canonicalized as 0.
//
// JSON numbers are treated as IEEE 754 double precision numbers.
// Any numbers with precision beyond what is representable by that form
// will lose their precision when canonicalized. For example,
// integer values beyond ±2⁵³ will lose their precision.
// For example, 1234567890123456789 is formatted as 1234567890123456800.
//
// This only affects encoding and is ignored when decoding.
func CanonicalizeRawInts(v bool) Options {
if v {
return jsonflags.CanonicalizeRawInts | 1
} else {
return jsonflags.CanonicalizeRawInts | 0
}
}
// CanonicalizeRawFloats specifies that when encoding a raw JSON
// floating-point number (i.e., a number with a fraction or exponent) in a
// [Token] or [Value], the number is canonicalized
// according to RFC 8785, section 3.2.2.3. As a special case,
// the number -0 is canonicalized as 0.
//
// JSON numbers are treated as IEEE 754 double precision numbers.
// It is safe to canonicalize a serialized single precision number and
// parse it back as a single precision number and expect the same value.
// If a number exceeds ±1.7976931348623157e+308, which is the maximum
// finite number, then it saturated at that value and formatted as such.
//
// This only affects encoding and is ignored when decoding.
func CanonicalizeRawFloats(v bool) Options {
if v {
return jsonflags.CanonicalizeRawFloats | 1
} else {
return jsonflags.CanonicalizeRawFloats | 0
}
}
// ReorderRawObjects specifies that when encoding a raw JSON object in a
// [Value], the object members are reordered according to
// RFC 8785, section 3.2.3.
//
// This only affects encoding and is ignored when decoding.
func ReorderRawObjects(v bool) Options {
if v {
return jsonflags.ReorderRawObjects | 1
} else {
return jsonflags.ReorderRawObjects | 0
}
}
// SpaceAfterColon specifies that the JSON output should emit a space character
// after each colon separator following a JSON object name.
// If false, then no space character appears after the colon separator.
//
// This only affects encoding and is ignored when decoding.
func SpaceAfterColon(v bool) Options {
if v {
return jsonflags.SpaceAfterColon | 1
} else {
return jsonflags.SpaceAfterColon | 0
}
}
// SpaceAfterComma specifies that the JSON output should emit a space character
// after each comma separator following a JSON object value or array element.
// If false, then no space character appears after the comma separator.
//
// This only affects encoding and is ignored when decoding.
func SpaceAfterComma(v bool) Options {
if v {
return jsonflags.SpaceAfterComma | 1
} else {
return jsonflags.SpaceAfterComma | 0
}
}
// Multiline specifies that the JSON output should expand to multiple lines,
// where every JSON object member or JSON array element appears on
// a new, indented line according to the nesting depth.
//
// If [SpaceAfterColon] is not specified, then the default is true.
// If [SpaceAfterComma] is not specified, then the default is false.
// If [WithIndent] is not specified, then the default is "\t".
//
// If set to false, then the output is a single-line,
// where the only whitespace emitted is determined by the current
// values of [SpaceAfterColon] and [SpaceAfterComma].
//
// This only affects encoding and is ignored when decoding.
func Multiline(v bool) Options {
if v {
return jsonflags.Multiline | 1
} else {
return jsonflags.Multiline | 0
}
}
@@ -102,10 +223,10 @@ func Expand(v bool) Options {
// The indent must only be composed of space or tab characters.
//
// If the intent to emit indented output without a preference for
// the particular indent string, then use [Expand] instead.
// the particular indent string, then use [Multiline] instead.
//
// This only affects encoding and is ignored when decoding.
// Use of this option implies [Expand] being set to true.
// Use of this option implies [Multiline] being set to true.
func WithIndent(indent string) Options {
// Fast-path: Return a constant for common indents, which avoids allocating.
// These are derived from analyzing the Go module proxy on 2023-07-01.
@@ -138,7 +259,7 @@ func WithIndent(indent string) Options {
// The prefix must only be composed of space or tab characters.
//
// This only affects encoding and is ignored when decoding.
// Use of this option implies [Expand] being set to true.
// Use of this option implies [Multiline] being set to true.
func WithIndentPrefix(prefix string) Options {
if s := strings.Trim(prefix, " \t"); len(s) > 0 {
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent prefix")
@@ -160,6 +281,7 @@ func WithIndentPrefix(prefix string) Options {
//
// A non-positive limit is equivalent to no limit at all.
// If unspecified, the default limit is no limit at all.
// This affects either encoding or decoding.
func WithByteLimit(n int64) Options {
return jsonopts.ByteLimit(max(n, 0))
}
@@ -172,6 +294,7 @@ func WithByteLimit(n int64) Options {
//
// A non-positive limit is equivalent to no limit at all.
// If unspecified, the default limit is 10000.
// This affects either encoding or decoding.
func WithDepthLimit(n int) Options {
return jsonopts.DepthLimit(max(n, 0))
}

View File

@@ -9,15 +9,18 @@ import (
"github.com/go-json-experiment/json/internal/jsonwire"
)
var errInvalidUTF8 = &SyntacticError{str: "invalid UTF-8 within string"}
// AppendQuote appends a double-quoted JSON string literal representing src
// to dst and returns the extended buffer.
// It uses the minimal string representation per RFC 8785, section 3.2.2.2.
// Invalid UTF-8 bytes are replaced with the Unicode replacement character
// and an error is returned at the end indicating the presence of invalid UTF-8.
// The dst must not overlap with the src.
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
return jsonwire.AppendQuote(dst, src, &jsonflags.Flags{})
dst, err := jsonwire.AppendQuote(dst, src, &jsonflags.Flags{})
if err != nil {
err = &SyntacticError{Err: err}
}
return dst, err
}
// AppendUnquote appends the decoded interpretation of src as a
@@ -26,6 +29,11 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)
// Invalid UTF-8 bytes are replaced with the Unicode replacement character
// and an error is returned at the end indicating the presence of invalid UTF-8.
// Any trailing bytes after the JSON string literal results in an error.
// The dst must not overlap with the src.
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
return jsonwire.AppendUnquote(dst, src)
dst, err := jsonwire.AppendUnquote(dst, src)
if err != nil {
err = &SyntacticError{Err: err}
}
return dst, err
}

View File

@@ -5,21 +5,45 @@
package jsontext
import (
"errors"
"iter"
"math"
"strconv"
"strings"
"unicode/utf8"
"github.com/go-json-experiment/json/internal/jsonwire"
)
var (
errMissingName = &SyntacticError{str: "missing string for object name"}
errMissingColon = &SyntacticError{str: "missing character ':' after object name"}
errMissingValue = &SyntacticError{str: "missing value after object name"}
errMissingComma = &SyntacticError{str: "missing character ',' after object or array value"}
errMismatchDelim = &SyntacticError{str: "mismatching structural token for object or array"}
errMaxDepth = &SyntacticError{str: "exceeded max depth"}
// ErrDuplicateName indicates that a JSON token could not be
// encoded or decoded because it results in a duplicate JSON object name.
// This error is directly wrapped within a [SyntacticError] when produced.
//
// The name of a duplicate JSON object member can be extracted as:
//
// err := ...
// var serr jsontext.SyntacticError
// if errors.As(err, &serr) && serr.Err == jsontext.ErrDuplicateName {
// ptr := serr.JSONPointer // JSON pointer to duplicate name
// name := ptr.LastToken() // duplicate name itself
// ...
// }
//
// This error is only returned if [AllowDuplicateNames] is false.
var ErrDuplicateName = errors.New("duplicate object member name")
errInvalidNamespace = &SyntacticError{str: "object namespace is in an invalid state"}
// ErrNonStringName indicates that a JSON token could not be
// encoded or decoded because it is not a string,
// as required for JSON object names according to RFC 8259, section 4.
// This error is directly wrapped within a [SyntacticError] when produced.
var ErrNonStringName = errors.New("object member name must be a string")
var (
errMissingValue = errors.New("missing value after object name")
errMismatchDelim = errors.New("mismatching structural token for object or array")
errMaxDepth = errors.New("exceeded max depth")
errInvalidNamespace = errors.New("object namespace is in an invalid state")
)
// Per RFC 8259, section 9, implementations may enforce a maximum depth.
@@ -31,7 +55,6 @@ type state struct {
Tokens stateMachine
// Names is a stack of object names.
// Not used if AllowDuplicateNames is true.
Names objectNameStack
// Namespaces is a stack of object namespaces.
@@ -42,48 +65,151 @@ type state struct {
Namespaces objectNamespaceStack
}
// needObjectValue reports whether the next token should be an object value.
// This method is used by [wrapSyntacticError].
func (s *state) needObjectValue() bool {
return s.Tokens.Last.needObjectValue()
}
func (s *state) reset() {
s.Tokens.reset()
s.Names.reset()
s.Namespaces.reset()
}
// Pointer is a JSON Pointer (RFC 6901) that references a particular JSON value
// relative to the root of the top-level JSON value.
//
// A Pointer is a slash-separated list of tokens, where each token is
// either a JSON object name or an index to a JSON array element
// encoded as a base-10 integer value.
// It is impossible to distinguish between an array index and an object name
// (that happens to be an base-10 encoded integer) without also knowing
// the structure of the top-level JSON value that the pointer refers to.
//
// There is exactly one representation of a pointer to a particular value,
// so comparability of Pointer values is equivalent to checking whether
// they both point to the exact same value.
type Pointer string
// IsValid reports whether p is a valid JSON Pointer according to RFC 6901.
// Note that the concatenation of two valid pointers produces a valid pointer.
func (p Pointer) IsValid() bool {
for i, r := range p {
switch {
case r == '~' && (i+1 == len(p) || (p[i+1] != '0' && p[i+1] != '1')):
return false // invalid escape
case r == '\ufffd' && !strings.HasPrefix(string(p[i:]), "\ufffd"):
return false // invalid UTF-8
}
}
return len(p) == 0 || p[0] == '/'
}
// Contains reports whether the JSON value that p points to
// is equal to or contains the JSON value that pc points to.
func (p Pointer) Contains(pc Pointer) bool {
// Invariant: len(p) <= len(pc) if p.Contains(pc)
suffix, ok := strings.CutPrefix(string(pc), string(p))
return ok && (suffix == "" || suffix[0] == '/')
}
// Parent strips off the last token and returns the remaining pointer.
// The parent of an empty p is an empty string.
func (p Pointer) Parent() Pointer {
return p[:max(strings.LastIndexByte(string(p), '/'), 0)]
}
// LastToken returns the last token in the pointer.
// The last token of an empty p is an empty string.
func (p Pointer) LastToken() string {
last := p[max(strings.LastIndexByte(string(p), '/'), 0):]
return unescapePointerToken(strings.TrimPrefix(string(last), "/"))
}
// AppendToken appends a token to the end of p and returns the full pointer.
func (p Pointer) AppendToken(tok string) Pointer {
return Pointer(appendEscapePointerName([]byte(p+"/"), tok))
}
// TODO: Add Pointer.AppendTokens,
// but should this take in a ...string or an iter.Seq[string]?
// Tokens returns an iterator over the reference tokens in the JSON pointer,
// starting from the first token until the last token (unless stopped early).
func (p Pointer) Tokens() iter.Seq[string] {
return func(yield func(string) bool) {
for len(p) > 0 {
p = Pointer(strings.TrimPrefix(string(p), "/"))
i := min(uint(strings.IndexByte(string(p), '/')), uint(len(p)))
if !yield(unescapePointerToken(string(p)[:i])) {
return
}
p = p[i:]
}
}
}
func unescapePointerToken(token string) string {
if strings.Contains(token, "~") {
// Per RFC 6901, section 3, unescape '~' and '/' characters.
token = strings.ReplaceAll(token, "~1", "/")
token = strings.ReplaceAll(token, "~0", "~")
}
return token
}
// appendStackPointer appends a JSON Pointer (RFC 6901) to the current value.
// The returned pointer is only accurate if s.names is populated,
// otherwise it uses the numeric index as the object member name.
//
// - If where is -1, then it points to the previously processed token.
//
// - If where is 0, then it points to the parent JSON object or array,
// or an object member if in-between an object member key and value.
// This is useful when the position is ambiguous whether
// we are interested in the previous or next token, or
// when we are uncertain whether the next token
// continues or terminates the current object or array.
//
// - If where is +1, then it points to the next expected value,
// assuming that it continues the current JSON object or array.
// As a special case, if the next token is a JSON object name,
// then it points to the parent JSON object.
//
// Invariant: Must call s.names.copyQuotedBuffer beforehand.
func (s state) appendStackPointer(b []byte) []byte {
func (s state) appendStackPointer(b []byte, where int) []byte {
var objectDepth int
for i := 1; i < s.Tokens.Depth(); i++ {
e := s.Tokens.index(i)
if e.Length() == 0 {
break // empty object or array
arrayDelta := -1 // by default point to previous array element
if isLast := i == s.Tokens.Depth()-1; isLast {
switch {
case where < 0 && e.Length() == 0 || where == 0 && !e.needObjectValue() || where > 0 && e.NeedObjectName():
return b
case where > 0 && e.isArray():
arrayDelta = 0 // point to next array element
}
}
b = append(b, '/')
switch {
case e.isObject():
if objectDepth < s.Names.length() {
for _, c := range s.Names.getUnquoted(objectDepth) {
// Per RFC 6901, section 3, escape '~' and '/' characters.
switch c {
case '~':
b = append(b, "~0"...)
case '/':
b = append(b, "~1"...)
default:
b = append(b, c)
}
}
} else {
// Since the names stack is unpopulated, the name is unknown.
// As a best-effort replacement, use the numeric member index.
// While inaccurate, it produces a syntactically valid pointer.
b = strconv.AppendUint(b, uint64((e.Length()-1)/2), 10)
}
b = appendEscapePointerName(append(b, '/'), s.Names.getUnquoted(objectDepth))
objectDepth++
case e.isArray():
b = strconv.AppendUint(b, uint64(e.Length()-1), 10)
b = strconv.AppendUint(append(b, '/'), uint64(e.Length()+int64(arrayDelta)), 10)
}
}
return b
}
func appendEscapePointerName[Bytes ~[]byte | ~string](b []byte, name Bytes) []byte {
for _, r := range string(name) {
// Per RFC 6901, section 3, escape '~' and '/' characters.
switch r {
case '~':
b = append(b, "~0"...)
case '/':
b = append(b, "~1"...)
default:
b = utf8.AppendRune(b, r)
}
}
return b
@@ -133,7 +259,7 @@ func (m *stateMachine) index(i int) *stateEntry {
// DepthLength reports the current nested depth and
// the length of the last JSON object or array.
func (m stateMachine) DepthLength() (int, int) {
func (m stateMachine) DepthLength() (int, int64) {
return m.Depth(), m.Last.Length()
}
@@ -142,7 +268,7 @@ func (m stateMachine) DepthLength() (int, int) {
func (m *stateMachine) appendLiteral() error {
switch {
case m.Last.NeedObjectName():
return errMissingName
return ErrNonStringName
case !m.Last.isValidNamespace():
return errInvalidNamespace
default:
@@ -174,7 +300,7 @@ func (m *stateMachine) appendNumber() error {
func (m *stateMachine) pushObject() error {
switch {
case m.Last.NeedObjectName():
return errMissingName
return ErrNonStringName
case !m.Last.isValidNamespace():
return errInvalidNamespace
case len(m.Stack) == maxNestingDepth:
@@ -209,7 +335,7 @@ func (m *stateMachine) popObject() error {
func (m *stateMachine) pushArray() error {
switch {
case m.Last.NeedObjectName():
return errMissingName
return ErrNonStringName
case !m.Last.isValidNamespace():
return errInvalidNamespace
case len(m.Stack) == maxNestingDepth:
@@ -283,21 +409,6 @@ func (m stateMachine) needDelim(next Kind) (delim byte) {
}
}
// checkDelim reports whether the specified delimiter should be there given
// the kind of the next token that appears immediately afterwards.
func (m stateMachine) checkDelim(delim byte, next Kind) error {
switch needDelim := m.needDelim(next); {
case needDelim == delim:
return nil
case needDelim == ':':
return errMissingColon
case needDelim == ',':
return errMissingComma
default:
return newInvalidCharacterError([]byte{delim}, "before next token")
}
}
// InvalidateDisabledNamespaces marks all disabled namespaces as invalid.
//
// For efficiency, Marshal and Unmarshal may disable namespaces since there are
@@ -306,7 +417,7 @@ func (m stateMachine) checkDelim(delim byte, next Kind) error {
// Mark the namespaces as invalid so that future method calls on
// Encoder or Decoder will return an error.
func (m *stateMachine) InvalidateDisabledNamespaces() {
for i := 0; i < m.Depth(); i++ {
for i := range m.Depth() {
e := m.index(i)
if !e.isActiveNamespace() {
e.invalidateNamespace()
@@ -342,8 +453,8 @@ const (
// Length reports the number of elements in the JSON object or array.
// Each name and value in an object entry is treated as a separate element.
func (e stateEntry) Length() int {
return int(e & stateCountMask)
func (e stateEntry) Length() int64 {
return int64(e & stateCountMask)
}
// isObject reports whether this is a JSON object.
@@ -453,7 +564,7 @@ func (ns *objectNameStack) length() int {
return len(ns.offsets)
}
// getUnquoted retrieves the ith unquoted name in the namespace.
// getUnquoted retrieves the ith unquoted name in the stack.
// It returns an empty string if the last object is empty.
//
// Invariant: Must call copyQuotedBuffer beforehand.

View File

@@ -5,6 +5,8 @@
package jsontext
import (
"bytes"
"errors"
"math"
"strconv"
@@ -20,9 +22,11 @@ const (
maxUint64 = math.MaxUint64
minUint64 = 0 // for consistency and readability purposes
invalidTokenPanic = "invalid json.Token; it has been voided by a subsequent json.Decoder call"
invalidTokenPanic = "invalid jsontext.Token; it has been voided by a subsequent json.Decoder call"
)
var errInvalidToken = errors.New("invalid jsontext.Token")
// Token represents a lexical JSON token, which may be one of the following:
// - a JSON literal (i.e., null, true, or false)
// - a JSON string (e.g., "hello, world!")
@@ -90,10 +94,10 @@ var (
False Token = rawToken("false")
True Token = rawToken("true")
ObjectStart Token = rawToken("{")
ObjectEnd Token = rawToken("}")
ArrayStart Token = rawToken("[")
ArrayEnd Token = rawToken("]")
BeginObject Token = rawToken("{")
EndObject Token = rawToken("}")
BeginArray Token = rawToken("[")
EndArray Token = rawToken("]")
zeroString Token = rawToken(`""`)
zeroNumber Token = rawToken(`0`)
@@ -172,22 +176,21 @@ func (t Token) Clone() Token {
return False
case True.raw:
return True
case ObjectStart.raw:
return ObjectStart
case ObjectEnd.raw:
return ObjectEnd
case ArrayStart.raw:
return ArrayStart
case ArrayEnd.raw:
return ArrayEnd
case BeginObject.raw:
return BeginObject
case EndObject.raw:
return EndObject
case BeginArray.raw:
return BeginArray
case EndArray.raw:
return EndArray
}
}
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
// TODO(https://go.dev/issue/45038): Use bytes.Clone.
buf := append([]byte(nil), raw.PreviousBuffer()...)
buf := bytes.Clone(raw.previousBuffer())
return Token{raw: &decodeBuffer{buf: buf, prevStart: 0, prevEnd: len(buf)}}
}
return t
@@ -211,7 +214,7 @@ func (t Token) Bool() bool {
func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error) {
if raw := t.raw; raw != nil {
// Handle raw string value.
buf := raw.PreviousBuffer()
buf := raw.previousBuffer()
if Kind(buf[0]) == '"' {
if jsonwire.ConsumeSimpleString(buf) == len(buf) {
return append(dst, buf...), nil
@@ -245,7 +248,7 @@ func (t Token) string() (string, []byte) {
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
buf := raw.PreviousBuffer()
buf := raw.previousBuffer()
if buf[0] == '"' {
// TODO: Preserve ValueFlags in Token?
isVerbatim := jsonwire.ConsumeSimpleString(buf) == len(buf)
@@ -268,20 +271,17 @@ func (t Token) string() (string, []byte) {
return strconv.FormatUint(uint64(t.num), 10), nil
}
}
return "<invalid json.Token>", nil
return "<invalid jsontext.Token>", nil
}
// appendNumber appends a JSON number to dst and returns it.
// It panics if t is not a JSON number.
func (t Token) appendNumber(dst []byte, canonicalize bool) ([]byte, error) {
func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error) {
if raw := t.raw; raw != nil {
// Handle raw number value.
buf := raw.PreviousBuffer()
buf := raw.previousBuffer()
if Kind(buf[0]).normalize() == '0' {
if !canonicalize {
return append(dst, buf...), nil
}
dst, _, err := jsonwire.ReformatNumber(dst, buf, canonicalize)
dst, _, err := jsonwire.ReformatNumber(dst, buf, flags)
return dst, err
}
} else if t.num != 0 {
@@ -309,7 +309,7 @@ func (t Token) Float() float64 {
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
buf := raw.PreviousBuffer()
buf := raw.previousBuffer()
if Kind(buf[0]).normalize() == '0' {
fv, _ := jsonwire.ParseFloat(buf, 64)
return fv
@@ -353,7 +353,7 @@ func (t Token) Int() int64 {
panic(invalidTokenPanic)
}
neg := false
buf := raw.PreviousBuffer()
buf := raw.previousBuffer()
if len(buf) > 0 && buf[0] == '-' {
neg, buf = true, buf[1:]
}
@@ -414,7 +414,7 @@ func (t Token) Uint() uint64 {
panic(invalidTokenPanic)
}
neg := false
buf := raw.PreviousBuffer()
buf := raw.previousBuffer()
if len(buf) > 0 && buf[0] == '-' {
neg, buf = true, buf[1:]
}
@@ -512,7 +512,7 @@ func (k Kind) String() string {
case ']':
return "]"
default:
return "<invalid json.Kind: " + jsonwire.QuoteRune(string(k)) + ">"
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune(string(k)) + ">"
}
}

View File

@@ -9,7 +9,6 @@ import (
"errors"
"io"
"slices"
"strings"
"sync"
"github.com/go-json-experiment/json/internal/jsonflags"
@@ -18,6 +17,22 @@ import (
// NOTE: Value is analogous to v1 json.RawMessage.
// AppendFormat formats the JSON value in src and appends it to dst
// according to the specified options.
// See [Value.Format] for more details about the formatting behavior.
//
// The dst and src may overlap.
// If an error is reported, then the entirety of src is appended to dst.
func AppendFormat(dst, src []byte, opts ...Options) ([]byte, error) {
e := getBufferedEncoder(opts...)
defer putBufferedEncoder(e)
e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
if err := e.s.WriteValue(src); err != nil {
return append(dst, src...), err
}
return append(dst, e.s.Buf...), nil
}
// Value represents a single raw JSON value, which may be one of the following:
// - a JSON literal (i.e., null, true, or false)
// - a JSON string (e.g., "hello, world!")
@@ -43,70 +58,159 @@ func (v Value) String() string {
}
// IsValid reports whether the raw JSON value is syntactically valid
// according to RFC 7493.
// according to the specified options.
//
// By default (if no options are specified), it validates according to RFC 7493.
// It verifies whether the input is properly encoded as UTF-8,
// that escape sequences within strings decode to valid Unicode codepoints, and
// that all names in each object are unique.
// It does not verify whether numbers are representable within the limits
// of any common numeric type (e.g., float64, int64, or uint64).
func (v Value) IsValid() bool {
d := getBufferedDecoder(v)
//
// Relevant options include:
// - [AllowDuplicateNames]
// - [AllowInvalidUTF8]
//
// All other options are ignored.
func (v Value) IsValid(opts ...Options) bool {
// TODO: Document support for [WithByteLimit] and [WithDepthLimit].
d := getBufferedDecoder(v, opts...)
defer putBufferedDecoder(d)
_, errVal := d.ReadValue()
_, errEOF := d.ReadToken()
return errVal == nil && errEOF == io.EOF
}
// Format formats the raw JSON value in place.
//
// By default (if no options are specified), it validates according to RFC 7493
// and produces the minimal JSON representation, where
// all whitespace is elided and JSON strings use the shortest encoding.
//
// Relevant options include:
// - [AllowDuplicateNames]
// - [AllowInvalidUTF8]
// - [EscapeForHTML]
// - [EscapeForJS]
// - [PreserveRawStrings]
// - [CanonicalizeRawInts]
// - [CanonicalizeRawFloats]
// - [ReorderRawObjects]
// - [SpaceAfterColon]
// - [SpaceAfterComma]
// - [Multiline]
// - [WithIndent]
// - [WithIndentPrefix]
//
// All other options are ignored.
//
// It is guaranteed to succeed if the value is valid according to the same options.
// If the value is already formatted, then the buffer is not mutated.
func (v *Value) Format(opts ...Options) error {
// TODO: Document support for [WithByteLimit] and [WithDepthLimit].
return v.format(opts, nil)
}
// format accepts two []Options to avoid the allocation appending them together.
// It is equivalent to v.Format(append(opts1, opts2...)...).
func (v *Value) format(opts1, opts2 []Options) error {
e := getBufferedEncoder(opts1...)
defer putBufferedEncoder(e)
e.s.Join(opts2...)
e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
if err := e.s.WriteValue(*v); err != nil {
return err
}
if !bytes.Equal(*v, e.s.Buf) {
*v = append((*v)[:0], e.s.Buf...)
}
return nil
}
// Compact removes all whitespace from the raw JSON value.
//
// It does not reformat JSON strings to use any other representation.
// It is guaranteed to succeed if the input is valid.
// If the value is already compacted, then the buffer is not mutated.
func (v *Value) Compact() error {
return v.reformat(false, false, "", "")
// It does not reformat JSON strings or numbers to use any other representation.
// To maximize the set of JSON values that can be formatted,
// this permits values with duplicate names and invalid UTF-8.
//
// Compact is equivalent to calling [Value.Format] with the following options:
// - [AllowDuplicateNames](true)
// - [AllowInvalidUTF8](true)
// - [PreserveRawStrings](true)
//
// Any options specified by the caller are applied after the initial set
// and may deliberately override prior options.
func (v *Value) Compact(opts ...Options) error {
return v.format([]Options{
AllowDuplicateNames(true),
AllowInvalidUTF8(true),
PreserveRawStrings(true),
}, opts)
}
// Indent reformats the whitespace in the raw JSON value so that each element
// in a JSON object or array begins on a new, indented line beginning with
// prefix followed by one or more copies of indent according to the nesting.
// The value does not begin with the prefix nor any indention,
// to make it easier to embed inside other formatted JSON data.
// in a JSON object or array begins on a indented line according to the nesting.
//
// It does not reformat JSON strings to use any other representation.
// It is guaranteed to succeed if the input is valid.
// If the value is already indented properly, then the buffer is not mutated.
// It does not reformat JSON strings or numbers to use any other representation.
// To maximize the set of JSON values that can be formatted,
// this permits values with duplicate names and invalid UTF-8.
//
// The prefix and indent strings must be composed of only spaces and/or tabs.
func (v *Value) Indent(prefix, indent string) error {
return v.reformat(false, true, prefix, indent)
// Indent is equivalent to calling [Value.Format] with the following options:
// - [AllowDuplicateNames](true)
// - [AllowInvalidUTF8](true)
// - [PreserveRawStrings](true)
// - [Multiline](true)
//
// Any options specified by the caller are applied after the initial set
// and may deliberately override prior options.
func (v *Value) Indent(opts ...Options) error {
return v.format([]Options{
AllowDuplicateNames(true),
AllowInvalidUTF8(true),
PreserveRawStrings(true),
Multiline(true),
}, opts)
}
// Canonicalize canonicalizes the raw JSON value according to the
// JSON Canonicalization Scheme (JCS) as defined by RFC 8785
// where it produces a stable representation of a JSON value.
//
// JSON strings are formatted to use their minimal representation,
// JSON numbers are formatted as double precision numbers according
// to some stable serialization algorithm.
// JSON object members are sorted in ascending order by name.
// All whitespace is removed.
//
// The output stability is dependent on the stability of the application data
// (see RFC 8785, Appendix E). It cannot produce stable output from
// fundamentally unstable input. For example, if the JSON value
// contains ephemeral data (e.g., a frequently changing timestamp),
// then the value is still unstable regardless of whether this is called.
//
// Canonicalize is equivalent to calling [Value.Format] with the following options:
// - [CanonicalizeRawInts](true)
// - [CanonicalizeRawFloats](true)
// - [ReorderRawObjects](true)
//
// Any options specified by the caller are applied after the initial set
// and may deliberately override prior options.
//
// Note that JCS treats all JSON numbers as IEEE 754 double precision numbers.
// Any numbers with precision beyond what is representable by that form
// will lose their precision when canonicalized. For example, integer values
// beyond ±2⁵³ will lose their precision. It is recommended that
// int64 and uint64 data types be represented as a JSON string.
// beyond ±2⁵³ will lose their precision. To preserve the original representation
// of JSON integers, additionally set [CanonicalizeRawInts] to false:
//
// It is guaranteed to succeed if the input is valid.
// If the value is already canonicalized, then the buffer is not mutated.
func (v *Value) Canonicalize() error {
return v.reformat(true, false, "", "")
// v.Canonicalize(jsontext.CanonicalizeRawInts(false))
func (v *Value) Canonicalize(opts ...Options) error {
return v.format([]Options{
CanonicalizeRawInts(true),
CanonicalizeRawFloats(true),
ReorderRawObjects(true),
}, opts)
}
// TODO: Instead of implementing the v1 Marshaler/Unmarshaler,
// consider implementing the v2 versions instead.
// MarshalJSON returns v as the JSON encoding of v.
// It returns the stored value as the raw JSON output without any validation.
// If v is nil, then this returns a JSON null.
@@ -123,7 +227,7 @@ func (v Value) MarshalJSON() ([]byte, error) {
func (v *Value) UnmarshalJSON(b []byte) error {
// NOTE: This matches the behavior of v1 json.RawMessage.UnmarshalJSON.
if v == nil {
return errors.New("json.Value: UnmarshalJSON on nil pointer")
return errors.New("jsontext.Value: UnmarshalJSON on nil pointer")
}
*v = append((*v)[:0], b...)
return nil
@@ -138,93 +242,62 @@ func (v Value) Kind() Kind {
return invalidKind
}
func (v *Value) reformat(canonical, multiline bool, prefix, indent string) error {
// Write the entire value to reformat all tokens and whitespace.
e := getBufferedEncoder()
defer putBufferedEncoder(e)
eo := &e.s.Struct
if canonical {
eo.Flags.Set(jsonflags.AllowInvalidUTF8 | 0) // per RFC 8785, section 3.2.4
eo.Flags.Set(jsonflags.AllowDuplicateNames | 0) // per RFC 8785, section 3.1
eo.Flags.Set(jsonflags.CanonicalizeNumbers | 1) // per RFC 8785, section 3.2.2.3
eo.Flags.Set(jsonflags.PreserveRawStrings | 0) // per RFC 8785, section 3.2.2.2
eo.Flags.Set(jsonflags.EscapeForHTML | 0) // per RFC 8785, section 3.2.2.2
eo.Flags.Set(jsonflags.EscapeForJS | 0) // per RFC 8785, section 3.2.2.2
eo.Flags.Set(jsonflags.Expand | 0) // per RFC 8785, section 3.2.1
} else {
if s := strings.TrimLeft(prefix, " \t"); len(s) > 0 {
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent prefix")
}
if s := strings.TrimLeft(indent, " \t"); len(s) > 0 {
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent")
}
eo.Flags.Set(jsonflags.AllowInvalidUTF8 | 1)
eo.Flags.Set(jsonflags.AllowDuplicateNames | 1)
eo.Flags.Set(jsonflags.PreserveRawStrings | 1)
if multiline {
eo.Flags.Set(jsonflags.Expand | 1)
eo.Flags.Set(jsonflags.Indent | 1)
eo.Flags.Set(jsonflags.IndentPrefix | 1)
eo.IndentPrefix = prefix
eo.Indent = indent
} else {
eo.Flags.Set(jsonflags.Expand | 0)
}
}
eo.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
if err := e.s.WriteValue(*v); err != nil {
return err
}
const commaAndWhitespace = ", \n\r\t"
// For canonical output, we may need to reorder object members.
if canonical {
// Obtain a buffered encoder just to use its internal buffer as
// a scratch buffer in reorderObjects for reordering object members.
e2 := getBufferedEncoder()
defer putBufferedEncoder(e2)
// Disable redundant checks performed earlier during encoding.
d := getBufferedDecoder(e.s.Buf)
defer putBufferedDecoder(d)
d.s.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1)
reorderObjects(d, &e2.s.Buf) // per RFC 8785, section 3.2.3
}
// Store the result back into the value if different.
if !bytes.Equal(*v, e.s.Buf) {
*v = append((*v)[:0], e.s.Buf...)
}
return nil
type objectMember struct {
// name is the unquoted name.
name []byte // e.g., "name"
// buffer is the entirety of the raw JSON object member
// starting from right after the previous member (or opening '{')
// until right after the member value.
buffer []byte // e.g., `, \n\r\t"name": "value"`
}
type memberName struct {
// name is the unescaped name.
name []byte
// before and after are byte offsets into Decoder.buf that represents
// the entire name/value pair. It may contain leading commas.
before, after int64
func (x objectMember) Compare(y objectMember) int {
if c := jsonwire.CompareUTF16(x.name, y.name); c != 0 {
return c
}
// With [AllowDuplicateNames] or [AllowInvalidUTF8],
// names could be identical, so also sort using the member value.
return jsonwire.CompareUTF16(
bytes.TrimLeft(x.buffer, commaAndWhitespace),
bytes.TrimLeft(y.buffer, commaAndWhitespace))
}
var memberNamePool = sync.Pool{New: func() any { return new([]memberName) }}
var objectMemberPool = sync.Pool{New: func() any { return new([]objectMember) }}
func getMemberNames() *[]memberName {
ns := memberNamePool.Get().(*[]memberName)
func getObjectMembers() *[]objectMember {
ns := objectMemberPool.Get().(*[]objectMember)
*ns = (*ns)[:0]
return ns
}
func putMemberNames(ns *[]memberName) {
func putObjectMembers(ns *[]objectMember) {
if cap(*ns) < 1<<10 {
clear(*ns) // avoid pinning name
memberNamePool.Put(ns)
clear(*ns) // avoid pinning name and buffer
objectMemberPool.Put(ns)
}
}
// reorderObjects recursively reorders all object members in place
// mustReorderObjects reorders in-place all object members in a JSON value,
// which must be valid otherwise it panics.
func mustReorderObjects(b []byte) {
// Obtain a buffered encoder just to use its internal buffer as
// a scratch buffer for reordering object members.
e2 := getBufferedEncoder()
defer putBufferedEncoder(e2)
// Disable unnecessary checks to syntactically parse the JSON value.
d := getBufferedDecoder(b)
defer putBufferedDecoder(d)
d.s.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1)
mustReorderObjectsFromDecoder(d, &e2.s.Buf) // per RFC 8785, section 3.2.3
}
// mustReorderObjectsFromDecoder recursively reorders all object members in place
// according to the ordering specified in RFC 8785, section 3.2.3.
//
// Pre-conditions:
// - The value is valid (i.e., no decoder errors should ever occur).
// - The value is compact (i.e., no whitespace is present).
// - Initial call is provided a Decoder reading from the start of v.
//
// Post-conditions:
@@ -234,13 +307,13 @@ func putMemberNames(ns *[]memberName) {
//
// The runtime is approximately O(n·log(n)) + O(m·log(m)),
// where n is len(v) and m is the total number of object members.
func reorderObjects(d *Decoder, scratch *[]byte) {
switch tok, _ := d.ReadToken(); tok.Kind() {
func mustReorderObjectsFromDecoder(d *Decoder, scratch *[]byte) {
switch tok, err := d.ReadToken(); tok.Kind() {
case '{':
// Iterate and collect the name and offsets for every object member.
members := getMemberNames()
defer putMemberNames(members)
var prevName []byte
members := getObjectMembers()
defer putObjectMembers(members)
var prevMember objectMember
isSorted := true
beforeBody := d.InputOffset() // offset after '{'
@@ -249,14 +322,15 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
var flags jsonwire.ValueFlags
name, _ := d.s.ReadValue(&flags)
name = jsonwire.UnquoteMayCopy(name, flags.IsVerbatim())
reorderObjects(d, scratch)
mustReorderObjectsFromDecoder(d, scratch)
afterValue := d.InputOffset()
currMember := objectMember{name, d.s.buf[beforeName:afterValue]}
if isSorted && len(*members) > 0 {
isSorted = jsonwire.CompareUTF16(prevName, []byte(name)) < 0
isSorted = objectMember.Compare(prevMember, currMember) < 0
}
*members = append(*members, memberName{name, beforeName, afterValue})
prevName = name
*members = append(*members, currMember)
prevMember = currMember
}
afterBody := d.InputOffset() // offset before '}'
d.ReadToken()
@@ -265,9 +339,9 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
if isSorted {
return
}
slices.SortFunc(*members, func(x, y memberName) int {
return jsonwire.CompareUTF16(x.name, y.name)
})
firstBufferBeforeSorting := (*members)[0].buffer
slices.SortFunc(*members, objectMember.Compare)
firstBufferAfterSorting := (*members)[0].buffer
// Append the reordered members to a new buffer,
// then copy the reordered members back over the original members.
@@ -277,14 +351,24 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
//
// The following invariant must hold:
// sum([m.after-m.before for m in members]) == afterBody-beforeBody
commaAndWhitespacePrefix := func(b []byte) []byte {
return b[:len(b)-len(bytes.TrimLeft(b, commaAndWhitespace))]
}
sorted := (*scratch)[:0]
for i, member := range *members {
if d.s.buf[member.before] == ',' {
member.before++ // trim leading comma
}
sorted = append(sorted, d.s.buf[member.before:member.after]...)
if i < len(*members)-1 {
sorted = append(sorted, ',') // append trailing comma
switch {
case i == 0 && &member.buffer[0] != &firstBufferBeforeSorting[0]:
// First member after sorting is not the first member before sorting,
// so use the prefix of the first member before sorting.
sorted = append(sorted, commaAndWhitespacePrefix(firstBufferBeforeSorting)...)
sorted = append(sorted, bytes.TrimLeft(member.buffer, commaAndWhitespace)...)
case i != 0 && &member.buffer[0] == &firstBufferBeforeSorting[0]:
// Later member after sorting is the first member before sorting,
// so use the prefix of the first member after sorting.
sorted = append(sorted, commaAndWhitespacePrefix(firstBufferAfterSorting)...)
sorted = append(sorted, bytes.TrimLeft(member.buffer, commaAndWhitespace)...)
default:
sorted = append(sorted, member.buffer...)
}
}
if int(afterBody-beforeBody) != len(sorted) {
@@ -298,8 +382,12 @@ func reorderObjects(d *Decoder, scratch *[]byte) {
}
case '[':
for d.PeekKind() != ']' {
reorderObjects(d, scratch)
mustReorderObjectsFromDecoder(d, scratch)
}
d.ReadToken()
default:
if err != nil {
panic("BUG: " + err.Error())
}
}
}