Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+57
-38
@@ -36,7 +36,7 @@
|
||||
//
|
||||
// - a JSON literal, string, or number
|
||||
// - a JSON object (e.g., `{"name":"value"}`)
|
||||
// - a JSON array (e.g., `[1,2,3,]`)
|
||||
// - a JSON array (e.g., `[1,2,3]`)
|
||||
//
|
||||
// A JSON value is represented by the [Value] type in Go and is a []byte
|
||||
// containing the raw textual representation of the value. There is some overlap
|
||||
@@ -45,18 +45,18 @@
|
||||
//
|
||||
// The [Encoder] and [Decoder] types contain methods to read or write the next
|
||||
// [Token] or [Value] in a sequence. They maintain a state machine to validate
|
||||
// whether the sequence of JSON tokens and/or values produces a valid JSON.
|
||||
// whether the sequence of JSON tokens and/or values produces valid JSON.
|
||||
// [Options] may be passed to the [NewEncoder] or [NewDecoder] constructors
|
||||
// to configure the syntactic behavior of encoding and decoding.
|
||||
// to configure the behavior of encoding and decoding.
|
||||
//
|
||||
// # Terminology
|
||||
//
|
||||
// The terms "encode" and "decode" are used for syntactic functionality
|
||||
// that is concerned with processing JSON based on its grammar, and
|
||||
// the terms "marshal" and "unmarshal" are used for semantic functionality
|
||||
// that determines the meaning of JSON values as Go values and vice-versa.
|
||||
// This package (i.e., [jsontext]) deals with JSON at a syntactic layer,
|
||||
// while [encoding/json/v2] deals with JSON at a semantic layer.
|
||||
// that determines the meaning of JSON values as Go values and vice versa.
|
||||
// This package deals with JSON syntax,
|
||||
// while [encoding/json/v2] deals with JSON semantics.
|
||||
// The goal is to provide a clear distinction between functionality that
|
||||
// is purely concerned with encoding versus that of marshaling.
|
||||
// For example, one can directly encode a stream of JSON tokens without
|
||||
@@ -136,7 +136,7 @@ import (
|
||||
// d.ReadValue() // {"k":"v"}
|
||||
// d.ReadToken() // }
|
||||
//
|
||||
// The above is one of many possible sequence of calls and
|
||||
// The above is one of many possible sequences of calls and
|
||||
// may not represent the most sensible method to call for any given token/value.
|
||||
// For example, it is probably more common to call [Decoder.ReadToken] to obtain a
|
||||
// string token for object names.
|
||||
@@ -151,7 +151,7 @@ func NewDecoder(r io.Reader, opts ...Options) *Decoder {
|
||||
return jsontext.NewDecoder(r, opts...)
|
||||
}
|
||||
|
||||
// Encoder is a streaming encoder from raw JSON tokens and values.
|
||||
// Encoder is a streaming encoder to raw JSON tokens and values.
|
||||
// It is used to write a stream of top-level JSON values,
|
||||
// each terminated with a newline character.
|
||||
//
|
||||
@@ -176,7 +176,7 @@ func NewDecoder(r io.Reader, opts ...Options) *Decoder {
|
||||
// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}
|
||||
// e.WriteToken(EndObject) // }
|
||||
//
|
||||
// The above is one of many possible sequence of calls and
|
||||
// The above is one of many possible sequences of calls and
|
||||
// may not represent the most sensible method to call for any given token/value.
|
||||
// For example, it is probably more common to call [Encoder.WriteToken] with a string
|
||||
// for object names.
|
||||
@@ -193,7 +193,7 @@ func NewEncoder(w io.Writer, opts ...Options) *Encoder {
|
||||
return jsontext.NewEncoder(w, opts...)
|
||||
}
|
||||
|
||||
// SyntacticError is a description of a syntactic error that occurred when
|
||||
// SyntacticError is a description of an error that occurred when
|
||||
// encoding or decoding JSON according to the grammar.
|
||||
//
|
||||
// The contents of this error as produced by this package may change over time.
|
||||
@@ -202,7 +202,7 @@ type SyntacticError = jsontext.SyntacticError
|
||||
// Options configures [NewEncoder], [Encoder.Reset], [NewDecoder],
|
||||
// and [Decoder.Reset] with specific features.
|
||||
// Each function takes in a variadic list of options, where properties
|
||||
// set in latter options override the value of previously set properties.
|
||||
// set in later 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:
|
||||
@@ -308,7 +308,7 @@ func CanonicalizeRawInts(v bool) Options {
|
||||
// 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.
|
||||
// finite number, then it is saturated at that value and formatted as such.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func CanonicalizeRawFloats(v bool) Options {
|
||||
@@ -350,7 +350,7 @@ func SpaceAfterComma(v bool) Options {
|
||||
// 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,
|
||||
// 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].
|
||||
//
|
||||
@@ -363,9 +363,9 @@ func Multiline(v bool) Options {
|
||||
// where each element in a JSON object or array begins on a new, indented line
|
||||
// beginning with the indent prefix (see [WithIndentPrefix])
|
||||
// followed by one or more copies of indent according to the nesting depth.
|
||||
// The indent must only be composed of space or tab characters.
|
||||
// The indent must be composed of only space and tab characters.
|
||||
//
|
||||
// If the intent to emit indented output without a preference for
|
||||
// If the intent is to emit indented output without a preference for
|
||||
// the particular indent string, then use [Multiline] instead.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
@@ -378,7 +378,7 @@ func WithIndent(indent string) Options {
|
||||
// where each element in a JSON object or array begins on a new, indented line
|
||||
// beginning with the indent prefix followed by one or more copies of indent
|
||||
// (see [WithIndent]) according to the nesting depth.
|
||||
// The prefix must only be composed of space or tab characters.
|
||||
// The prefix must be composed of only space and tab characters.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
// Use of this option implies [Multiline] being set to true.
|
||||
@@ -414,8 +414,8 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, erro
|
||||
// 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 {
|
||||
// serr, ok := errors.AsType[*jsontext.SyntacticError](err)
|
||||
// if ok && serr.Err == jsontext.ErrDuplicateName {
|
||||
// ptr := serr.JSONPointer // JSON pointer to duplicate name
|
||||
// name := ptr.LastToken() // duplicate name itself
|
||||
// ...
|
||||
@@ -437,12 +437,12 @@ var ErrNonStringName = jsontext.ErrNonStringName
|
||||
// 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
|
||||
// (that happens to be a 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.
|
||||
// they both point to the same value.
|
||||
type Pointer = jsontext.Pointer
|
||||
|
||||
// Token represents a lexical JSON token, which may be one of the following:
|
||||
@@ -455,6 +455,20 @@ type Pointer = jsontext.Pointer
|
||||
// A Token cannot represent entire array or object values, while a [Value] can.
|
||||
// There is no Token to represent commas and colons since
|
||||
// these structural tokens can be inferred from the surrounding context.
|
||||
//
|
||||
// A Token stores data in one of two forms:
|
||||
//
|
||||
// - As raw JSON text: backed by the internal buffer of the [Decoder]
|
||||
// and only ever produced by [Decoder.ReadToken].
|
||||
// Such a token is only valid until the next call to any method on that
|
||||
// [Decoder] (e.g., [Decoder.PeekKind], [Decoder.ReadToken],
|
||||
// [Decoder.ReadValue], or [Decoder.SkipValue]).
|
||||
// Call [Token.Clone] to copy the raw text into an independent allocation
|
||||
// that persists beyond subsequent [Decoder] calls.
|
||||
//
|
||||
// - As a typed Go value: a self-contained representation produced by
|
||||
// the constructor functions (e.g., [String], [Int], [Uint], [Float]).
|
||||
// Such tokens are valid indefinitely and do not need to be cloned.
|
||||
type Token = jsontext.Token
|
||||
|
||||
var (
|
||||
@@ -479,7 +493,10 @@ func String(s string) Token {
|
||||
return jsontext.String(s)
|
||||
}
|
||||
|
||||
// Float constructs a Token representing a JSON number.
|
||||
// Float constructs a Token representing a JSON number as
|
||||
// a 64-bit floating-point number formatted according to
|
||||
// ECMA-262, 10th edition, section 7.1.12.1 and RFC 8785, section 3.2.2.3.
|
||||
// with the exception that -0 is still formatted as -0.
|
||||
// The values NaN, +Inf, and -Inf will be represented
|
||||
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
|
||||
func Float(n float64) Token {
|
||||
@@ -496,32 +513,34 @@ func Uint(n uint64) Token {
|
||||
return jsontext.Uint(n)
|
||||
}
|
||||
|
||||
// Kind represents each possible JSON token kind with a single byte,
|
||||
// which is conveniently the first byte of that kind's grammar
|
||||
// with the restriction that numbers always be represented with '0':
|
||||
// A Kind represents the kind of a JSON token.
|
||||
//
|
||||
// - 'n': null
|
||||
// - 'f': false
|
||||
// - 't': true
|
||||
// - '"': string
|
||||
// - '0': number
|
||||
// - '{': object begin
|
||||
// - '}': object end
|
||||
// - '[': array begin
|
||||
// - ']': array end
|
||||
//
|
||||
// An invalid kind is usually represented using 0,
|
||||
// but may be non-zero due to invalid JSON data.
|
||||
// A Kind is a single byte, which is conveniently the first byte of that
|
||||
// kind's symbol in the grammar (except for numbers, which are always represented
|
||||
// with '0').
|
||||
type Kind = jsontext.Kind
|
||||
|
||||
const (
|
||||
KindInvalid = jsontext.KindInvalid
|
||||
KindNull = jsontext.KindNull
|
||||
KindFalse = jsontext.KindFalse
|
||||
KindTrue = jsontext.KindTrue
|
||||
KindString = jsontext.KindString
|
||||
KindNumber = jsontext.KindNumber
|
||||
KindBeginObject = jsontext.KindBeginObject
|
||||
KindEndObject = jsontext.KindEndObject
|
||||
KindBeginArray = jsontext.KindBeginArray
|
||||
KindEndArray = jsontext.KindEndArray
|
||||
)
|
||||
|
||||
// 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) {
|
||||
return jsontext.AppendFormat(dst, src, opts...)
|
||||
func AppendFormat[Bytes ~[]byte | ~string](dst []byte, src Bytes, opts ...Options) ([]byte, error) {
|
||||
return jsontext.AppendFormat(dst, []byte(src), opts...)
|
||||
}
|
||||
|
||||
// Value represents a single raw JSON value, which may be one of the following:
|
||||
|
||||
+59
-18
@@ -34,11 +34,11 @@ import (
|
||||
// return the end of the consumed JSON token as a position,
|
||||
// also relative to the start of Decoder.buf.
|
||||
//
|
||||
// - In the event of an I/O errors or state machine violations,
|
||||
// - In the event of an I/O error or state machine violations,
|
||||
// the implementation avoids mutating the state of Decoder
|
||||
// (aside from the book-keeping needed to implement Decoder.fetch).
|
||||
// For this reason, only Decoder.ReadToken and Decoder.ReadValue are
|
||||
// responsible for updated Decoder.prevStart and Decoder.prevEnd.
|
||||
// responsible for updating Decoder.prevStart and Decoder.prevEnd.
|
||||
//
|
||||
// - For performance, much of the implementation uses the pattern of calling
|
||||
// the inlinable consumeXXX functions first, and if more work is necessary,
|
||||
@@ -71,7 +71,7 @@ import (
|
||||
// d.ReadValue() // {"k":"v"}
|
||||
// d.ReadToken() // }
|
||||
//
|
||||
// The above is one of many possible sequence of calls and
|
||||
// The above is one of many possible sequences of calls and
|
||||
// may not represent the most sensible method to call for any given token/value.
|
||||
// For example, it is probably more common to call [Decoder.ReadToken] to obtain a
|
||||
// string token for object names.
|
||||
@@ -80,7 +80,7 @@ type Decoder struct {
|
||||
}
|
||||
|
||||
// decoderState is the low-level state of Decoder.
|
||||
// It has exported fields and method for use by the "json" package.
|
||||
// It has exported fields and methods for use by the "json" package.
|
||||
type decoderState struct {
|
||||
state
|
||||
decodeBuffer
|
||||
@@ -126,7 +126,7 @@ 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
|
||||
// configured with the provided options. Reset must not be called on
|
||||
// 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) {
|
||||
@@ -154,7 +154,7 @@ func (d *decoderState) reset(b []byte, r io.Reader, opts ...Options) {
|
||||
d.Struct = opts2
|
||||
}
|
||||
|
||||
// Options returns the options used to construct the encoder and
|
||||
// Options returns the options used to construct the decoder and
|
||||
// may additionally contain semantic options passed to a
|
||||
// [encoding/json/v2.UnmarshalDecode] call.
|
||||
//
|
||||
@@ -166,6 +166,8 @@ func (d *Decoder) Options() Options {
|
||||
return &d.s.Struct
|
||||
}
|
||||
|
||||
func (d *decoderState) options() *jsonopts.Struct { return &d.Struct }
|
||||
|
||||
var errBufferWriteAfterNext = errors.New("invalid bytes.Buffer.Write call after calling bytes.Buffer.Next")
|
||||
|
||||
// fetch reads at least 1 byte from the underlying io.Reader.
|
||||
@@ -193,7 +195,7 @@ func (d *decoderState) fetch() error {
|
||||
// may overwrite the contents of the current buffer.
|
||||
//
|
||||
// The user is trying to use a bytes.Buffer as a pipe,
|
||||
// but a bytes.Buffer is poor implementation of a pipe,
|
||||
// but a bytes.Buffer is a poor implementation of a pipe,
|
||||
// the purpose-built io.Pipe should be used instead.
|
||||
return &ioError{action: "read", err: errBufferWriteAfterNext}
|
||||
}
|
||||
@@ -306,7 +308,7 @@ func (d *decodeBuffer) PreviousTokenOrValue() []byte {
|
||||
|
||||
// PeekKind retrieves the next token kind, but does not advance the read offset.
|
||||
//
|
||||
// It returns 0 if an error occurs. Any such error is cached until
|
||||
// It returns [KindInvalid] 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 {
|
||||
@@ -407,10 +409,11 @@ func (d *decoderState) checkDelim(delim byte, next Kind) error {
|
||||
}
|
||||
|
||||
// SkipValue is semantically equivalent to calling [Decoder.ReadValue] and discarding
|
||||
// the result except that memory is not wasted trying to hold the entire result.
|
||||
// the result, except that memory is not wasted trying to hold the entire result.
|
||||
func (d *Decoder) SkipValue() error {
|
||||
return d.s.SkipValue()
|
||||
}
|
||||
|
||||
func (d *decoderState) SkipValue() error {
|
||||
switch d.PeekKind() {
|
||||
case '{', '[':
|
||||
@@ -621,6 +624,7 @@ func (d *decoderState) ReadToken() (Token, error) {
|
||||
if !d.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
d.Namespaces.push()
|
||||
}
|
||||
d.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
|
||||
pos += 1
|
||||
d.prevStart, d.prevEnd = pos, pos
|
||||
return BeginObject, nil
|
||||
@@ -641,6 +645,7 @@ func (d *decoderState) ReadToken() (Token, error) {
|
||||
if err = d.Tokens.pushArray(); err != nil {
|
||||
return Token{}, wrapSyntacticError(d, err, pos, +1)
|
||||
}
|
||||
d.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
|
||||
pos += 1
|
||||
d.prevStart, d.prevEnd = pos, pos
|
||||
return BeginArray, nil
|
||||
@@ -792,6 +797,12 @@ func (d *decoderState) CheckNextValue(last bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AtEOF reports whether the decoder is at EOF.
|
||||
func (d *decoderState) AtEOF() bool {
|
||||
_, err := d.consumeWhitespace(d.prevEnd)
|
||||
return err == io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
// CheckEOF verifies that the input has no more data.
|
||||
func (d *decoderState) CheckEOF() error {
|
||||
return d.checkEOF(d.prevEnd)
|
||||
@@ -1121,24 +1132,54 @@ func (d *decoderState) consumeArray(flags *jsonwire.ValueFlags, pos, depth int)
|
||||
// InputOffset returns the current input byte offset. It gives the location
|
||||
// of the next byte immediately after the most recently returned token or value.
|
||||
// The number of bytes actually read from the underlying [io.Reader] may be more
|
||||
// than this offset due to internal buffering effects.
|
||||
// than this offset due to internal buffering.
|
||||
func (d *Decoder) InputOffset() int64 {
|
||||
return d.s.previousOffsetEnd()
|
||||
}
|
||||
|
||||
// UnreadBuffer returns the data remaining in the unread buffer,
|
||||
// which may contain zero or more bytes.
|
||||
// This is the data already consumed from the input [io.Reader],
|
||||
// but not yet read by a [Decoder.ReadToken] or [Decoder.ReadValue] call.
|
||||
// It may contain bytes that do not form valid JSON, since it has not yet
|
||||
// been validated according to the JSON grammar.
|
||||
// The exact amount of buffered data is an implementation detail
|
||||
// of the Decoder and may change over time.
|
||||
//
|
||||
// It is the caller's responsibility to concatenate this buffer with
|
||||
// the remainder of the input Reader to obtain the full sequence
|
||||
// of bytes after the last read JSON token or value.
|
||||
//
|
||||
// The returned buffer must not be mutated while Decoder continues to be used.
|
||||
// The buffer contents are valid until the next Peek, Read, or Skip call.
|
||||
func (d *Decoder) UnreadBuffer() []byte {
|
||||
return d.s.unreadBuffer()
|
||||
}
|
||||
|
||||
// StackDepth returns the depth of the state machine for read JSON data.
|
||||
// StackDepth returns the depth of the state machine for JSON data
|
||||
// that has already been read.
|
||||
// Each level on the stack represents a nested JSON object or array.
|
||||
// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered
|
||||
// It is incremented whenever a [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.
|
||||
//
|
||||
// StackDepth returns 0 when not inside any object or array.
|
||||
// In particular, it returns 0 before any tokens have been read,
|
||||
// after any top-level value has been read, and between values
|
||||
// when decoding a stream of top-level values (e.g., NDJSON).
|
||||
// StackDepth returns 1 inside a top-level object or array,
|
||||
// 2 inside a nested object or array, and so on.
|
||||
//
|
||||
// For example, consider decoding the following JSON:
|
||||
//
|
||||
// {"a": [1, 2], "b": {"c": 3}}
|
||||
//
|
||||
// While decoding, StackDepth would report the following:
|
||||
//
|
||||
// - At the start, StackDepth reports 0.
|
||||
// - After decoding the outer '{', StackDepth reports 1.
|
||||
// - After decoding the inner '[', StackDepth reports 2.
|
||||
// - After decoding the inner ']', StackDepth reports 1.
|
||||
// - After decoding the outer '}', StackDepth reports 0.
|
||||
func (d *Decoder) StackDepth() int {
|
||||
// NOTE: Keep in sync with Encoder.StackDepth.
|
||||
return d.s.Tokens.Depth() - 1
|
||||
@@ -1148,13 +1189,13 @@ func (d *Decoder) StackDepth() int {
|
||||
// It must be a number between 0 and [Decoder.StackDepth], inclusive.
|
||||
// For each level, it reports the kind:
|
||||
//
|
||||
// - 0 for a level of zero,
|
||||
// - '{' for a level representing a JSON object, and
|
||||
// - '[' for a level representing a JSON array.
|
||||
// - [KindInvalid] for a level of zero,
|
||||
// - [KindBeginObject] for a level representing a JSON object, and
|
||||
// - [KindBeginArray] for a level representing a JSON array.
|
||||
//
|
||||
// It also reports the length of that JSON object or array.
|
||||
// It also reports the length of that JSON object or array decoded so far.
|
||||
// Each name and value in a JSON object is counted separately,
|
||||
// so the effective number of members would be half the length.
|
||||
// so the effective number of members is half the length.
|
||||
// A complete JSON object must have an even length.
|
||||
func (d *Decoder) StackIndex(i int) (Kind, int64) {
|
||||
// NOTE: Keep in sync with Encoder.StackIndex.
|
||||
|
||||
+6
-6
@@ -34,7 +34,7 @@
|
||||
//
|
||||
// - a JSON literal, string, or number
|
||||
// - a JSON object (e.g., `{"name":"value"}`)
|
||||
// - a JSON array (e.g., `[1,2,3,]`)
|
||||
// - a JSON array (e.g., `[1,2,3]`)
|
||||
//
|
||||
// A JSON value is represented by the [Value] type in Go and is a []byte
|
||||
// containing the raw textual representation of the value. There is some overlap
|
||||
@@ -43,18 +43,18 @@
|
||||
//
|
||||
// The [Encoder] and [Decoder] types contain methods to read or write the next
|
||||
// [Token] or [Value] in a sequence. They maintain a state machine to validate
|
||||
// whether the sequence of JSON tokens and/or values produces a valid JSON.
|
||||
// whether the sequence of JSON tokens and/or values produces valid JSON.
|
||||
// [Options] may be passed to the [NewEncoder] or [NewDecoder] constructors
|
||||
// to configure the syntactic behavior of encoding and decoding.
|
||||
// to configure the behavior of encoding and decoding.
|
||||
//
|
||||
// # Terminology
|
||||
//
|
||||
// The terms "encode" and "decode" are used for syntactic functionality
|
||||
// that is concerned with processing JSON based on its grammar, and
|
||||
// the terms "marshal" and "unmarshal" are used for semantic functionality
|
||||
// that determines the meaning of JSON values as Go values and vice-versa.
|
||||
// This package (i.e., [jsontext]) deals with JSON at a syntactic layer,
|
||||
// while [encoding/json/v2] deals with JSON at a semantic layer.
|
||||
// that determines the meaning of JSON values as Go values and vice versa.
|
||||
// This package deals with JSON syntax,
|
||||
// while [encoding/json/v2] deals with JSON semantics.
|
||||
// The goal is to provide a clear distinction between functionality that
|
||||
// is purely concerned with encoding versus that of marshaling.
|
||||
// For example, one can directly encode a stream of JSON tokens without
|
||||
|
||||
+58
-42
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// Encoder is a streaming encoder from raw JSON tokens and values.
|
||||
// Encoder is a streaming encoder to raw JSON tokens and values.
|
||||
// It is used to write a stream of top-level JSON values,
|
||||
// each terminated with a newline character.
|
||||
//
|
||||
@@ -41,7 +41,7 @@ import (
|
||||
// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}
|
||||
// e.WriteToken(EndObject) // }
|
||||
//
|
||||
// The above is one of many possible sequence of calls and
|
||||
// The above is one of many possible sequences of calls and
|
||||
// may not represent the most sensible method to call for any given token/value.
|
||||
// For example, it is probably more common to call [Encoder.WriteToken] with a string
|
||||
// for object names.
|
||||
@@ -50,7 +50,7 @@ type Encoder struct {
|
||||
}
|
||||
|
||||
// encoderState is the low-level state of Encoder.
|
||||
// It has exported fields and method for use by the "json" package.
|
||||
// It has exported fields and methods for use by the "json" package.
|
||||
type encoderState struct {
|
||||
state
|
||||
encodeBuffer
|
||||
@@ -96,7 +96,7 @@ 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.MarshalerTo.MarshalJSONTo] method
|
||||
// an 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 {
|
||||
@@ -124,21 +124,12 @@ func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) {
|
||||
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"
|
||||
}
|
||||
if e.Struct.Flags.Get(jsonflags.Multiline) {
|
||||
e.Struct.InitializeMultiline()
|
||||
}
|
||||
}
|
||||
|
||||
// Options returns the options used to construct the decoder and
|
||||
// Options returns the options used to construct the encoder and
|
||||
// may additionally contain semantic options passed to a
|
||||
// [encoding/json/v2.MarshalEncode] call.
|
||||
//
|
||||
@@ -150,6 +141,8 @@ func (e *Encoder) Options() Options {
|
||||
return &e.s.Struct
|
||||
}
|
||||
|
||||
func (e *encoderState) options() *jsonopts.Struct { return &e.Struct }
|
||||
|
||||
// NeedFlush determines whether to flush at this point.
|
||||
func (e *encoderState) NeedFlush() bool {
|
||||
// NOTE: This function is carefully written to be inlinable.
|
||||
@@ -178,7 +171,7 @@ func (e *encoderState) Flush() error {
|
||||
|
||||
// Specialize bytes.Buffer for better performance.
|
||||
if bb, ok := e.wr.(*bytes.Buffer); ok {
|
||||
// If e.buf already aliases the internal buffer of bb,
|
||||
// If e.Buf already aliases the internal buffer of bb,
|
||||
// then the Write call simply increments the internal offset,
|
||||
// otherwise Write operates as expected.
|
||||
// See https://go.dev/issue/42986.
|
||||
@@ -213,7 +206,7 @@ func (e *encoderState) Flush() error {
|
||||
e.Buf = e.Buf[:0]
|
||||
|
||||
// Check whether to grow the buffer.
|
||||
// Note that cap(e.buf) may already exceed maxBufferSize since
|
||||
// Note that cap(e.Buf) may already exceed maxBufferSize since
|
||||
// an append elsewhere already grew it to store a large token.
|
||||
const maxBufferSize = 4 << 10
|
||||
const growthSizeFactor = 2 // higher value is faster
|
||||
@@ -229,7 +222,7 @@ func (e *encoderState) Flush() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
func (d *encodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) }
|
||||
func (e *encodeBuffer) offsetAt(pos int) int64 { return e.baseOffset + int64(pos) }
|
||||
func (e *encodeBuffer) previousOffsetEnd() int64 { return e.baseOffset + int64(len(e.Buf)) }
|
||||
func (e *encodeBuffer) unflushedBuffer() []byte { return e.Buf }
|
||||
|
||||
@@ -344,9 +337,10 @@ func (e *encoderState) UnwriteOnlyObjectMemberName() string {
|
||||
// For example, it is an error to provide a number when the encoder
|
||||
// is expecting an object name (which is always a string), or
|
||||
// to provide an end object delimiter when the encoder is finishing an array.
|
||||
// If the provided token is invalid, then it reports a [SyntacticError] and
|
||||
// If the provided token is invalid, then WriteToken reports a [SyntacticError] and
|
||||
// the internal state remains unchanged. The offset reported
|
||||
// in [SyntacticError] will be relative to the [Encoder.OutputOffset].
|
||||
// in [SyntacticError] will be the [Encoder.OutputOffset] plus any delimiter
|
||||
// or whitespace characters that would have preceded the provided token.
|
||||
func (e *Encoder) WriteToken(t Token) error {
|
||||
return e.s.WriteToken(t)
|
||||
}
|
||||
@@ -405,6 +399,7 @@ func (e *encoderState) WriteToken(t Token) error {
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
e.Namespaces.push()
|
||||
}
|
||||
e.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
|
||||
case '}':
|
||||
b = append(b, '}')
|
||||
if err = e.Tokens.popObject(); err != nil {
|
||||
@@ -417,6 +412,7 @@ func (e *encoderState) WriteToken(t Token) error {
|
||||
case '[':
|
||||
b = append(b, '[')
|
||||
err = e.Tokens.pushArray()
|
||||
e.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
|
||||
case ']':
|
||||
b = append(b, ']')
|
||||
err = e.Tokens.popArray()
|
||||
@@ -471,7 +467,7 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
|
||||
if !isVerbatim {
|
||||
var err error
|
||||
b2 := append(e.availBuffer, b[pos+len(`"`):len(b)-len(`"`)]...)
|
||||
b, err = jsonwire.AppendQuote(b[:pos], string(b2), &e.Flags)
|
||||
b, err = jsonwire.AppendQuote(b[:pos], b2, &e.Flags)
|
||||
e.availBuffer = b2[:0]
|
||||
if err != nil {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
@@ -482,7 +478,7 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
|
||||
if e.Tokens.Last.NeedObjectName() {
|
||||
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
|
||||
if !e.Tokens.Last.isValidNamespace() {
|
||||
return wrapSyntacticError(e, err, pos, +1)
|
||||
return wrapSyntacticError(e, errInvalidNamespace, pos, +1)
|
||||
}
|
||||
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) {
|
||||
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
|
||||
@@ -522,9 +518,9 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
|
||||
//
|
||||
// The provided value kind must be consistent with the JSON grammar
|
||||
// (see examples on [Encoder.WriteToken]). If the provided value is invalid,
|
||||
// then it reports a [SyntacticError] and the internal state remains unchanged.
|
||||
// The offset reported in [SyntacticError] will be relative to the
|
||||
// [Encoder.OutputOffset] plus the offset into v of any encountered syntax error.
|
||||
// then WriteValue reports a [SyntacticError] and the internal state remains unchanged.
|
||||
// The offset reported in [SyntacticError] will be the [Encoder.OutputOffset]
|
||||
// plus the offset into v of any encountered syntax error.
|
||||
func (e *Encoder) WriteValue(v Value) error {
|
||||
return e.s.WriteValue(v)
|
||||
}
|
||||
@@ -541,7 +537,7 @@ func (e *encoderState) WriteValue(v Value) error {
|
||||
}
|
||||
pos := len(b) // offset before the value
|
||||
|
||||
// Append the value the output.
|
||||
// Append the value to the output.
|
||||
var n int
|
||||
n += jsonwire.ConsumeWhitespace(v[n:])
|
||||
b, m, err := e.reformatValue(b, v[n:], e.Tokens.Depth())
|
||||
@@ -715,7 +711,7 @@ func (e *encoderState) reformatValue(dst []byte, src Value, depth int) ([]byte,
|
||||
}
|
||||
|
||||
// reformatObject parses a JSON object from the start of src and
|
||||
// appends it to the end of src, reformatting whitespace and strings as needed.
|
||||
// appends it to the end of dst, reformatting whitespace and strings as needed.
|
||||
// It returns the extended dst buffer and the number of consumed input bytes.
|
||||
func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte, int, error) {
|
||||
// Append object begin.
|
||||
@@ -900,7 +896,7 @@ func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte,
|
||||
// OutputOffset returns the current output byte offset. It gives the location
|
||||
// of the next byte immediately after the most recently written token or value.
|
||||
// The number of bytes actually written to the underlying [io.Writer] may be less
|
||||
// than this offset due to internal buffering effects.
|
||||
// than this offset due to internal buffering.
|
||||
func (e *Encoder) OutputOffset() int64 {
|
||||
return e.s.previousOffsetEnd()
|
||||
}
|
||||
@@ -911,19 +907,21 @@ func (e *Encoder) OutputOffset() int64 {
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// b := d.AvailableBuffer()
|
||||
// b := e.AvailableBuffer()
|
||||
// b = append(b, '"')
|
||||
// b = appendString(b, v) // append the string formatting of v
|
||||
// b = append(b, '"')
|
||||
// ... := d.WriteValue(b)
|
||||
// ... := e.WriteValue(b)
|
||||
//
|
||||
// It is the user's responsibility to ensure that the value is valid JSON.
|
||||
// Note that WriteValue expects a valid JSON value. Constructing a value in a
|
||||
// raw []byte requires more care than using constructor functions like
|
||||
// [String], which always return valid [Token]s or [Value]s.
|
||||
func (e *Encoder) AvailableBuffer() []byte {
|
||||
// NOTE: We don't return e.buf[len(e.buf):cap(e.buf)] since WriteValue would
|
||||
// NOTE: We don't return e.Buf[len(e.Buf):cap(e.Buf)] since WriteValue would
|
||||
// need to take special care to avoid mangling the data while reformatting.
|
||||
// WriteValue can't easily identify whether the input Value aliases e.buf
|
||||
// WriteValue can't easily identify whether the input Value aliases e.Buf
|
||||
// without using unsafe.Pointer. Thus, we just return a different buffer.
|
||||
// Should this ever alias e.buf, we need to consider how it operates with
|
||||
// Should this ever alias e.Buf, we need to consider how it operates with
|
||||
// the specialized performance optimization for bytes.Buffer.
|
||||
n := 1 << bits.Len(uint(e.s.maxValue|63)) // fast approximation for max length
|
||||
if cap(e.s.availBuffer) < n {
|
||||
@@ -934,9 +932,27 @@ func (e *Encoder) AvailableBuffer() []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 [BeginObject] or [BeginArray] token is encountered
|
||||
// It is incremented whenever a [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.
|
||||
//
|
||||
// StackDepth returns 0 when not inside any object or array.
|
||||
// In particular, it returns 0 before any tokens have been written,
|
||||
// after any top-level value has been written, and between values
|
||||
// when encoding a stream of top-level values (e.g., NDJSON).
|
||||
// StackDepth returns 1 inside a top-level object or array,
|
||||
// 2 inside a nested object or array, and so on.
|
||||
//
|
||||
// For example, consider encoding the following JSON:
|
||||
//
|
||||
// {"a": [1, 2], "b": {"c": 3}}
|
||||
//
|
||||
// While encoding, StackDepth would report the following:
|
||||
//
|
||||
// - At the start, StackDepth reports 0.
|
||||
// - After encoding the outer '{', StackDepth reports 1.
|
||||
// - After encoding the inner '[', StackDepth reports 2.
|
||||
// - After encoding the inner ']', StackDepth reports 1.
|
||||
// - After encoding the outer '}', StackDepth reports 0.
|
||||
func (e *Encoder) StackDepth() int {
|
||||
// NOTE: Keep in sync with Decoder.StackDepth.
|
||||
return e.s.Tokens.Depth() - 1
|
||||
@@ -946,13 +962,13 @@ func (e *Encoder) StackDepth() int {
|
||||
// It must be a number between 0 and [Encoder.StackDepth], inclusive.
|
||||
// For each level, it reports the kind:
|
||||
//
|
||||
// - 0 for a level of zero,
|
||||
// - '{' for a level representing a JSON object, and
|
||||
// - '[' for a level representing a JSON array.
|
||||
// - [KindInvalid] for a level of zero,
|
||||
// - [KindBeginObject] for a level representing a JSON object, and
|
||||
// - [KindBeginArray] for a level representing a JSON array.
|
||||
//
|
||||
// It also reports the length of that JSON object or array.
|
||||
// It also reports the length of that JSON object or array encoded so far.
|
||||
// Each name and value in a JSON object is counted separately,
|
||||
// so the effective number of members would be half the length.
|
||||
// so the effective number of members is half the length.
|
||||
// A complete JSON object must have an even length.
|
||||
func (e *Encoder) StackIndex(i int) (Kind, int64) {
|
||||
// NOTE: Keep in sync with Decoder.StackIndex.
|
||||
|
||||
+34
-3
@@ -11,6 +11,8 @@ import (
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-json-experiment/json/internal/jsonflags"
|
||||
"github.com/go-json-experiment/json/internal/jsonopts"
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
@@ -28,7 +30,20 @@ func (e *ioError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// SyntacticError is a description of a syntactic error that occurred when
|
||||
type numError struct {
|
||||
accessor string // either "Int", "Uint", or "Float"
|
||||
value string // e.g., "1e1000"
|
||||
err error // either [strconv.ErrSyntax] or [strconv.ErrRange]
|
||||
}
|
||||
|
||||
func (e *numError) Error() string {
|
||||
return "jsontext.Token(" + e.value + ")." + e.accessor + " error: " + e.err.Error()
|
||||
}
|
||||
func (e *numError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// SyntacticError is a description of an error that occurred when
|
||||
// encoding or decoding JSON according to the grammar.
|
||||
//
|
||||
// The contents of this error as produced by this package may change over time.
|
||||
@@ -36,7 +51,7 @@ type SyntacticError struct {
|
||||
requireKeyedLiterals
|
||||
nonComparable
|
||||
|
||||
// ByteOffset indicates that an error occurred after this byte offset.
|
||||
// ByteOffset indicates that an error occurred at or after this byte offset.
|
||||
ByteOffset int64
|
||||
// JSONPointer indicates that an error occurred within this JSON value
|
||||
// as indicated using the JSON Pointer notation (see RFC 6901).
|
||||
@@ -53,10 +68,11 @@ type SyntacticError struct {
|
||||
// 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.
|
||||
// It takes a where that specifies 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 {
|
||||
options() *jsonopts.Struct
|
||||
offsetAt(pos int) int64
|
||||
AppendStackPointer(b []byte, where int) []byte
|
||||
}, err error, pos, where int) error {
|
||||
@@ -83,6 +99,21 @@ func wrapSyntacticError(state interface {
|
||||
}
|
||||
err = jsonwire.NewInvalidCharacterError(d.buf[pos:], where)
|
||||
}
|
||||
if state.options().Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) && err != io.ErrUnexpectedEOF {
|
||||
// The v1 error offset more consistently reported
|
||||
// the number of bytes read that lead to the error.
|
||||
// This implies that the offset is after the invalid text.
|
||||
if werr, ok := err.(*jsonwire.InvalidTextError); ok && werr != nil {
|
||||
offset += int64(len(werr.What))
|
||||
} else {
|
||||
// This implies a structural error (e.g., parsing a JSON number
|
||||
// when the grammar is expecting a JSON string for the object name).
|
||||
// In such a case, simply increment by one, since the first byte
|
||||
// is usually sufficient to indicate that the incoming token
|
||||
// cannot possibly be correct.
|
||||
offset++
|
||||
}
|
||||
}
|
||||
return &SyntacticError{ByteOffset: offset, JSONPointer: Pointer(ptr), Err: err}
|
||||
}
|
||||
|
||||
|
||||
+14
@@ -12,6 +12,20 @@ import (
|
||||
"github.com/go-json-experiment/json/internal"
|
||||
)
|
||||
|
||||
// TODO(https://go.dev/issue/73435): Remove the Internal symbol.
|
||||
//
|
||||
// The Go language lacks a 3rd category of visibility where
|
||||
// certain symbols can only be referenced from within the same module.
|
||||
// One solution to this is to put the entirety of a package with
|
||||
// both public and module-only symbols exposed as an internal package.
|
||||
// A separate, public package can re-export all of the public symbols
|
||||
// via type aliases and thus ensuring module-only symbols cannot be
|
||||
// referenced by the end-user. While this works, it unfortunately
|
||||
// leads to a poor user experience since the Go pkgsite is unable to
|
||||
// forward the documentation for symbols like methods and fields.
|
||||
// We need to improve the pkgsite experience before we can delete
|
||||
// the Internal symbol.
|
||||
|
||||
// Internal is for internal use only.
|
||||
// This is exempt from the Go compatibility agreement.
|
||||
var Internal exporter
|
||||
|
||||
+8
-8
@@ -17,7 +17,7 @@ import (
|
||||
// Options configures [NewEncoder], [Encoder.Reset], [NewDecoder],
|
||||
// and [Decoder.Reset] with specific features.
|
||||
// Each function takes in a variadic list of options, where properties
|
||||
// set in latter options override the value of previously set properties.
|
||||
// set in later 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:
|
||||
@@ -147,7 +147,7 @@ func CanonicalizeRawInts(v bool) Options {
|
||||
// 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.
|
||||
// finite number, then it is saturated at that value and formatted as such.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
func CanonicalizeRawFloats(v bool) Options {
|
||||
@@ -205,7 +205,7 @@ func SpaceAfterComma(v bool) Options {
|
||||
// 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,
|
||||
// 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].
|
||||
//
|
||||
@@ -222,9 +222,9 @@ func Multiline(v bool) Options {
|
||||
// where each element in a JSON object or array begins on a new, indented line
|
||||
// beginning with the indent prefix (see [WithIndentPrefix])
|
||||
// followed by one or more copies of indent according to the nesting depth.
|
||||
// The indent must only be composed of space or tab characters.
|
||||
// The indent must be composed of only space and tab characters.
|
||||
//
|
||||
// If the intent to emit indented output without a preference for
|
||||
// If the intent is to emit indented output without a preference for
|
||||
// the particular indent string, then use [Multiline] instead.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
@@ -249,7 +249,7 @@ func WithIndent(indent string) Options {
|
||||
|
||||
// Otherwise, allocate for this unique value.
|
||||
if s := strings.Trim(indent, " \t"); len(s) > 0 {
|
||||
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent")
|
||||
panic("json: invalid character " + jsonwire.QuoteRune([]byte(s)) + " in indent")
|
||||
}
|
||||
return jsonopts.Indent(indent)
|
||||
}
|
||||
@@ -258,13 +258,13 @@ func WithIndent(indent string) Options {
|
||||
// where each element in a JSON object or array begins on a new, indented line
|
||||
// beginning with the indent prefix followed by one or more copies of indent
|
||||
// (see [WithIndent]) according to the nesting depth.
|
||||
// The prefix must only be composed of space or tab characters.
|
||||
// The prefix must be composed of only space and tab characters.
|
||||
//
|
||||
// This only affects encoding and is ignored when decoding.
|
||||
// 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")
|
||||
panic("json: invalid character " + jsonwire.QuoteRune([]byte(s)) + " in indent prefix")
|
||||
}
|
||||
return jsonopts.IndentPrefix(prefix)
|
||||
}
|
||||
|
||||
+12
@@ -54,6 +54,10 @@ func getBufferedEncoder(opts ...Options) *Encoder {
|
||||
return e
|
||||
}
|
||||
func putBufferedEncoder(e *Encoder) {
|
||||
if cap(e.s.availBuffer) > 64<<10 {
|
||||
e.s.availBuffer = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
|
||||
// Recycle large buffers only if sufficiently utilized.
|
||||
// If a buffer is under-utilized enough times sequentially,
|
||||
// then it is discarded, ensuring that a single large buffer
|
||||
@@ -95,9 +99,14 @@ func getStreamingEncoder(w io.Writer, opts ...Options) *Encoder {
|
||||
}
|
||||
}
|
||||
func putStreamingEncoder(e *Encoder) {
|
||||
if cap(e.s.availBuffer) > 64<<10 {
|
||||
e.s.availBuffer = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
if _, ok := e.s.wr.(*bytes.Buffer); ok {
|
||||
e.s.wr, e.s.Buf = nil, nil // avoid pinning the provided bytes.Buffer
|
||||
bytesBufferEncoderPool.Put(e)
|
||||
} else {
|
||||
e.s.wr = nil // avoid pinning the provided io.Writer
|
||||
if cap(e.s.Buf) > 64<<10 {
|
||||
e.s.Buf = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
@@ -126,6 +135,7 @@ func getBufferedDecoder(b []byte, opts ...Options) *Decoder {
|
||||
return d
|
||||
}
|
||||
func putBufferedDecoder(d *Decoder) {
|
||||
d.s.buf = nil // avoid pinning the provided buffer
|
||||
bufferedDecoderPool.Put(d)
|
||||
}
|
||||
|
||||
@@ -142,8 +152,10 @@ func getStreamingDecoder(r io.Reader, opts ...Options) *Decoder {
|
||||
}
|
||||
func putStreamingDecoder(d *Decoder) {
|
||||
if _, ok := d.s.rd.(*bytes.Buffer); ok {
|
||||
d.s.rd, d.s.buf = nil, nil // avoid pinning the provided bytes.Buffer
|
||||
bytesBufferDecoderPool.Put(d)
|
||||
} else {
|
||||
d.s.rd = nil // avoid pinning the provided io.Reader
|
||||
if cap(d.s.buf) > 64<<10 {
|
||||
d.s.buf = nil // avoid pinning arbitrarily large amounts of memory
|
||||
}
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ import (
|
||||
// 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) {
|
||||
dst, err := jsonwire.AppendQuote(dst, src, &jsonflags.Flags{})
|
||||
dst, err := jsonwire.AppendQuote(dst, []byte(src), &jsonflags.Flags{})
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: err}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)
|
||||
// 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) {
|
||||
dst, err := jsonwire.AppendUnquote(dst, src)
|
||||
dst, err := jsonwire.AppendUnquote(dst, []byte(src))
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: err}
|
||||
}
|
||||
|
||||
+16
-16
@@ -24,8 +24,8 @@ import (
|
||||
// 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 {
|
||||
// serr, ok := errors.AsType[*jsontext.SyntacticError](err)
|
||||
// if ok && serr.Err == jsontext.ErrDuplicateName {
|
||||
// ptr := serr.JSONPointer // JSON pointer to duplicate name
|
||||
// name := ptr.LastToken() // duplicate name itself
|
||||
// ...
|
||||
@@ -86,12 +86,12 @@ func (s *state) reset() {
|
||||
// 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
|
||||
// (that happens to be a 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.
|
||||
// they both point to the same value.
|
||||
type Pointer string
|
||||
|
||||
// IsValid reports whether p is a valid JSON Pointer according to RFC 6901.
|
||||
@@ -117,13 +117,13 @@ func (p Pointer) Contains(pc Pointer) bool {
|
||||
}
|
||||
|
||||
// Parent strips off the last token and returns the remaining pointer.
|
||||
// The parent of an empty p is an empty string.
|
||||
// The parent of an empty Pointer is the 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.
|
||||
// The last token of an empty Pointer is the empty string.
|
||||
func (p Pointer) LastToken() string {
|
||||
last := p[max(strings.LastIndexByte(string(p), '/'), 0):]
|
||||
return unescapePointerToken(strings.TrimPrefix(string(last), "/"))
|
||||
@@ -131,14 +131,14 @@ func (p Pointer) LastToken() string {
|
||||
|
||||
// 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))
|
||||
return Pointer(appendEscapePointerName([]byte(p+"/"), []byte(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).
|
||||
// from first to last.
|
||||
func (p Pointer) Tokens() iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
for len(p) > 0 {
|
||||
@@ -202,7 +202,7 @@ func (s state) appendStackPointer(b []byte, where int) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func appendEscapePointerName[Bytes ~[]byte | ~string](b []byte, name Bytes) []byte {
|
||||
func appendEscapePointerName(b, name []byte) []byte {
|
||||
for _, r := range string(name) {
|
||||
// Per RFC 6901, section 3, escape '~' and '/' characters.
|
||||
switch r {
|
||||
@@ -439,7 +439,7 @@ const (
|
||||
stateTypeObject stateEntry = 0x8000_0000_0000_0000
|
||||
stateTypeArray stateEntry = 0x0000_0000_0000_0000
|
||||
|
||||
// The name check mask (2 bit) records whether to update
|
||||
// The name check mask (2 bits) records whether to update
|
||||
// the namespaces for the current JSON object and
|
||||
// whether the namespace is valid.
|
||||
stateNamespaceMask stateEntry = 0x6000_0000_0000_0000
|
||||
@@ -475,7 +475,7 @@ func (e stateEntry) NeedObjectName() bool {
|
||||
return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountEven
|
||||
}
|
||||
|
||||
// needImplicitColon reports whether an colon should occur next,
|
||||
// needImplicitColon reports whether a colon should occur next,
|
||||
// which always occurs after JSON object names.
|
||||
func (e stateEntry) needImplicitColon() bool {
|
||||
return e.needObjectValue()
|
||||
@@ -487,7 +487,7 @@ func (e stateEntry) needObjectValue() bool {
|
||||
return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountOdd
|
||||
}
|
||||
|
||||
// needImplicitComma reports whether an comma should occur next,
|
||||
// needImplicitComma reports whether a comma should occur next,
|
||||
// which always occurs after a value in a JSON object or array
|
||||
// before the next value (or name).
|
||||
func (e stateEntry) needImplicitComma(next Kind) bool {
|
||||
@@ -502,7 +502,7 @@ func (e *stateEntry) Increment() {
|
||||
}
|
||||
|
||||
// decrement decrements the number of elements for the current object or array.
|
||||
// It is the callers responsibility to ensure that e.length > 0.
|
||||
// It is the caller's responsibility to ensure that e.length > 0.
|
||||
func (e *stateEntry) decrement() {
|
||||
(*e)--
|
||||
}
|
||||
@@ -575,7 +575,7 @@ func (ns *objectNameStack) getUnquoted(i int) []byte {
|
||||
if i == 0 {
|
||||
return ns.unquotedNames[:ns.offsets[0]]
|
||||
} else {
|
||||
return ns.unquotedNames[ns.offsets[i-1]:ns.offsets[i-0]]
|
||||
return ns.unquotedNames[ns.offsets[i-1]:ns.offsets[i]]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ func (nss *objectNamespaceStack) pop() {
|
||||
}
|
||||
|
||||
// objectNamespace is the namespace for a JSON object.
|
||||
// In contrast to objectNameStack, this needs to remember a all names
|
||||
// In contrast to objectNameStack, this needs to remember all names
|
||||
// per JSON object.
|
||||
//
|
||||
// The zero value is an empty namespace ready for use.
|
||||
@@ -747,7 +747,7 @@ func (ns *objectNamespace) getUnquoted(i int) []byte {
|
||||
if i == 0 {
|
||||
return ns.allUnquotedNames[:ns.endOffsets[0]]
|
||||
} else {
|
||||
return ns.allUnquotedNames[ns.endOffsets[i-1]:ns.endOffsets[i-0]]
|
||||
return ns.allUnquotedNames[ns.endOffsets[i-1]:ns.endOffsets[i]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+288
-118
@@ -39,6 +39,20 @@ var errInvalidToken = errors.New("invalid jsontext.Token")
|
||||
// A Token cannot represent entire array or object values, while a [Value] can.
|
||||
// There is no Token to represent commas and colons since
|
||||
// these structural tokens can be inferred from the surrounding context.
|
||||
//
|
||||
// A Token stores data in one of two forms:
|
||||
//
|
||||
// - As raw JSON text: backed by the internal buffer of the [Decoder]
|
||||
// and only ever produced by [Decoder.ReadToken].
|
||||
// Such a token is only valid until the next call to any method on that
|
||||
// [Decoder] (e.g., [Decoder.PeekKind], [Decoder.ReadToken],
|
||||
// [Decoder.ReadValue], or [Decoder.SkipValue]).
|
||||
// Call [Token.Clone] to copy the raw text into an independent allocation
|
||||
// that persists beyond subsequent [Decoder] calls.
|
||||
//
|
||||
// - As a typed Go value: a self-contained representation produced by
|
||||
// the constructor functions (e.g., [String], [Int], [Uint], [Float]).
|
||||
// Such tokens are valid indefinitely and do not need to be cloned.
|
||||
type Token struct {
|
||||
nonComparable
|
||||
|
||||
@@ -48,21 +62,22 @@ type Token struct {
|
||||
// The Encoder accepts Tokens in either the "raw" or "exact" form.
|
||||
//
|
||||
// The following chart shows the possible values for each Token type:
|
||||
// ╔═════════════════╦════════════╤════════════╤════════════╗
|
||||
// ║ Token type ║ raw field │ str field │ num field ║
|
||||
// ╠═════════════════╬════════════╪════════════╪════════════╣
|
||||
// ║ null (raw) ║ "null" │ "" │ 0 ║
|
||||
// ║ false (raw) ║ "false" │ "" │ 0 ║
|
||||
// ║ true (raw) ║ "true" │ "" │ 0 ║
|
||||
// ║ string (raw) ║ non-empty │ "" │ offset ║
|
||||
// ║ string (string) ║ nil │ non-empty │ 0 ║
|
||||
// ║ number (raw) ║ non-empty │ "" │ offset ║
|
||||
// ║ number (float) ║ nil │ "f" │ non-zero ║
|
||||
// ║ number (int64) ║ nil │ "i" │ non-zero ║
|
||||
// ║ number (uint64) ║ nil │ "u" │ non-zero ║
|
||||
// ║ object (delim) ║ "{" or "}" │ "" │ 0 ║
|
||||
// ║ array (delim) ║ "[" or "]" │ "" │ 0 ║
|
||||
// ╚═════════════════╩════════════╧════════════╧════════════╝
|
||||
// ╔══════════════════╦════════════╤════════════╤════════════╗
|
||||
// ║ Token type ║ raw field │ str field │ num field ║
|
||||
// ╠══════════════════╬════════════╪════════════╪════════════╣
|
||||
// ║ null (raw) ║ "null" │ "" │ 0 ║
|
||||
// ║ false (raw) ║ "false" │ "" │ 0 ║
|
||||
// ║ true (raw) ║ "true" │ "" │ 0 ║
|
||||
// ║ string (raw) ║ non-empty │ "" │ offset ║
|
||||
// ║ string (string) ║ nil │ non-empty │ 0 ║
|
||||
// ║ number (raw) ║ non-empty │ "" │ offset ║
|
||||
// ║ number (float32) ║ nil │ "F" │ non-zero ║
|
||||
// ║ number (float64) ║ nil │ "f" │ non-zero ║
|
||||
// ║ number (int64) ║ nil │ "i" │ non-zero ║
|
||||
// ║ number (uint64) ║ nil │ "u" │ non-zero ║
|
||||
// ║ object (delim) ║ "{" or "}" │ "" │ 0 ║
|
||||
// ║ array (delim) ║ "[" or "]" │ "" │ 0 ║
|
||||
// ╚══════════════════╩════════════╧════════════╧════════════╝
|
||||
//
|
||||
// Notes:
|
||||
// - For tokens stored in "raw" form, the num field contains the
|
||||
@@ -80,11 +95,12 @@ type Token struct {
|
||||
raw *decodeBuffer
|
||||
|
||||
// str is the unescaped JSON string if num is zero.
|
||||
// Otherwise, it is "f", "i", or "u" if num should be interpreted
|
||||
// as a float64, int64, or uint64, respectively.
|
||||
// Otherwise, it is "F", "f", "i", or "u" if num should be interpreted
|
||||
// as a float32, float64, int64, or uint64, respectively.
|
||||
str string
|
||||
|
||||
// num is a float64, int64, or uint64 stored as a uint64 value.
|
||||
// num is a float32, float64, int64, or uint64 stored as a uint64 value.
|
||||
// For floating-point values, it stores the raw IEEE-754 bit-pattern.
|
||||
// It is non-zero for any JSON number in the "exact" form.
|
||||
num uint64
|
||||
}
|
||||
@@ -131,7 +147,29 @@ func String(s string) Token {
|
||||
return Token{str: s}
|
||||
}
|
||||
|
||||
// Float constructs a Token representing a JSON number.
|
||||
// Float32 constructs a Token representing a JSON number as
|
||||
// a 32-bit floating-point number formatted according to
|
||||
// ECMA-262, 10th edition, section 7.1.12.1,
|
||||
// with the exception that -0 is still formatted as -0.
|
||||
// The values NaN, +Inf, and -Inf will be represented
|
||||
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
|
||||
//
|
||||
// Note that most JSON libraries and standards assume that JSON numbers
|
||||
// are 64-bit floating-point numbers. Use of 32-bit precision should
|
||||
// only be used if the corresponding decoder knows that
|
||||
// this JSON number token is expected to only have 32-bit precision.
|
||||
// For all other situations, prefer using the [Float] constructor instead.
|
||||
func Float32(n float32) Token {
|
||||
if n != 0 && !math.IsNaN(float64(n)) && !math.IsInf(float64(n), 0) {
|
||||
return Token{str: "F", num: uint64(math.Float32bits(n))}
|
||||
}
|
||||
return Float(float64(n)) // handles ±0, NaN, and ±Inf
|
||||
}
|
||||
|
||||
// Float constructs a Token representing a JSON number as
|
||||
// a 64-bit floating-point number formatted according to
|
||||
// ECMA-262, 10th edition, section 7.1.12.1 and RFC 8785, section 3.2.2.3.
|
||||
// with the exception that -0 is still formatted as -0.
|
||||
// The values NaN, +Inf, and -Inf will be represented
|
||||
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
|
||||
func Float(n float64) Token {
|
||||
@@ -164,8 +202,10 @@ func Uint(n uint64) Token {
|
||||
return Token{str: "u", num: uint64(n)}
|
||||
}
|
||||
|
||||
// Clone makes a copy of the Token such that its value remains valid
|
||||
// even after a subsequent [Decoder.Read] call.
|
||||
// Clone returns a copy of the token with a value that is not backed by the
|
||||
// [Decoder] buffer and therefore remains valid past subsequent [Decoder] calls.
|
||||
// It has no effect on tokens produced by constructor functions,
|
||||
// since those are already self-contained.
|
||||
func (t Token) Clone() Token {
|
||||
// TODO: Allow caller to avoid any allocations?
|
||||
if raw := t.raw; raw != nil {
|
||||
@@ -189,6 +229,7 @@ func (t Token) Clone() Token {
|
||||
}
|
||||
}
|
||||
|
||||
// This only ever needs to clone strings and numbers.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
@@ -226,7 +267,7 @@ func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error)
|
||||
}
|
||||
} else if len(t.str) != 0 && t.num == 0 {
|
||||
// Handle exact string value.
|
||||
return jsonwire.AppendQuote(dst, t.str, flags)
|
||||
return jsonwire.AppendQuote(dst, []byte(t.str), flags)
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
@@ -265,8 +306,10 @@ func (t Token) string() (string, []byte) {
|
||||
// Handle tokens that are not JSON strings for fmt.Stringer.
|
||||
if t.num > 0 {
|
||||
switch t.str[0] {
|
||||
case 'F':
|
||||
return string(jsonwire.AppendFloat(nil, float64(math.Float32frombits(uint32(t.num))), 32)), nil
|
||||
case 'f':
|
||||
return string(jsonwire.AppendFloat(nil, math.Float64frombits(t.num), 64)), nil
|
||||
return string(jsonwire.AppendFloat(nil, float64(math.Float64frombits(uint64(t.num))), 64)), nil
|
||||
case 'i':
|
||||
return strconv.FormatInt(int64(t.num), 10), nil
|
||||
case 'u':
|
||||
@@ -289,8 +332,10 @@ func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error)
|
||||
} else if t.num != 0 {
|
||||
// Handle exact number value.
|
||||
switch t.str[0] {
|
||||
case 'F':
|
||||
return jsonwire.AppendFloat(dst, float64(math.Float32frombits(uint32(t.num))), 32), nil
|
||||
case 'f':
|
||||
return jsonwire.AppendFloat(dst, math.Float64frombits(t.num), 64), nil
|
||||
return jsonwire.AppendFloat(dst, float64(math.Float64frombits(uint64(t.num))), 64), nil
|
||||
case 'i':
|
||||
return strconv.AppendInt(dst, int64(t.num), 10), nil
|
||||
case 'u':
|
||||
@@ -301,30 +346,77 @@ func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error)
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// Float returns the floating-point value for a JSON number.
|
||||
// Float32 returns the floating-point value for a JSON number
|
||||
// parsed according to 32 bits of precision.
|
||||
//
|
||||
// If the JSON number is outside the representable range of a float32,
|
||||
// it returns +Inf or -Inf along with an error
|
||||
// that matches [strconv.ErrRange] according to [errors.Is].
|
||||
//
|
||||
// It returns a NaN, +Inf, or -Inf value for any JSON string
|
||||
// with the values "NaN", "Infinity", or "-Infinity".
|
||||
// It panics for all other cases.
|
||||
func (t Token) Float() float64 {
|
||||
//
|
||||
// It panics if the token kind is not a JSON number
|
||||
// or a JSON string with the aforementioned values.
|
||||
//
|
||||
// Note that most JSON libraries and standards assume that JSON numbers
|
||||
// are 64-bit floating-point numbers.
|
||||
// This method should only be used if the caller knows
|
||||
// from other context that this token is a JSON number
|
||||
// formatted only to 32 bits of precision (such as being encoded
|
||||
// using the [Float32] constructor). For all other situations,
|
||||
// prefer using the [Token.Float] accessor instead.
|
||||
func (t Token) Float32() (float32, error) {
|
||||
f, err := t.float(32)
|
||||
return float32(f), err
|
||||
}
|
||||
|
||||
// Float returns the floating-point value for a JSON number
|
||||
// parsed according to 64 bits of precision.
|
||||
//
|
||||
// If the JSON number is outside the representable range of a float64,
|
||||
// it returns +Inf or -Inf along with an error
|
||||
// that matches [strconv.ErrRange] according to [errors.Is].
|
||||
//
|
||||
// It returns a NaN, +Inf, or -Inf value for any JSON string
|
||||
// with the values "NaN", "Infinity", or "-Infinity".
|
||||
//
|
||||
// It panics if the token kind is not a JSON number
|
||||
// or a JSON string with the aforementioned values.
|
||||
func (t Token) Float() (float64, error) {
|
||||
f, err := t.float(64)
|
||||
return float64(f), err
|
||||
}
|
||||
|
||||
func (t Token) float(bits int) (float64, error) {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw number value.
|
||||
// Handle raw JSON number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
buf := raw.previousBuffer()
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
fv, _ := jsonwire.ParseFloat(buf, 64)
|
||||
return fv
|
||||
fv, err := strconv.ParseFloat(string(buf), bits)
|
||||
if err != nil {
|
||||
err = &numError{accessor: "Float", value: t.String(), err: errors.Unwrap(err)} // only ever ErrRange
|
||||
}
|
||||
return fv, err
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact number value.
|
||||
// Handle typed Go number value.
|
||||
switch t.str[0] {
|
||||
case 'F':
|
||||
return float64(math.Float32frombits(uint32(t.num))), nil
|
||||
case 'f':
|
||||
return math.Float64frombits(t.num)
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if bits == 32 && !math.IsInf(f64, 0) && math.IsInf(float64(float32(f64)), 0) {
|
||||
return f64, &numError{accessor: "Float", value: t.String(), err: strconv.ErrRange}
|
||||
}
|
||||
return f64, nil
|
||||
case 'i':
|
||||
return float64(int64(t.num))
|
||||
return float64(int64(t.num)), nil // NOTE: This may lead to loss of precision.
|
||||
case 'u':
|
||||
return float64(uint64(t.num))
|
||||
return float64(uint64(t.num)), nil // NOTE: This may lead to loss of precision.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,128 +424,181 @@ func (t Token) Float() float64 {
|
||||
if t.Kind() == '"' {
|
||||
switch t.String() {
|
||||
case "NaN":
|
||||
return math.NaN()
|
||||
return math.NaN(), nil
|
||||
case "Infinity":
|
||||
return math.Inf(+1)
|
||||
return math.Inf(+1), nil
|
||||
case "-Infinity":
|
||||
return math.Inf(-1)
|
||||
return math.Inf(-1), nil
|
||||
}
|
||||
// TODO: Should this be an error instead of a panic?
|
||||
// We can safely switch from a panic to an error in the future.
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// Int returns the signed integer value for a JSON number.
|
||||
//
|
||||
// It reports an error that matches [strconv.ErrSyntax] according to [errors.Is]
|
||||
// if the JSON number does not match the restricted grammar of just a signed integer.
|
||||
// It reports an error that matches [strconv.ErrRange] according to [errors.Is]
|
||||
// if the JSON number is a signed integer, but outside the range of an int64.
|
||||
// Even if an error is reported, a reasonable value is still returned.
|
||||
// The fractional component of any number is ignored (truncation toward zero).
|
||||
// Any number beyond the representation of an int64 will be saturated
|
||||
// to the closest representable value.
|
||||
//
|
||||
// It panics if the token kind is not a JSON number.
|
||||
func (t Token) Int() int64 {
|
||||
func (t Token) Int() (int64, error) {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw integer value.
|
||||
// Handle raw JSON number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
neg := false
|
||||
buf := raw.previousBuffer()
|
||||
if len(buf) > 0 && buf[0] == '-' {
|
||||
neg, buf = true, buf[1:]
|
||||
}
|
||||
if numAbs, ok := jsonwire.ParseUint(buf); ok {
|
||||
if neg {
|
||||
if numAbs > -minInt64 {
|
||||
return minInt64
|
||||
}
|
||||
return -1 * int64(numAbs)
|
||||
} else {
|
||||
if numAbs > +maxInt64 {
|
||||
return maxInt64
|
||||
}
|
||||
return +1 * int64(numAbs)
|
||||
// Prospectively parse a negative integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf[len("-"):]); {
|
||||
case abs > -minInt64:
|
||||
return minInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
case ok:
|
||||
return -1 * int64(abs), nil
|
||||
}
|
||||
} else {
|
||||
// Prospectively parse a non-negative integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf); {
|
||||
case abs > +maxInt64:
|
||||
return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
case ok:
|
||||
return +1 * int64(abs), nil
|
||||
}
|
||||
}
|
||||
// This is not a signed integer, which implies ErrSyntax.
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
f64, _ := strconv.ParseFloat(string(buf), 64)
|
||||
return f64toi64(f64), &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact integer value.
|
||||
// Handle typed Go number value.
|
||||
switch t.str[0] {
|
||||
case 'i':
|
||||
return int64(t.num)
|
||||
return int64(t.num), nil
|
||||
case 'u':
|
||||
if t.num > maxInt64 {
|
||||
return maxInt64
|
||||
return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
}
|
||||
return int64(t.num), nil
|
||||
case 'f', 'F':
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if t.str[0] == 'F' {
|
||||
f64 = float64(math.Float32frombits(uint32(t.num)))
|
||||
}
|
||||
switch i64 := f64toi64(f64); {
|
||||
case math.IsNaN(f64), math.Trunc(f64) != f64:
|
||||
return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
|
||||
case (i64 == minInt64 && f64 < minInt64) || (i64 == maxInt64 && f64 > maxInt64):
|
||||
return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
|
||||
default:
|
||||
return i64, nil
|
||||
}
|
||||
return int64(t.num)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON number that is a floating-point value.
|
||||
if t.Kind() == '0' {
|
||||
switch fv := t.Float(); {
|
||||
case fv >= maxInt64:
|
||||
return maxInt64
|
||||
case fv <= minInt64:
|
||||
return minInt64
|
||||
default:
|
||||
return int64(fv) // truncation toward zero
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
func f64toi64(f64 float64) int64 {
|
||||
switch {
|
||||
case math.IsNaN(f64):
|
||||
return 0
|
||||
case f64 >= maxInt64+1:
|
||||
return maxInt64
|
||||
case f64 < minInt64:
|
||||
return minInt64
|
||||
default:
|
||||
return int64(f64) // NOTE: This may lead to loss of precision.
|
||||
}
|
||||
}
|
||||
|
||||
// Uint returns the unsigned integer value for a JSON number.
|
||||
//
|
||||
// It reports an error that matches [strconv.ErrSyntax] if the JSON number
|
||||
// does not match the restricted grammar of just an unsigned integer.
|
||||
// It reports an error that matches [strconv.ErrRange] if the JSON number
|
||||
// is an unsigned integer, but outside the representable range of a uint64.
|
||||
// Even if an error is reported, a reasonable value is still returned.
|
||||
// The fractional component of any number is ignored (truncation toward zero).
|
||||
// Any number beyond the representation of an uint64 will be saturated
|
||||
// Any number beyond the representation of a uint64 will be saturated
|
||||
// to the closest representable value.
|
||||
//
|
||||
// It panics if the token kind is not a JSON number.
|
||||
func (t Token) Uint() uint64 {
|
||||
func (t Token) Uint() (uint64, error) {
|
||||
// NOTE: This accessor returns 0 for any negative JSON number,
|
||||
// which might be surprising, but is at least consistent with the behavior
|
||||
// of saturating out-of-bounds numbers to the closest representable number.
|
||||
// We report ErrSyntax instead of ErrRange since the grammar for
|
||||
// an unsigned integer does not permit a negative sign.
|
||||
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw integer value.
|
||||
// Handle raw JSON number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
neg := false
|
||||
buf := raw.previousBuffer()
|
||||
if len(buf) > 0 && buf[0] == '-' {
|
||||
neg, buf = true, buf[1:]
|
||||
// Prospectively parse an unsigned integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf); {
|
||||
case ok:
|
||||
return abs, nil
|
||||
case abs == maxUint64: // implies overflows
|
||||
return maxUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
|
||||
}
|
||||
if num, ok := jsonwire.ParseUint(buf); ok {
|
||||
if neg {
|
||||
return minUint64
|
||||
}
|
||||
return num
|
||||
// This is not an unsigned integer, which implies ErrSyntax.
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
f64, _ := strconv.ParseFloat(string(buf), 64)
|
||||
return f64tou64(f64), &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact integer value.
|
||||
// Handle typed Go number value.
|
||||
switch t.str[0] {
|
||||
case 'u':
|
||||
return t.num
|
||||
return t.num, nil
|
||||
case 'i':
|
||||
if int64(t.num) < minUint64 {
|
||||
return minUint64
|
||||
return minUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
|
||||
}
|
||||
return uint64(int64(t.num)), nil
|
||||
case 'f', 'F':
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if t.str[0] == 'F' {
|
||||
f64 = float64(math.Float32frombits(uint32(t.num)))
|
||||
}
|
||||
switch u64 := f64tou64(f64); {
|
||||
case math.IsNaN(f64), math.Trunc(f64) != f64, math.Signbit(f64):
|
||||
return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
|
||||
case (u64 == minUint64 && f64 < minUint64) || (u64 == maxUint64 && f64 > maxUint64):
|
||||
return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
|
||||
default:
|
||||
return u64, nil
|
||||
}
|
||||
return uint64(int64(t.num))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON number that is a floating-point value.
|
||||
if t.Kind() == '0' {
|
||||
switch fv := t.Float(); {
|
||||
case fv >= maxUint64:
|
||||
return maxUint64
|
||||
case fv <= minUint64:
|
||||
return minUint64
|
||||
default:
|
||||
return uint64(fv) // truncation toward zero
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
func f64tou64(f64 float64) uint64 {
|
||||
switch {
|
||||
case math.IsNaN(f64):
|
||||
return 0
|
||||
case f64 >= maxUint64+1:
|
||||
return maxUint64
|
||||
case f64 < minUint64:
|
||||
return minUint64
|
||||
default:
|
||||
return uint64(f64) // NOTE: This may lead to loss of precision.
|
||||
}
|
||||
}
|
||||
|
||||
// Kind returns the token kind.
|
||||
func (t Token) Kind() Kind {
|
||||
switch {
|
||||
@@ -472,29 +617,33 @@ func (t Token) Kind() Kind {
|
||||
}
|
||||
}
|
||||
|
||||
// Kind represents each possible JSON token kind with a single byte,
|
||||
// which is conveniently the first byte of that kind's grammar
|
||||
// with the restriction that numbers always be represented with '0':
|
||||
// A Kind represents the kind of a JSON token.
|
||||
//
|
||||
// - 'n': null
|
||||
// - 'f': false
|
||||
// - 't': true
|
||||
// - '"': string
|
||||
// - '0': number
|
||||
// - '{': object begin
|
||||
// - '}': object end
|
||||
// - '[': array begin
|
||||
// - ']': array end
|
||||
//
|
||||
// An invalid kind is usually represented using 0,
|
||||
// but may be non-zero due to invalid JSON data.
|
||||
// A Kind is a single byte, which is conveniently the first byte of that
|
||||
// kind's symbol in the grammar (except for numbers, which are always represented
|
||||
// with '0').
|
||||
type Kind byte
|
||||
|
||||
const (
|
||||
KindInvalid Kind = 0 // invalid kind
|
||||
KindNull Kind = 'n' // null
|
||||
KindFalse Kind = 'f' // false
|
||||
KindTrue Kind = 't' // true
|
||||
KindString Kind = '"' // string
|
||||
KindNumber Kind = '0' // number
|
||||
KindBeginObject Kind = '{' // begin object
|
||||
KindEndObject Kind = '}' // end object
|
||||
KindBeginArray Kind = '[' // begin array
|
||||
KindEndArray Kind = ']' // end array
|
||||
)
|
||||
|
||||
const invalidKind Kind = 0
|
||||
|
||||
// String prints the kind in a humanly readable fashion.
|
||||
// String returns a string representation of k.
|
||||
func (k Kind) String() string {
|
||||
switch k {
|
||||
case 0:
|
||||
return "invalid"
|
||||
case 'n':
|
||||
return "null"
|
||||
case 'f':
|
||||
@@ -514,14 +663,35 @@ func (k Kind) String() string {
|
||||
case ']':
|
||||
return "]"
|
||||
default:
|
||||
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune(string(k)) + ">"
|
||||
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune([]byte{byte(k)}) + ">"
|
||||
}
|
||||
}
|
||||
|
||||
// normalize coalesces all possible starting characters of a number as just '0'.
|
||||
var normKind = [256]Kind{
|
||||
'n': 'n',
|
||||
'f': 'f',
|
||||
't': 't',
|
||||
'"': '"',
|
||||
'{': '{',
|
||||
'}': '}',
|
||||
'[': '[',
|
||||
']': ']',
|
||||
'-': '0',
|
||||
'0': '0',
|
||||
'1': '0',
|
||||
'2': '0',
|
||||
'3': '0',
|
||||
'4': '0',
|
||||
'5': '0',
|
||||
'6': '0',
|
||||
'7': '0',
|
||||
'8': '0',
|
||||
'9': '0',
|
||||
}
|
||||
|
||||
// normalize coalesces all possible starting characters of a number as just '0',
|
||||
// and converts all invalid kinds to 0.
|
||||
func (k Kind) normalize() Kind {
|
||||
if k == '-' || ('0' <= k && k <= '9') {
|
||||
return '0'
|
||||
}
|
||||
return k
|
||||
// A lookup table keeps the inlining cost as low as possible.
|
||||
return normKind[k]
|
||||
}
|
||||
+32
-17
@@ -17,7 +17,24 @@ import (
|
||||
"github.com/go-json-experiment/json/internal/jsonwire"
|
||||
)
|
||||
|
||||
// NOTE: Value is analogous to v1 json.RawMessage.
|
||||
// AppendFloat appends src to dst as a JSON number per RFC 8259, section 6.
|
||||
//
|
||||
// Except for -0, which is formatted as -0 instead of 0,
|
||||
// the output is identical to ECMA-262, 10th edition, section 7.1.12.1
|
||||
// and (for 64-bit precision) identical to RFC 8785, section 3.2.2.3.
|
||||
// The values NaN, +Inf, and -Inf will be represented as a JSON string
|
||||
// with the values "NaN", "Infinity", and "-Infinity".
|
||||
//
|
||||
// Note that most JSON libraries and standards assume that JSON numbers
|
||||
// are 64-bit floating-point numbers. As such, prefer using 64 bits
|
||||
// of precision unless the recipient can know from other context
|
||||
// that the encoded number uses 32 bits of precision.
|
||||
func AppendFloat(dst []byte, src float64, bits int) []byte {
|
||||
if bits != 32 && bits != 64 {
|
||||
panic("illegal AppendFloat bit size")
|
||||
}
|
||||
return jsonwire.AppendFloat(dst, src, bits)
|
||||
}
|
||||
|
||||
// AppendFormat formats the JSON value in src and appends it to dst
|
||||
// according to the specified options.
|
||||
@@ -25,16 +42,18 @@ import (
|
||||
//
|
||||
// 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) {
|
||||
func AppendFormat[Bytes ~[]byte | ~string](dst []byte, src Bytes, 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 {
|
||||
if err := e.s.WriteValue(Value(src)); err != nil {
|
||||
return append(dst, src...), err
|
||||
}
|
||||
return append(dst, e.s.Buf...), nil
|
||||
}
|
||||
|
||||
// NOTE: Value is analogous to v1 json.RawMessage.
|
||||
|
||||
// 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!")
|
||||
@@ -113,7 +132,7 @@ func (v *Value) Format(opts ...Options) error {
|
||||
return v.format(opts, nil)
|
||||
}
|
||||
|
||||
// format accepts two []Options to avoid the allocation appending them together.
|
||||
// format accepts two []Options to avoid the allocation of appending them together.
|
||||
// It is equivalent to v.Format(append(opts1, opts2...)...).
|
||||
func (v *Value) format(opts1, opts2 []Options) error {
|
||||
e := getBufferedEncoder(opts1...)
|
||||
@@ -133,7 +152,7 @@ func (v *Value) format(opts1, opts2 []Options) error {
|
||||
//
|
||||
// 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.
|
||||
// it permits values with duplicate names and invalid UTF-8.
|
||||
//
|
||||
// Compact is equivalent to calling [Value.Format] with the following options:
|
||||
// - [AllowDuplicateNames](true)
|
||||
@@ -151,11 +170,11 @@ func (v *Value) Compact(opts ...Options) error {
|
||||
}
|
||||
|
||||
// Indent reformats the whitespace in the raw JSON value so that each element
|
||||
// in a JSON object or array begins on a indented line according to the nesting.
|
||||
// in a JSON object or array begins on an indented line according to the nesting.
|
||||
//
|
||||
// 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.
|
||||
// it permits values with duplicate names and invalid UTF-8.
|
||||
//
|
||||
// Indent is equivalent to calling [Value.Format] with the following options:
|
||||
// - [AllowDuplicateNames](true)
|
||||
@@ -175,8 +194,10 @@ func (v *Value) Indent(opts ...Options) error {
|
||||
}
|
||||
|
||||
// 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 Canonicalization Scheme (JCS) as defined by RFC 8785.
|
||||
// Canonicalization produces a JSON value with the same meaning as the original,
|
||||
// but is stable in the sense that calling Canonicalize on a canonicalized
|
||||
// value does nothing.
|
||||
//
|
||||
// JSON strings are formatted to use their minimal representation,
|
||||
// JSON numbers are formatted as double precision numbers according
|
||||
@@ -184,12 +205,6 @@ func (v *Value) Indent(opts ...Options) error {
|
||||
// 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)
|
||||
@@ -214,7 +229,7 @@ func (v *Value) Canonicalize(opts ...Options) error {
|
||||
}
|
||||
|
||||
// MarshalJSON returns v as the JSON encoding of v.
|
||||
// It returns the stored value as the raw JSON output without any validation.
|
||||
// It performs no validation.
|
||||
// If v is nil, then this returns a JSON null.
|
||||
func (v Value) MarshalJSON() ([]byte, error) {
|
||||
// NOTE: This matches the behavior of v1 json.RawMessage.MarshalJSON.
|
||||
@@ -236,7 +251,7 @@ func (v *Value) UnmarshalJSON(b []byte) error {
|
||||
}
|
||||
|
||||
// Kind returns the starting token kind.
|
||||
// For a valid value, this will never include '}' or ']'.
|
||||
// For a valid value, this will never include [KindEndObject] or [KindEndArray].
|
||||
func (v Value) Kind() Kind {
|
||||
if v := v[jsonwire.ConsumeWhitespace(v):]; len(v) > 0 {
|
||||
return Kind(v[0]).normalize()
|
||||
|
||||
Reference in new issue
Block a user