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

@@ -12,7 +12,7 @@ with the string "WARNING: " near the top of the commit message.
It is your responsibility to inspect the list of commit changes
when upgrading the module. Not all breaking changes will lead to build failures.
A [Discussion about including this package in Go as `encoding/json/v2`](https://github.com/golang/go/discussions/63397) has been started on the Go Github project on 2023-10-05. Please provide your feedback there.
A [proposal to include this module in Go as `encoding/json/v2` and `encoding/json/jsontext`](https://github.com/golang/go/issues/71497) has been started on the Go Github project on 2025-01-30. Please provide your feedback there.
## Goals and objectives
@@ -24,7 +24,7 @@ in v2 to be named the same and have a mostly compatible signature.
Behaviorally, we should aim for 95% to 99% backwards compatibility.
We do not aim for 100% compatibility since we want the freedom to break
certain behaviors that are now considered to have been a mistake.
We may provide options that can bring the v2 implementation to 100% compatibility,
Options exist that can bring the v2 implementation to 100% compatibility,
but it will not be the default.
* **More flexible:** There is a
@@ -141,30 +141,28 @@ This table shows an overview of the changes:
| v1 | v2 | Details |
| -- | -- | ------- |
| JSON object members are unmarshaled into a Go struct using a **case-insensitive name match**. | JSON object members are unmarshaled into a Go struct using a **case-sensitive name match**. | [CaseSensitivity](/diff_test.go#:~:text=TestCaseSensitivity) |
| When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value is an empty Go value**, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. | When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value would encode as an empty JSON value**, which is defined as a JSON null, or an empty JSON string, object, or array. | [OmitEmptyOption](/diff_test.go#:~:text=TestOmitEmptyOption) |
| The `string` option **does affect** Go bools. | The `string` option **does not affect** Go bools. | [StringOption](/diff_test.go#:~:text=TestStringOption) |
| The `string` option **does not recursively affect** sub-values of the Go field value. | The `string` option **does recursively affect** sub-values of the Go field value. | [StringOption](/diff_test.go#:~:text=TestStringOption) |
| The `string` option **sometimes accepts** a JSON null escaped within a JSON string. | The `string` option **never accepts** a JSON null escaped within a JSON string. | [StringOption](/diff_test.go#:~:text=TestStringOption) |
| A nil Go slice is marshaled as a **JSON null**. | A nil Go slice is marshaled as an **empty JSON array**. | [NilSlicesAndMaps](/diff_test.go#:~:text=TestNilSlicesAndMaps) |
| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/diff_test.go#:~:text=TestNilSlicesAndMaps) |
| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/diff_test.go#:~:text=Arrays) |
| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/diff_test.go#:~:text=TestByteArrays) |
| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/diff_test.go#:~:text=TestPointerReceiver) |
| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/diff_test.go#:~:text=TestMapDeterminism) |
| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/diff_test.go#:~:text=TestEscapeHTML) |
| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/diff_test.go#:~:text=TestInvalidUTF8) |
| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/diff_test.go#:~:text=TestInvalidUTF8) |
| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/diff_test.go#:~:text=TestDuplicateNames) |
| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/diff_test.go#:~:text=TestDuplicateNames) |
| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/diff_test.go#:~:text=TestMergeNull) |
| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/diff_test.go#:~:text=TestMergeComposite) |
| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/diff_test.go#:~:text=TestTimeDurations) |
| Unmarshaling a JSON number into a Go float beyond its representation **results in an error**. | Unmarshaling a JSON number into a Go float beyond its representation **uses the closest representable value (e.g., ±`math.MaxFloat`)**. | [MaxFloats](/diff_test.go#:~:text=TestMaxFloats) |
| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/diff_test.go#:~:text=TestEmptyStructs) |
| A Go struct that embeds an unexported struct type **can sometimes be serialized**. | A Go struct that embeds an unexported struct type **cannot be serialized**. | [EmbedUnexported](/diff_test.go#:~:text=TestEmbedUnexported) |
| JSON object members are unmarshaled into a Go struct using a **case-insensitive name match**. | JSON object members are unmarshaled into a Go struct using a **case-sensitive name match**. | [CaseSensitivity](/v1/diff_test.go#:~:text=TestCaseSensitivity) |
| When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value is an empty Go value**, which is defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string. | When marshaling a Go struct, a struct field marked as `omitempty` is omitted if **the field value would encode as an empty JSON value**, which is defined as a JSON null, or an empty JSON string, object, or array. | [OmitEmptyOption](/v1/diff_test.go#:~:text=TestOmitEmptyOption) |
| The `string` option **does affect** Go strings and bools. | The `string` option **does not affect** Go strings or bools. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| The `string` option **does not recursively affect** sub-values of the Go field value. | The `string` option **does recursively affect** sub-values of the Go field value. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| The `string` option **sometimes accepts** a JSON null escaped within a JSON string. | The `string` option **never accepts** a JSON null escaped within a JSON string. | [StringOption](/v1/diff_test.go#:~:text=TestStringOption) |
| A nil Go slice is marshaled as a **JSON null**. | A nil Go slice is marshaled as an **empty JSON array**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
| A nil Go map is marshaled as a **JSON null**. | A nil Go map is marshaled as an **empty JSON object**. | [NilSlicesAndMaps](/v1/diff_test.go#:~:text=TestNilSlicesAndMaps) |
| A Go array may be unmarshaled from a **JSON array of any length**. | A Go array must be unmarshaled from a **JSON array of the same length**. | [Arrays](/v1/diff_test.go#:~:text=Arrays) |
| A Go byte array is represented as a **JSON array of JSON numbers**. | A Go byte array is represented as a **Base64-encoded JSON string**. | [ByteArrays](/v1/diff_test.go#:~:text=TestByteArrays) |
| `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **inconsistently called**. | `MarshalJSON` and `UnmarshalJSON` methods declared on a pointer receiver are **consistently called**. | [PointerReceiver](/v1/diff_test.go#:~:text=TestPointerReceiver) |
| A Go map is marshaled in a **deterministic order**. | A Go map is marshaled in a **non-deterministic order**. | [MapDeterminism](/v1/diff_test.go#:~:text=TestMapDeterminism) |
| JSON strings are encoded **with HTML-specific characters being escaped**. | JSON strings are encoded **without any characters being escaped** (unless necessary). | [EscapeHTML](/v1/diff_test.go#:~:text=TestEscapeHTML) |
| When marshaling, invalid UTF-8 within a Go string **are silently replaced**. | When marshaling, invalid UTF-8 within a Go string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) |
| When unmarshaling, invalid UTF-8 within a JSON string **are silently replaced**. | When unmarshaling, invalid UTF-8 within a JSON string **results in an error**. | [InvalidUTF8](/v1/diff_test.go#:~:text=TestInvalidUTF8) |
| When marshaling, **an error does not occur** if the output JSON value contains objects with duplicate names. | When marshaling, **an error does occur** if the output JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) |
| When unmarshaling, **an error does not occur** if the input JSON value contains objects with duplicate names. | When unmarshaling, **an error does occur** if the input JSON value contains objects with duplicate names. | [DuplicateNames](/v1/diff_test.go#:~:text=TestDuplicateNames) |
| Unmarshaling a JSON null into a non-empty Go value **inconsistently clears the value or does nothing**. | Unmarshaling a JSON null into a non-empty Go value **always clears the value**. | [MergeNull](/v1/diff_test.go#:~:text=TestMergeNull) |
| Unmarshaling a JSON value into a non-empty Go value **follows inconsistent and bizarre behavior**. | Unmarshaling a JSON value into a non-empty Go value **always merges if the input is an object, and otherwise replaces**. | [MergeComposite](/v1/diff_test.go#:~:text=TestMergeComposite) |
| A `time.Duration` is represented as a **JSON number containing the decimal number of nanoseconds**. | A `time.Duration` is represented as a **JSON string containing the formatted duration (e.g., "1h2m3.456s")**. | [TimeDurations](/v1/diff_test.go#:~:text=TestTimeDurations) |
| A Go struct with only unexported fields **can be serialized**. | A Go struct with only unexported fields **cannot be serialized**. | [EmptyStructs](/v1/diff_test.go#:~:text=TestEmptyStructs) |
See [diff_test.go](/diff_test.go) for details about every change.
See [diff_test.go](/v1/diff_test.go) for details about every change.
## Performance

View File

@@ -6,32 +6,33 @@ package json
import (
"bytes"
"errors"
"encoding"
"io"
"reflect"
"slices"
"strings"
"sync"
"time"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/internal/jsonwire"
"github.com/go-json-experiment/json/jsontext"
)
// Reference encoding and time packages to assist pkgsite
// in being able to hotlink references to those packages.
var (
_ encoding.TextMarshaler
_ encoding.TextAppender
_ encoding.TextUnmarshaler
_ time.Time
_ time.Duration
)
// export exposes internal functionality of the "jsontext" package.
var export = jsontext.Internal.Export(&internal.AllowInternalUse)
var structOptionsPool = &sync.Pool{New: func() any { return new(jsonopts.Struct) }}
func getStructOptions() *jsonopts.Struct {
return structOptionsPool.Get().(*jsonopts.Struct)
}
func putStructOptions(o *jsonopts.Struct) {
*o = jsonopts.Struct{}
structOptionsPool.Put(o)
}
// Marshal serializes a Go value as a []byte according to the provided
// marshal and encode options (while ignoring unmarshal or decode options).
// It does not terminate the output with a newline.
@@ -52,12 +53,16 @@ func putStructOptions(o *jsonopts.Struct) {
// If all applicable functions return [SkipFunc],
// then the value is encoded according to subsequent rules.
//
// - If the value type implements [MarshalerV2],
// then the MarshalJSONV2 method is called to encode the value.
// - If the value type implements [MarshalerTo],
// then the MarshalJSONTo method is called to encode the value.
//
// - If the value type implements [MarshalerV1],
// - If the value type implements [Marshaler],
// then the MarshalJSON method is called to encode the value.
//
// - If the value type implements [encoding.TextAppender],
// then the AppendText method is called to encode the value and
// subsequently encode its result as a JSON string.
//
// - If the value type implements [encoding.TextMarshaler],
// then the MarshalText method is called to encode the value and
// subsequently encode its result as a JSON string.
@@ -90,11 +95,12 @@ func putStructOptions(o *jsonopts.Struct) {
// where each byte is recursively JSON-encoded as each JSON array element.
//
// - A Go integer is encoded as a JSON number without fractions or exponents.
// If [StringifyNumbers] is specified, then the JSON number is
// encoded within a JSON string. It does not support any custom format flags.
// If [StringifyNumbers] is specified or encoding a JSON object name,
// then the JSON number is encoded within a JSON string.
// It does not support any custom format flags.
//
// - A Go float is encoded as a JSON number.
// If [StringifyNumbers] is specified,
// If [StringifyNumbers] is specified or encoding a JSON object name,
// then the JSON number is encoded within a JSON string.
// If the format is "nonfinite", then NaN, +Inf, and -Inf are encoded as
// the JSON strings "NaN", "Infinity", and "-Infinity", respectively.
@@ -103,10 +109,8 @@ func putStructOptions(o *jsonopts.Struct) {
// - A Go map is encoded as a JSON object, where each Go map key and value
// is recursively encoded as a name and value pair in the JSON object.
// The Go map key must encode as a JSON string, otherwise this results
// in a [SemanticError]. When encoding keys, [StringifyNumbers]
// is automatically applied so that numeric keys encode as JSON strings.
// The Go map is traversed in a non-deterministic order.
// For deterministic encoding, consider using [jsontext.Value.Canonicalize].
// in a [SemanticError]. The Go map is traversed in a non-deterministic order.
// For deterministic encoding, consider using the [Deterministic] option.
// If the format is "emitnull", then a nil map is encoded as a JSON null.
// If the format is "emitempty", then a nil map is encoded as an empty JSON object,
// regardless of whether [FormatNilMapAsNull] is specified.
@@ -151,8 +155,6 @@ func putStructOptions(o *jsonopts.Struct) {
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is encoded as a JSON number of the number of seconds
// (or milliseconds, microseconds, or nanoseconds) in the duration.
// If the format is "base60", it is encoded as a JSON string
// using the "H:MM:SS.SSSSSSSSS" representation.
// If the format is "units", it uses [time.Duration.String].
//
// - All other Go types (e.g., complex numbers, channels, and functions)
@@ -166,6 +168,9 @@ func Marshal(in any, opts ...Options) (out []byte, err error) {
xe := export.Encoder(enc)
xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
err = marshalEncode(enc, in, &xe.Struct)
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return nil, internal.TransformMarshalError(in, err)
}
return bytes.Clone(xe.Buf), err
}
@@ -178,21 +183,33 @@ func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
defer export.PutStreamingEncoder(enc)
xe := export.Encoder(enc)
xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
return marshalEncode(enc, in, &xe.Struct)
err = marshalEncode(enc, in, &xe.Struct)
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformMarshalError(in, err)
}
return err
}
// MarshalEncode serializes a Go value into an [jsontext.Encoder] according to
// the provided marshal options (while ignoring unmarshal, encode, or decode options).
// Any marshal-relevant options already specified on the [jsontext.Encoder]
// take lower precedence than the set of options provided by the caller.
// Unlike [Marshal] and [MarshalWrite], encode options are ignored because
// they must have already been specified on the provided [jsontext.Encoder].
//
// See [Marshal] for details about the conversion of a Go value into JSON.
func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
mo := getStructOptions()
defer putStructOptions(mo)
mo.Join(opts...)
xe := export.Encoder(out)
mo.CopyCoderOptions(&xe.Struct)
return marshalEncode(out, in, mo)
if len(opts) > 0 {
optsOriginal := xe.Struct
defer func() { xe.Struct = optsOriginal }()
xe.Struct.JoinWithoutCoderOptions(opts...)
}
err = marshalEncode(out, in, &xe.Struct)
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformMarshalError(in, err)
}
return err
}
func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err error) {
@@ -202,12 +219,13 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
}
// Shallow copy non-pointer values to obtain an addressable value.
// It is beneficial to performance to always pass pointers to avoid this.
if v.Kind() != reflect.Pointer {
forceAddr := v.Kind() != reflect.Pointer
if forceAddr {
v2 := reflect.New(v.Type())
v2.Elem().Set(v)
v = v2
}
va := addressableValue{v.Elem()} // dereferenced pointer is always addressable
va := addressableValue{v.Elem(), forceAddr} // dereferenced pointer is always addressable
t := va.Type()
// Lookup and call the marshal function for this type.
@@ -216,9 +234,8 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t)
}
if err := marshal(out, va, mo); err != nil {
xe := export.Encoder(out)
if !xe.Flags.Get(jsonflags.AllowDuplicateNames) {
xe.Tokens.InvalidateDisabledNamespaces()
if !mo.Flags.Get(jsonflags.AllowDuplicateNames) {
export.Encoder(out).Tokens.InvalidateDisabledNamespaces()
}
return err
}
@@ -245,10 +262,10 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// value. If all applicable functions return [SkipFunc],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [UnmarshalerV2],
// then the UnmarshalJSONV2 method is called to decode the JSON value.
// - If the value type implements [UnmarshalerFrom],
// then the UnmarshalJSONFrom method is called to decode the JSON value.
//
// - If the value type implements [UnmarshalerV1],
// - If the value type implements [Unmarshaler],
// then the UnmarshalJSON method is called to decode the JSON value.
//
// - If the value type implements [encoding.TextUnmarshaler],
@@ -293,26 +310,24 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// otherwise it fails with a [SemanticError].
//
// - A Go integer is decoded from a JSON number.
// It may also be decoded from a JSON string containing a JSON number
// if [StringifyNumbers] is specified.
// It must be decoded from a JSON string containing a JSON number
// if [StringifyNumbers] is specified or decoding a JSON object name.
// It fails with a [SemanticError] if the JSON number
// has a fractional or exponent component.
// It also fails if it overflows the representation of the Go integer type.
// It does not support any custom format flags.
//
// - A Go float is decoded from a JSON number.
// It may also be decoded from a JSON string containing a JSON number
// if [StringifyNumbers] is specified.
// The JSON number is parsed as the closest representable Go float value.
// It must be decoded from a JSON string containing a JSON number
// if [StringifyNumbers] is specified or decoding a JSON object name.
// It fails if it overflows the representation of the Go float type.
// If the format is "nonfinite", then the JSON strings
// "NaN", "Infinity", and "-Infinity" are decoded as NaN, +Inf, and -Inf.
// Otherwise, the presence of such strings results in a [SemanticError].
//
// - A Go map is decoded from a JSON object,
// where each JSON object name and value pair is recursively decoded
// as the Go map key and value. When decoding keys,
// [StringifyNumbers] is automatically applied so that
// numeric keys can decode from JSON strings. Maps are not cleared.
// as the Go map key and value. Maps are not cleared.
// If the Go map is nil, then a new map is allocated to decode into.
// If the decoded key matches an existing Go map entry, the entry value
// is reused by decoding the JSON object value into it.
@@ -368,8 +383,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is decoded from a JSON number of the number of seconds
// (or milliseconds, microseconds, or nanoseconds) in the duration.
// If the format is "base60", it is decoded from a JSON string
// using the "H:MM:SS.SSSSSSSSS" representation.
// If the format is "units", it uses [time.ParseDuration].
//
// - All other Go types (e.g., complex numbers, channels, and functions)
@@ -384,7 +397,11 @@ func Unmarshal(in []byte, out any, opts ...Options) (err error) {
dec := export.GetBufferedDecoder(in, opts...)
defer export.PutBufferedDecoder(dec)
xd := export.Decoder(dec)
return unmarshalFull(dec, out, &xd.Struct)
err = unmarshalFull(dec, out, &xd.Struct)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err)
}
return err
}
// UnmarshalRead deserializes a Go value from an [io.Reader] according to the
@@ -397,7 +414,11 @@ func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
dec := export.GetStreamingDecoder(in, opts...)
defer export.PutStreamingDecoder(dec)
xd := export.Decoder(dec)
return unmarshalFull(dec, out, &xd.Struct)
err = unmarshalFull(dec, out, &xd.Struct)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err)
}
return err
}
func unmarshalFull(in *jsontext.Decoder, out any, uo *jsonopts.Struct) error {
@@ -413,46 +434,53 @@ func unmarshalFull(in *jsontext.Decoder, out any, uo *jsonopts.Struct) error {
// UnmarshalDecode deserializes a Go value from a [jsontext.Decoder] according to
// the provided unmarshal options (while ignoring marshal, encode, or decode options).
// Any unmarshal options already specified on the [jsontext.Decoder]
// take lower precedence than the set of options provided by the caller.
// Unlike [Unmarshal] and [UnmarshalRead], decode options are ignored because
// they must have already been specified on the provided [jsontext.Decoder].
//
// The input may be a stream of one or more JSON values,
// where this only unmarshals the next JSON value in the stream.
// The output must be a non-nil pointer.
// See [Unmarshal] for details about the conversion of JSON into a Go value.
func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error) {
uo := getStructOptions()
defer putStructOptions(uo)
uo.Join(opts...)
xd := export.Decoder(in)
uo.CopyCoderOptions(&xd.Struct)
return unmarshalDecode(in, out, uo)
if len(opts) > 0 {
optsOriginal := xd.Struct
defer func() { xd.Struct = optsOriginal }()
xd.Struct.JoinWithoutCoderOptions(opts...)
}
err = unmarshalDecode(in, out, &xd.Struct)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.TransformUnmarshalError(out, err)
}
return err
}
func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err error) {
v := reflect.ValueOf(out)
if !v.IsValid() || v.Kind() != reflect.Pointer || v.IsNil() {
var t reflect.Type
if v.IsValid() {
t = v.Type()
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
}
err := errors.New("value must be passed as a non-nil pointer reference")
return &SemanticError{action: "unmarshal", GoType: t, Err: err}
if v.Kind() != reflect.Pointer || v.IsNil() {
return &SemanticError{action: "unmarshal", GoType: reflect.TypeOf(out), Err: internal.ErrNonNilReference}
}
va := addressableValue{v.Elem()} // dereferenced pointer is always addressable
va := addressableValue{v.Elem(), false} // dereferenced pointer is always addressable
t := va.Type()
// In legacy semantics, the entirety of the next JSON value
// was validated before attempting to unmarshal it.
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err := export.Decoder(in).CheckNextValue(); err != nil {
return err
}
}
// Lookup and call the unmarshal function for this type.
unmarshal := lookupArshaler(t).unmarshal
if uo.Unmarshalers != nil {
unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t)
}
if err := unmarshal(in, va, uo); err != nil {
xd := export.Decoder(in)
if !xd.Flags.Get(jsonflags.AllowDuplicateNames) {
xd.Tokens.InvalidateDisabledNamespaces()
if !uo.Flags.Get(jsonflags.AllowDuplicateNames) {
export.Decoder(in).Tokens.InvalidateDisabledNamespaces()
}
return err
}
@@ -465,13 +493,23 @@ func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err er
// There is no compile magic that enforces this property,
// but rather the need to construct this type makes it easier to examine each
// construction site to ensure that this property is upheld.
type addressableValue struct{ reflect.Value }
type addressableValue struct {
reflect.Value
// forcedAddr reports whether this value is addressable
// only through the use of [newAddressableValue].
// This is only used for [jsonflags.CallMethodsWithLegacySemantics].
forcedAddr bool
}
// newAddressableValue constructs a new addressable value of type t.
func newAddressableValue(t reflect.Type) addressableValue {
return addressableValue{reflect.New(t).Elem()}
return addressableValue{reflect.New(t).Elem(), true}
}
// TODO: Remove *jsonopts.Struct argument from [marshaler] and [unmarshaler].
// This can be directly accessed on the encoder or decoder.
// All marshal and unmarshal behavior is implemented using these signatures.
// The *jsonopts.Struct argument is guaranteed to identical to or at least
// a strict super-set of the options in Encoder.Struct or Decoder.Struct.
@@ -525,7 +563,6 @@ func putStrings(s *stringSlice) {
stringsPools.Put(s)
}
// Sort sorts the string slice according to RFC 8785, section 3.2.3.
func (ss *stringSlice) Sort() {
slices.SortFunc(*ss, func(x, y string) int { return jsonwire.CompareUTF16(x, y) })
slices.SortFunc(*ss, func(x, y string) int { return strings.Compare(x, y) })
}

View File

@@ -5,9 +5,11 @@
package json
import (
"cmp"
"reflect"
"strconv"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/internal/jsonwire"
@@ -19,6 +21,9 @@ import (
// no knowledge of the JSON schema. This is a common enough occurrence
// to justify the complexity of adding logic for this.
// marshalValueAny marshals a Go any as a JSON value.
// This assumes that there are no special formatting directives
// for any possible nested value.
func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error {
switch val := val.(type) {
case nil:
@@ -44,6 +49,10 @@ func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error
}
}
// unmarshalValueAny unmarshals a JSON value as a Go any.
// This assumes that there are no special formatting directives
// for any possible nested value.
// Duplicate names must be rejected since this does not implement merging.
func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error) {
switch k := dec.PeekKind(); k {
case '{':
@@ -71,9 +80,12 @@ func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error)
}
return makeString(xd.StringCache, val), nil
case '0':
if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) {
return internal.RawNumberOf(val), nil
}
fv, ok := jsonwire.ParseFloat(val, 64)
if !ok && uo.Flags.Get(jsonflags.RejectFloatOverflow) {
return nil, &SemanticError{action: "unmarshal", JSONKind: k, GoType: float64Type, Err: strconv.ErrRange}
if !ok {
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange)
}
return fv, nil
default:
@@ -82,13 +94,15 @@ func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error)
}
}
// marshalObjectAny marshals a Go map[string]any as a JSON object
// (or as a JSON null if nil and [jsonflags.FormatNilMapAsNull]).
func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.Struct) error {
// Check for cycles.
xe := export.Encoder(enc)
if xe.Tokens.Depth() > startDetectingCyclesAfter {
v := reflect.ValueOf(obj)
if err := visitPointer(&xe.SeenPointers, v); err != nil {
return err
return newMarshalErrorBefore(enc, anyType, err)
}
defer leavePointer(&xe.SeenPointers, v)
}
@@ -99,7 +113,7 @@ func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.St
return enc.WriteToken(jsontext.Null)
}
// Optimize for marshaling an empty map without any preceding whitespace.
if !xe.Flags.Get(jsonflags.Expand) && !xe.Tokens.Last.NeedObjectName() {
if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() {
xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '{'), "{}"...)
xe.Tokens.Last.Increment()
if xe.NeedFlush() {
@@ -109,12 +123,12 @@ func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.St
}
}
if err := enc.WriteToken(jsontext.ObjectStart); err != nil {
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
return err
}
// A Go map guarantees that each entry has a unique key
// The only possibility of duplicates is due to invalid UTF-8.
if !xe.Flags.Get(jsonflags.AllowInvalidUTF8) {
if !mo.Flags.Get(jsonflags.AllowInvalidUTF8) {
xe.Tokens.Last.DisableNamespace()
}
if !mo.Flags.Get(jsonflags.Deterministic) || len(obj) <= 1 {
@@ -144,64 +158,67 @@ func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.St
}
putStrings(names)
}
if err := enc.WriteToken(jsontext.ObjectEnd); err != nil {
if err := enc.WriteToken(jsontext.EndObject); err != nil {
return err
}
return nil
}
// unmarshalObjectAny unmarshals a JSON object as a Go map[string]any.
// It panics if not decoding a JSON object.
func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]any, error) {
tok, err := dec.ReadToken()
if err != nil {
switch tok, err := dec.ReadToken(); {
case err != nil:
return nil, err
case tok.Kind() != '{':
panic("BUG: invalid kind: " + tok.Kind().String())
}
k := tok.Kind()
switch k {
case 'n':
return nil, nil
case '{':
xd := export.Decoder(dec)
obj := make(map[string]any)
// A Go map guarantees that each entry has a unique key
// The only possibility of duplicates is due to invalid UTF-8.
if !xd.Flags.Get(jsonflags.AllowInvalidUTF8) {
xd.Tokens.Last.DisableNamespace()
}
for dec.PeekKind() != '}' {
tok, err := dec.ReadToken()
if err != nil {
return obj, err
}
name := tok.String()
// Manually check for duplicate names.
if _, ok := obj[name]; ok {
name := xd.PreviousBuffer()
err := export.NewDuplicateNameError(name, dec.InputOffset()-len64(name))
return obj, err
}
val, err := unmarshalValueAny(dec, uo)
obj[name] = val
if err != nil {
return obj, err
}
}
if _, err := dec.ReadToken(); err != nil {
obj := make(map[string]any)
// A Go map guarantees that each entry has a unique key
// The only possibility of duplicates is due to invalid UTF-8.
if !uo.Flags.Get(jsonflags.AllowInvalidUTF8) {
export.Decoder(dec).Tokens.Last.DisableNamespace()
}
var errUnmarshal error
for dec.PeekKind() != '}' {
tok, err := dec.ReadToken()
if err != nil {
return obj, err
}
return obj, nil
name := tok.String()
// Manually check for duplicate names.
if _, ok := obj[name]; ok {
// TODO: Unread the object name.
name := export.Decoder(dec).PreviousTokenOrValue()
err := newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(name))
return obj, err
}
val, err := unmarshalValueAny(dec, uo)
obj[name] = val
if err != nil {
if isFatalError(err, uo.Flags) {
return obj, err
}
errUnmarshal = cmp.Or(err, errUnmarshal)
}
}
return nil, &SemanticError{action: "unmarshal", JSONKind: k, GoType: mapStringAnyType}
if _, err := dec.ReadToken(); err != nil {
return obj, err
}
return obj, errUnmarshal
}
// marshalArrayAny marshals a Go []any as a JSON array
// (or as a JSON null if nil and [jsonflags.FormatNilSliceAsNull]).
func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) error {
// Check for cycles.
xe := export.Encoder(enc)
if xe.Tokens.Depth() > startDetectingCyclesAfter {
v := reflect.ValueOf(arr)
if err := visitPointer(&xe.SeenPointers, v); err != nil {
return err
return newMarshalErrorBefore(enc, sliceAnyType, err)
}
defer leavePointer(&xe.SeenPointers, v)
}
@@ -212,7 +229,7 @@ func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) erro
return enc.WriteToken(jsontext.Null)
}
// Optimize for marshaling an empty slice without any preceding whitespace.
if !xe.Flags.Get(jsonflags.Expand) && !xe.Tokens.Last.NeedObjectName() {
if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() {
xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '['), "[]"...)
xe.Tokens.Last.Increment()
if xe.NeedFlush() {
@@ -222,7 +239,7 @@ func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) erro
}
}
if err := enc.WriteToken(jsontext.ArrayStart); err != nil {
if err := enc.WriteToken(jsontext.BeginArray); err != nil {
return err
}
for _, val := range arr {
@@ -230,34 +247,35 @@ func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) erro
return err
}
}
if err := enc.WriteToken(jsontext.ArrayEnd); err != nil {
if err := enc.WriteToken(jsontext.EndArray); err != nil {
return err
}
return nil
}
// unmarshalArrayAny unmarshals a JSON array as a Go []any.
// It panics if not decoding a JSON array.
func unmarshalArrayAny(dec *jsontext.Decoder, uo *jsonopts.Struct) ([]any, error) {
tok, err := dec.ReadToken()
if err != nil {
switch tok, err := dec.ReadToken(); {
case err != nil:
return nil, err
case tok.Kind() != '[':
panic("BUG: invalid kind: " + tok.Kind().String())
}
k := tok.Kind()
switch k {
case 'n':
return nil, nil
case '[':
arr := []any{}
for dec.PeekKind() != ']' {
val, err := unmarshalValueAny(dec, uo)
arr = append(arr, val)
if err != nil {
arr := []any{}
var errUnmarshal error
for dec.PeekKind() != ']' {
val, err := unmarshalValueAny(dec, uo)
arr = append(arr, val)
if err != nil {
if isFatalError(err, uo.Flags) {
return arr, err
}
errUnmarshal = cmp.Or(errUnmarshal, err)
}
if _, err := dec.ReadToken(); err != nil {
return arr, err
}
return arr, nil
}
return nil, &SemanticError{action: "unmarshal", JSONKind: k, GoType: sliceAnyType}
if _, err := dec.ReadToken(); err != nil {
return arr, err
}
return arr, errUnmarshal
}

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,13 @@ import (
"reflect"
"sync"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/jsontext"
)
// SkipFunc may be returned by [MarshalFuncV2] and [UnmarshalFuncV2] functions.
// SkipFunc may be returned by [MarshalToFunc] and [UnmarshalFromFunc] functions.
//
// Any function that returns SkipFunc must not cause observable side effects
// on the provided [jsontext.Encoder] or [jsontext.Decoder].
@@ -24,6 +25,9 @@ import (
// [jsontext.Encoder.WriteToken] since such methods mutate the state.
var SkipFunc = errors.New("json: skip function")
var errSkipMutation = errors.New("must not read or write any tokens when skipping")
var errNonSingularValue = errors.New("must read or write exactly one value")
// Marshalers is a list of functions that may override the marshal behavior
// of specific types. Populate [WithMarshalers] to use it with
// [Marshal], [MarshalWrite], or [MarshalEncode].
@@ -31,7 +35,7 @@ var SkipFunc = errors.New("json: skip function")
// There are no exported fields or methods on Marshalers.
type Marshalers = typedMarshalers
// NewMarshalers constructs a flattened list of marshal functions.
// JoinMarshalers constructs a flattened list of marshal functions.
// If multiple functions in the list are applicable for a value of a given type,
// then those earlier in the list take precedence over those that come later.
// If a function returns [SkipFunc], then the next applicable function is called,
@@ -39,10 +43,10 @@ type Marshalers = typedMarshalers
//
// For example:
//
// m1 := NewMarshalers(f1, f2)
// m2 := NewMarshalers(f0, m1, f3) // equivalent to m3
// m3 := NewMarshalers(f0, f1, f2, f3) // equivalent to m2
func NewMarshalers(ms ...*Marshalers) *Marshalers {
// m1 := JoinMarshalers(f1, f2)
// m2 := JoinMarshalers(f0, m1, f3) // equivalent to m3
// m3 := JoinMarshalers(f0, f1, f2, f3) // equivalent to m2
func JoinMarshalers(ms ...*Marshalers) *Marshalers {
return newMarshalers(ms...)
}
@@ -53,7 +57,7 @@ func NewMarshalers(ms ...*Marshalers) *Marshalers {
// There are no exported fields or methods on Unmarshalers.
type Unmarshalers = typedUnmarshalers
// NewUnmarshalers constructs a flattened list of unmarshal functions.
// JoinUnmarshalers constructs a flattened list of unmarshal functions.
// If multiple functions in the list are applicable for a value of a given type,
// then those earlier in the list take precedence over those that come later.
// If a function returns [SkipFunc], then the next applicable function is called,
@@ -61,10 +65,10 @@ type Unmarshalers = typedUnmarshalers
//
// For example:
//
// u1 := NewUnmarshalers(f1, f2)
// u2 := NewUnmarshalers(f0, u1, f3) // equivalent to u3
// u3 := NewUnmarshalers(f0, f1, f2, f3) // equivalent to u2
func NewUnmarshalers(us ...*Unmarshalers) *Unmarshalers {
// u1 := JoinUnmarshalers(f1, f2)
// u2 := JoinUnmarshalers(f0, u1, f3) // equivalent to u3
// u3 := JoinUnmarshalers(f0, f1, f2, f3) // equivalent to u2
func JoinUnmarshalers(us ...*Unmarshalers) *Unmarshalers {
return newUnmarshalers(us...)
}
@@ -156,7 +160,7 @@ func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsono
return v.(func(*Coder, addressableValue, *jsonopts.Struct) error), true
}
// MarshalFuncV1 constructs a type-specific marshaler that
// MarshalFunc constructs a type-specific marshaler that
// specifies how to marshal values of type T.
// T can be any type except a named pointer.
// The function is always provided with a non-nil pointer value
@@ -165,8 +169,8 @@ func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsono
// The function must marshal exactly one JSON value.
// The value of T must not be retained outside the function call.
// It may not return [SkipFunc].
func MarshalFuncV1[T any](fn func(T) ([]byte, error)) *Marshalers {
t := reflect.TypeOf((*T)(nil)).Elem()
func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, true)
typFnc := typedMarshaler{
typ: t,
@@ -174,12 +178,20 @@ func MarshalFuncV1[T any](fn func(T) ([]byte, error)) *Marshalers {
val, err := fn(va.castTo(t).Interface().(T))
if err != nil {
err = wrapSkipFunc(err, "marshal function of type func(T) ([]byte, error)")
// TODO: Avoid wrapping semantic errors.
return &SemanticError{action: "marshal", GoType: t, Err: err}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped
}
err = newMarshalErrorBefore(enc, t, err)
return collapseSemanticErrors(err)
}
if err := enc.WriteValue(val); err != nil {
// TODO: Avoid wrapping semantic or I/O errors.
return &SemanticError{action: "marshal", JSONKind: jsontext.Value(val).Kind(), GoType: t, Err: err}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped
}
if isSyntacticError(err) {
err = newMarshalErrorBefore(enc, t, err)
}
return err
}
return nil
},
@@ -187,7 +199,7 @@ func MarshalFuncV1[T any](fn func(T) ([]byte, error)) *Marshalers {
return &Marshalers{fncVals: []typedMarshaler{typFnc}, fromAny: castableToFromAny(t)}
}
// MarshalFuncV2 constructs a type-specific marshaler that
// MarshalToFunc constructs a type-specific marshaler that
// specifies how to marshal values of type T.
// T can be any type except a named pointer.
// The function is always provided with a non-nil pointer value
@@ -197,10 +209,10 @@ func MarshalFuncV1[T any](fn func(T) ([]byte, error)) *Marshalers {
// on the provided encoder. It may return [SkipFunc] such that marshaling can
// move on to the next marshal function. However, no mutable method calls may
// be called on the encoder if [SkipFunc] is returned.
// The pointer to [jsontext.Encoder], the value of T, and the [Options] value
// The pointer to [jsontext.Encoder] and the value of T
// must not be retained outside the function call.
func MarshalFuncV2[T any](fn func(*jsontext.Encoder, T, Options) error) *Marshalers {
t := reflect.TypeOf((*T)(nil)).Elem()
func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, true)
typFnc := typedMarshaler{
typ: t,
@@ -208,21 +220,26 @@ func MarshalFuncV2[T any](fn func(*jsontext.Encoder, T, Options) error) *Marshal
xe := export.Encoder(enc)
prevDepth, prevLength := xe.Tokens.DepthLength()
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
err := fn(enc, va.castTo(t).Interface().(T), mo)
err := fn(enc, va.castTo(t).Interface().(T))
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xe.Tokens.DepthLength()
if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) {
err = errors.New("must write exactly one JSON value")
err = errNonSingularValue
}
if err != nil {
if err == SkipFunc {
if prevDepth == currDepth && prevLength == currLength {
return SkipFunc
}
err = errors.New("must not write any JSON tokens when skipping")
err = errSkipMutation
}
// TODO: Avoid wrapping semantic or I/O errors.
return &SemanticError{action: "marshal", GoType: t, Err: err}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalToFunc") // unlike unmarshal, always wrapped
}
if !export.IsIOError(err) {
err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err)
}
return err
}
return nil
},
@@ -231,7 +248,7 @@ func MarshalFuncV2[T any](fn func(*jsontext.Encoder, T, Options) error) *Marshal
return &Marshalers{fncVals: []typedMarshaler{typFnc}, fromAny: castableToFromAny(t)}
}
// UnmarshalFuncV1 constructs a type-specific unmarshaler that
// UnmarshalFunc constructs a type-specific unmarshaler that
// specifies how to unmarshal values of type T.
// T must be an unnamed pointer or an interface type.
// The function is always provided with a non-nil pointer value.
@@ -240,8 +257,8 @@ func MarshalFuncV2[T any](fn func(*jsontext.Encoder, T, Options) error) *Marshal
// The input []byte must not be mutated.
// The input []byte and value T must not be retained outside the function call.
// It may not return [SkipFunc].
func UnmarshalFuncV1[T any](fn func([]byte, T) error) *Unmarshalers {
t := reflect.TypeOf((*T)(nil)).Elem()
func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, false)
typFnc := typedUnmarshaler{
typ: t,
@@ -253,8 +270,11 @@ func UnmarshalFuncV1[T any](fn func([]byte, T) error) *Unmarshalers {
err = fn(val, va.castTo(t).Interface().(T))
if err != nil {
err = wrapSkipFunc(err, "unmarshal function of type func([]byte, T) error")
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
return &SemanticError{action: "unmarshal", JSONKind: val.Kind(), GoType: t, Err: err}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
err = newUnmarshalErrorAfter(dec, t, err)
return collapseSemanticErrors(err)
}
return nil
},
@@ -262,7 +282,7 @@ func UnmarshalFuncV1[T any](fn func([]byte, T) error) *Unmarshalers {
return &Unmarshalers{fncVals: []typedUnmarshaler{typFnc}, fromAny: castableToFromAny(t)}
}
// UnmarshalFuncV2 constructs a type-specific unmarshaler that
// UnmarshalFromFunc constructs a type-specific unmarshaler that
// specifies how to unmarshal values of type T.
// T must be an unnamed pointer or an interface type.
// The function is always provided with a non-nil pointer value.
@@ -271,10 +291,10 @@ func UnmarshalFuncV1[T any](fn func([]byte, T) error) *Unmarshalers {
// on the provided decoder. It may return [SkipFunc] such that unmarshaling can
// move on to the next unmarshal function. However, no mutable method calls may
// be called on the decoder if [SkipFunc] is returned.
// The pointer to [jsontext.Decoder], the value of T, and [Options] value
// The pointer to [jsontext.Decoder] and the value of T
// must not be retained outside the function call.
func UnmarshalFuncV2[T any](fn func(*jsontext.Decoder, T, Options) error) *Unmarshalers {
t := reflect.TypeOf((*T)(nil)).Elem()
func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, false)
typFnc := typedUnmarshaler{
typ: t,
@@ -282,21 +302,29 @@ func UnmarshalFuncV2[T any](fn func(*jsontext.Decoder, T, Options) error) *Unmar
xd := export.Decoder(dec)
prevDepth, prevLength := xd.Tokens.DepthLength()
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
err := fn(dec, va.castTo(t).Interface().(T), uo)
err := fn(dec, va.castTo(t).Interface().(T))
xd.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xd.Tokens.DepthLength()
if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) {
err = errors.New("must read exactly one JSON value")
err = errNonSingularValue
}
if err != nil {
if err == SkipFunc {
if prevDepth == currDepth && prevLength == currLength {
return SkipFunc
}
err = errors.New("must not read any JSON tokens when skipping")
err = errSkipMutation
}
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
return &SemanticError{action: "unmarshal", GoType: t, Err: err}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil {
return err2
}
return err // unlike marshal, never wrapped
}
if !isSyntacticError(err) && !export.IsIOError(err) {
err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err)
}
return err
}
return nil
},

View File

@@ -29,13 +29,15 @@ import (
// represent any arbitrary JSON object member. Explicitly named fields take
// precedence over the inlined fallback. Only one inlined fallback is allowed.
var jsontextValueType = reflect.TypeOf((*jsontext.Value)(nil)).Elem()
var errRawInlinedNotObject = errors.New("inlined raw value must be a JSON object")
var jsontextValueType = reflect.TypeFor[jsontext.Value]()
// marshalInlinedFallbackAll marshals all the members in an inlined fallback.
func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct, f *structField, insertUnquotedName func([]byte) bool) error {
v := addressableValue{va.Field(f.index[0])} // addressable if struct value is addressable
if len(f.index) > 1 {
v = v.fieldByIndex(f.index[1:], false)
v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable
if len(f.index) > 0 {
v = v.fieldByIndex(f.index, false)
if !v.IsValid() {
return nil // implies a nil inlined field
}
@@ -62,23 +64,22 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return &SemanticError{action: "marshal", GoType: jsontextValueType, Err: err}
return newMarshalErrorBefore(enc, v.Type(), err)
}
if tok.Kind() != '{' {
err := errors.New("inlined raw value must be a JSON object")
return &SemanticError{action: "marshal", JSONKind: tok.Kind(), GoType: jsontextValueType, Err: err}
return newMarshalErrorBefore(enc, v.Type(), errRawInlinedNotObject)
}
for dec.PeekKind() != '}' {
// Parse the JSON object name.
var flags jsonwire.ValueFlags
val, err := xd.ReadValue(&flags)
if err != nil {
return &SemanticError{action: "marshal", GoType: jsontextValueType, Err: err}
return newMarshalErrorBefore(enc, v.Type(), err)
}
if insertUnquotedName != nil {
name := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if !insertUnquotedName(name) {
return export.NewDuplicateNameError(val, 0)
return newDuplicateNameError(enc.StackPointer().Parent(), val, enc.OutputOffset())
}
}
if err := enc.WriteValue(val); err != nil {
@@ -88,38 +89,37 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
// Parse the JSON object value.
val, err = xd.ReadValue(&flags)
if err != nil {
return &SemanticError{action: "marshal", GoType: jsontextValueType, Err: err}
return newMarshalErrorBefore(enc, v.Type(), err)
}
if err := enc.WriteValue(val); err != nil {
return err
}
}
if _, err := dec.ReadToken(); err != nil {
return &SemanticError{action: "marshal", GoType: jsontextValueType, Err: err}
return newMarshalErrorBefore(enc, v.Type(), err)
}
if err := xd.CheckEOF(); err != nil {
return &SemanticError{action: "marshal", GoType: jsontextValueType, Err: err}
return newMarshalErrorBefore(enc, v.Type(), err)
}
return nil
} else {
m := v // must be a map[string]V
m := v // must be a map[~string]V
n := m.Len()
if n == 0 {
return nil
}
mk := newAddressableValue(stringType)
mk := newAddressableValue(m.Type().Key())
mv := newAddressableValue(m.Type().Elem())
marshalKey := func(mk addressableValue) error {
xe := export.Encoder(enc)
b, err := jsonwire.AppendQuote(enc.UnusedBuffer(), mk.String(), &xe.Flags)
b, err := jsonwire.AppendQuote(enc.UnusedBuffer(), mk.String(), &mo.Flags)
if err != nil {
return err
return newMarshalErrorBefore(enc, m.Type().Key(), err)
}
if insertUnquotedName != nil {
isVerbatim := bytes.IndexByte(b, '\\') < 0
name := jsonwire.UnquoteMayCopy(b, isVerbatim)
if !insertUnquotedName(name) {
return export.NewDuplicateNameError(b, 0)
return newDuplicateNameError(enc.StackPointer().Parent(), b, enc.OutputOffset())
}
}
return enc.WriteValue(b)
@@ -165,9 +165,9 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
// unmarshalInlinedFallbackNext unmarshals only the next member in an inlined fallback.
func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct, f *structField, quotedName, unquotedName []byte) error {
v := addressableValue{va.Field(f.index[0])} // addressable if struct value is addressable
if len(f.index) > 1 {
v = v.fieldByIndex(f.index[1:], true)
v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable
if len(f.index) > 0 {
v = v.fieldByIndex(f.index, true)
}
v = v.indirect(true)
@@ -186,8 +186,7 @@ func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo
*b = append(*b, ',')
}
} else {
err := errors.New("inlined raw value must be a JSON object")
return &SemanticError{action: "unmarshal", GoType: jsontextValueType, Err: err}
return newUnmarshalErrorAfterWithSkipping(dec, uo, v.Type(), errRawInlinedNotObject)
}
}
*b = append(*b, quotedName...)
@@ -202,12 +201,15 @@ func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo
} else {
name := string(unquotedName) // TODO: Intern this?
m := v // must be a map[string]V
m := v // must be a map[~string]V
if m.IsNil() {
m.Set(reflect.MakeMap(m.Type()))
}
mk := reflect.ValueOf(name)
mv := newAddressableValue(v.Type().Elem()) // TODO: Cache across calls?
if mkt := m.Type().Key(); mkt != stringType {
mk = mk.Convert(mkt)
}
mv := newAddressableValue(m.Type().Elem()) // TODO: Cache across calls?
if v2 := m.MapIndex(mk); v2.IsValid() {
mv.Set(v2)
}

View File

@@ -9,62 +9,58 @@ import (
"errors"
"reflect"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/internal/jsonwire"
"github.com/go-json-experiment/json/jsontext"
)
var errNonStringValue = errors.New("JSON value must be string type")
// Interfaces for custom serialization.
var (
jsonMarshalerV1Type = reflect.TypeOf((*MarshalerV1)(nil)).Elem()
jsonMarshalerV2Type = reflect.TypeOf((*MarshalerV2)(nil)).Elem()
jsonUnmarshalerV1Type = reflect.TypeOf((*UnmarshalerV1)(nil)).Elem()
jsonUnmarshalerV2Type = reflect.TypeOf((*UnmarshalerV2)(nil)).Elem()
textAppenderType = reflect.TypeOf((*encodingTextAppender)(nil)).Elem()
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
jsonMarshalerType = reflect.TypeFor[Marshaler]()
jsonMarshalerToType = reflect.TypeFor[MarshalerTo]()
jsonUnmarshalerType = reflect.TypeFor[Unmarshaler]()
jsonUnmarshalerFromType = reflect.TypeFor[UnmarshalerFrom]()
textAppenderType = reflect.TypeFor[encoding.TextAppender]()
textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]()
textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]()
// TODO(https://go.dev/issue/62384): Use encoding.TextAppender instead of this hack.
// This exists for now to provide performance benefits to netip types.
// There is no semantic difference with this change.
appenderToType = reflect.TypeOf((*interface{ AppendTo([]byte) []byte })(nil)).Elem()
allMarshalerTypes = []reflect.Type{jsonMarshalerToType, jsonMarshalerType, textAppenderType, textMarshalerType}
allUnmarshalerTypes = []reflect.Type{jsonUnmarshalerFromType, jsonUnmarshalerType, textUnmarshalerType}
allMethodTypes = append(allMarshalerTypes, allUnmarshalerTypes...)
)
// TODO(https://go.dev/issue/62384): Use encoding.TextAppender instead
// and document public support for this method in json.Marshal.
type encodingTextAppender interface {
AppendText(b []byte) ([]byte, error)
}
// MarshalerV1 is implemented by types that can marshal themselves.
// It is recommended that types implement [MarshalerV2] unless the implementation
// Marshaler is implemented by types that can marshal themselves.
// It is recommended that types implement [MarshalerTo] unless the implementation
// is trying to avoid a hard dependency on the "jsontext" package.
//
// It is recommended that implementations return a buffer that is safe
// for the caller to retain and potentially mutate.
type MarshalerV1 interface {
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// MarshalerV2 is implemented by types that can marshal themselves.
// It is recommended that types implement MarshalerV2 instead of [MarshalerV1]
// MarshalerTo is implemented by types that can marshal themselves.
// It is recommended that types implement MarshalerTo instead of [Marshaler]
// since this is both more performant and flexible.
// If a type implements both MarshalerV1 and MarshalerV2,
// then MarshalerV2 takes precedence. In such a case, both implementations
// If a type implements both Marshaler and MarshalerTo,
// then MarshalerTo takes precedence. In such a case, both implementations
// should aim to have equivalent behavior for the default marshal options.
//
// The implementation must write only one JSON value to the Encoder and
// must not retain the pointer to [jsontext.Encoder] or the [Options] value.
type MarshalerV2 interface {
MarshalJSONV2(*jsontext.Encoder, Options) error
// must not retain the pointer to [jsontext.Encoder].
type MarshalerTo interface {
MarshalJSONTo(*jsontext.Encoder) error
// TODO: Should users call the MarshalEncode function or
// should/can they call this method directly? Does it matter?
}
// UnmarshalerV1 is implemented by types that can unmarshal themselves.
// It is recommended that types implement [UnmarshalerV2] unless the implementation
// Unmarshaler is implemented by types that can unmarshal themselves.
// It is recommended that types implement [UnmarshalerFrom] unless the implementation
// is trying to avoid a hard dependency on the "jsontext" package.
//
// The input can be assumed to be a valid encoding of a JSON value
@@ -74,25 +70,24 @@ type MarshalerV2 interface {
// unmarshaling into a pre-populated value.
//
// Implementations must not retain or mutate the input []byte.
type UnmarshalerV1 interface {
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
// UnmarshalerV2 is implemented by types that can unmarshal themselves.
// It is recommended that types implement UnmarshalerV2 instead of [UnmarshalerV1]
// UnmarshalerFrom is implemented by types that can unmarshal themselves.
// It is recommended that types implement UnmarshalerFrom instead of [Unmarshaler]
// since this is both more performant and flexible.
// If a type implements both UnmarshalerV1 and UnmarshalerV2,
// then UnmarshalerV2 takes precedence. In such a case, both implementations
// If a type implements both Unmarshaler and UnmarshalerFrom,
// then UnmarshalerFrom takes precedence. In such a case, both implementations
// should aim to have equivalent behavior for the default unmarshal options.
//
// The implementation must read only one JSON value from the Decoder.
// It is recommended that UnmarshalJSONV2 implement merge semantics when
// It is recommended that UnmarshalJSONFrom implement merge semantics when
// unmarshaling into a pre-populated value.
//
// Implementations must not retain the pointer to [jsontext.Decoder] or
// the [Options] value.
type UnmarshalerV2 interface {
UnmarshalJSONV2(*jsontext.Decoder, Options) error
// Implementations must not retain the pointer to [jsontext.Decoder].
type UnmarshalerFrom interface {
UnmarshalJSONFrom(*jsontext.Decoder) error
// TODO: Should users call the UnmarshalDecode function or
// should/can they call this method directly? Does it matter?
@@ -106,121 +101,118 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return fncs
}
// Handle custom marshaler.
switch which := implementsWhich(t, jsonMarshalerV2Type, jsonMarshalerV1Type, textAppenderType, textMarshalerType); which {
case jsonMarshalerV2Type:
if needAddr, ok := implements(t, textMarshalerType); ok {
fncs.nonDefault = true
prevMarshal := fncs.marshal
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
prevDepth, prevLength := xe.Tokens.DepthLength()
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
err := va.Addr().Interface().(MarshalerV2).MarshalJSONV2(enc, mo)
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xe.Tokens.DepthLength()
if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil {
err = errors.New("must write exactly one JSON value")
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
(needAddr && va.forcedAddr) {
return prevMarshal(enc, va, mo)
}
if err != nil {
err = wrapSkipFunc(err, "marshal method")
// TODO: Avoid wrapping semantic or I/O errors.
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
return nil
}
case jsonMarshalerV1Type:
fncs.nonDefault = true
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
marshaler := va.Addr().Interface().(MarshalerV1)
val, err := marshaler.MarshalJSON()
if err != nil {
err = wrapSkipFunc(err, "marshal method")
// TODO: Avoid wrapping semantic errors.
return &SemanticError{action: "marshal", GoType: t, Err: err}
}
if err := enc.WriteValue(val); err != nil {
// TODO: Avoid wrapping semantic or I/O errors.
return &SemanticError{action: "marshal", JSONKind: jsontext.Value(val).Kind(), GoType: t, Err: err}
}
return nil
}
case textAppenderType:
fncs.nonDefault = true
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) {
appender := va.Addr().Interface().(encodingTextAppender)
if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil {
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
err = wrapSkipFunc(err, "append method")
return &SemanticError{action: "marshal", JSONKind: '"', GoType: t, Err: err}
}
return nil
}
case textMarshalerType:
fncs.nonDefault = true
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
marshaler := va.Addr().Interface().(encoding.TextMarshaler)
if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) {
b2, err := marshaler.MarshalText()
return append(b, b2...), err
}); err != nil {
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
err = wrapSkipFunc(err, "marshal method")
return &SemanticError{action: "marshal", JSONKind: '"', GoType: t, Err: err}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped
}
if !isSemanticError(err) && !export.IsIOError(err) {
err = newMarshalErrorBefore(enc, t, err)
}
return err
}
return nil
}
// TODO(https://go.dev/issue/62384): Rely on encoding.TextAppender instead.
if implementsWhich(t, appenderToType) != nil && t.PkgPath() == "net/netip" {
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
appender := va.Addr().Interface().(interface{ AppendTo([]byte) []byte })
if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) {
return appender.AppendTo(b), nil
}); err != nil {
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
err = wrapSkipFunc(err, "append method")
return &SemanticError{action: "marshal", JSONKind: '"', GoType: t, Err: err}
}
return nil
}
}
}
// Handle custom unmarshaler.
switch which := implementsWhich(t, jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType); which {
case jsonUnmarshalerV2Type:
if needAddr, ok := implements(t, textAppenderType); ok {
fncs.nonDefault = true
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
prevDepth, prevLength := xd.Tokens.DepthLength()
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
err := va.Addr().Interface().(UnmarshalerV2).UnmarshalJSONV2(dec, uo)
xd.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xd.Tokens.DepthLength()
prevMarshal := fncs.marshal
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) {
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
(needAddr && va.forcedAddr) {
return prevMarshal(enc, va, mo)
}
appender := va.Addr().Interface().(encoding.TextAppender)
if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil {
err = wrapSkipFunc(err, "append method")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped
}
if !isSemanticError(err) && !export.IsIOError(err) {
err = newMarshalErrorBefore(enc, t, err)
}
return err
}
return nil
}
}
if needAddr, ok := implements(t, jsonMarshalerType); ok {
fncs.nonDefault = true
prevMarshal := fncs.marshal
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) {
return prevMarshal(enc, va, mo)
}
marshaler := va.Addr().Interface().(Marshaler)
val, err := marshaler.MarshalJSON()
if err != nil {
err = wrapSkipFunc(err, "marshal method")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
}
err = newMarshalErrorBefore(enc, t, err)
return collapseSemanticErrors(err)
}
if err := enc.WriteValue(val); err != nil {
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
}
if isSyntacticError(err) {
err = newMarshalErrorBefore(enc, t, err)
}
return err
}
return nil
}
}
if needAddr, ok := implements(t, jsonMarshalerToType); ok {
fncs.nonDefault = true
prevMarshal := fncs.marshal
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) {
return prevMarshal(enc, va, mo)
}
xe := export.Encoder(enc)
prevDepth, prevLength := xe.Tokens.DepthLength()
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
err := va.Addr().Interface().(MarshalerTo).MarshalJSONTo(enc)
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xe.Tokens.DepthLength()
if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil {
err = errors.New("must read exactly one JSON value")
err = errNonSingularValue
}
if err != nil {
err = wrapSkipFunc(err, "unmarshal method")
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
return &SemanticError{action: "unmarshal", GoType: t, Err: err}
err = wrapSkipFunc(err, "marshal method")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONTo") // unlike unmarshal, always wrapped
}
if !export.IsIOError(err) {
err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err)
}
return err
}
return nil
}
case jsonUnmarshalerV1Type:
fncs.nonDefault = true
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
val, err := dec.ReadValue()
if err != nil {
return err // must be a syntactic or I/O error
}
unmarshaler := va.Addr().Interface().(UnmarshalerV1)
if err := unmarshaler.UnmarshalJSON(val); err != nil {
err = wrapSkipFunc(err, "unmarshal method")
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
return &SemanticError{action: "unmarshal", JSONKind: val.Kind(), GoType: t, Err: err}
}
return nil
}
case textUnmarshalerType:
}
if _, ok := implements(t, textUnmarshalerType); ok {
fncs.nonDefault = true
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
@@ -229,16 +221,85 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
if err != nil {
return err // must be a syntactic or I/O error
}
if val.Kind() == 'n' {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetZero()
}
return nil
}
if val.Kind() != '"' {
err = errors.New("JSON value must be string type")
return &SemanticError{action: "unmarshal", JSONKind: val.Kind(), GoType: t, Err: err}
return newUnmarshalErrorAfter(dec, t, errNonStringValue)
}
s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler)
if err := unmarshaler.UnmarshalText(s); err != nil {
err = wrapSkipFunc(err, "unmarshal method")
// TODO: Avoid wrapping semantic, syntactic, or I/O errors.
return &SemanticError{action: "unmarshal", JSONKind: val.Kind(), GoType: t, Err: err}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
if !isSemanticError(err) && !isSyntacticError(err) && !export.IsIOError(err) {
err = newUnmarshalErrorAfter(dec, t, err)
}
return err
}
return nil
}
}
if _, ok := implements(t, jsonUnmarshalerType); ok {
fncs.nonDefault = true
prevUnmarshal := fncs.unmarshal
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
export.Decoder(dec).Tokens.Last.NeedObjectName() {
return prevUnmarshal(dec, va, uo)
}
val, err := dec.ReadValue()
if err != nil {
return err // must be a syntactic or I/O error
}
unmarshaler := va.Addr().Interface().(Unmarshaler)
if err := unmarshaler.UnmarshalJSON(val); err != nil {
err = wrapSkipFunc(err, "unmarshal method")
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
err = newUnmarshalErrorAfter(dec, t, err)
return collapseSemanticErrors(err)
}
return nil
}
}
if _, ok := implements(t, jsonUnmarshalerFromType); ok {
fncs.nonDefault = true
prevUnmarshal := fncs.unmarshal
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) &&
export.Decoder(dec).Tokens.Last.NeedObjectName() {
return prevUnmarshal(dec, va, uo)
}
xd := export.Decoder(dec)
prevDepth, prevLength := xd.Tokens.DepthLength()
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
err := va.Addr().Interface().(UnmarshalerFrom).UnmarshalJSONFrom(dec)
xd.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xd.Tokens.DepthLength()
if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil {
err = errNonSingularValue
}
if err != nil {
err = wrapSkipFunc(err, "unmarshal method")
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil {
return err2
}
return err // unlike marshal, never wrapped
}
if !isSyntacticError(err) && !export.IsIOError(err) {
err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err)
}
return err
}
return nil
}
@@ -247,13 +308,28 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return fncs
}
// implementsWhich is like t.Implements(ifaceType) for a list of interfaces,
// implementsAny is like t.Implements(ifaceType) for a list of interfaces,
// but checks whether either t or reflect.PointerTo(t) implements the interface.
func implementsWhich(t reflect.Type, ifaceTypes ...reflect.Type) (which reflect.Type) {
func implementsAny(t reflect.Type, ifaceTypes ...reflect.Type) bool {
for _, ifaceType := range ifaceTypes {
if t.Implements(ifaceType) || reflect.PointerTo(t).Implements(ifaceType) {
return ifaceType
if _, ok := implements(t, ifaceType); ok {
return true
}
}
return nil
return false
}
// implements is like t.Implements(ifaceType) but checks whether
// either t or reflect.PointerTo(t) implements the interface.
// It also reports whether the value needs to be addressed
// in order to satisfy the interface.
func implements(t, ifaceType reflect.Type) (needAddr, ok bool) {
switch {
case t.Implements(ifaceType):
return false, true
case reflect.PointerTo(t).Implements(ifaceType):
return true, true
default:
return false, false
}
}

View File

@@ -6,6 +6,7 @@ package json
import (
"bytes"
"cmp"
"errors"
"fmt"
"math"
@@ -15,6 +16,7 @@ import (
"strings"
"time"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/internal/jsonwire"
@@ -22,12 +24,12 @@ import (
)
var (
timeDurationType = reflect.TypeOf((*time.Duration)(nil)).Elem()
timeTimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
timeDurationType = reflect.TypeFor[time.Duration]()
timeTimeType = reflect.TypeFor[time.Time]()
)
func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
// Ideally, time types would implement MarshalerV2 and UnmarshalerV2,
// Ideally, time types would implement MarshalerTo and UnmarshalerFrom,
// but that would incur a dependency on package json from package time.
// Given how widely used time is, it is more acceptable that we incur a
// dependency on time from json.
@@ -44,17 +46,20 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
var m durationArshaler
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
if !m.initFormat(mo.Format) {
return newInvalidFormatError("marshal", t, mo.Format)
return newInvalidFormatError(enc, t, mo)
}
} else if mo.Flags.Get(jsonflags.FormatTimeDurationAsNanosecond) {
} else if mo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
return marshalNano(enc, va, mo)
}
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
m.td = *va.Addr().Interface().(*time.Duration)
k := stringOrNumberKind(!m.isNumeric() || mo.Flags.Get(jsonflags.StringifyNumbers))
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
if err := xe.AppendRaw(k, true, m.appendMarshal); err != nil {
return &SemanticError{action: "marshal", GoType: t, Err: err}
if !isSyntacticError(err) && !export.IsIOError(err) {
err = newMarshalErrorBefore(enc, t, err)
}
return err
}
return nil
}
@@ -64,12 +69,13 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
var u durationArshaler
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
if !u.initFormat(uo.Format) {
return newInvalidFormatError("unmarshal", t, uo.Format)
return newInvalidFormatError(dec, t, uo)
}
} else if uo.Flags.Get(jsonflags.FormatTimeDurationAsNanosecond) {
} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
return unmarshalNano(dec, va, uo)
}
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
var flags jsonwire.ValueFlags
td := va.Addr().Interface().(*time.Duration)
val, err := xd.ReadValue(&flags)
@@ -78,30 +84,31 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
}
switch k := val.Kind(); k {
case 'n':
*td = time.Duration(0)
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
*td = time.Duration(0)
}
return nil
case '"':
if u.isNumeric() && !uo.Flags.Get(jsonflags.StringifyNumbers) {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
if !stringify {
break
}
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if err := u.unmarshal(val); err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
return newUnmarshalErrorAfter(dec, t, err)
}
*td = u.td
return nil
case '0':
if !u.isNumeric() {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
if stringify {
break
}
if err := u.unmarshal(val); err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
return newUnmarshalErrorAfter(dec, t, err)
}
*td = u.td
return nil
default:
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
}
return newUnmarshalErrorAfter(dec, t, nil)
}
case timeTimeType:
fncs.nonDefault = true
@@ -110,15 +117,21 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
var m timeArshaler
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
if !m.initFormat(mo.Format) {
return newInvalidFormatError("marshal", t, mo.Format)
return newInvalidFormatError(enc, t, mo)
}
}
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
m.tt = *va.Addr().Interface().(*time.Time)
k := stringOrNumberKind(!m.isNumeric() || mo.Flags.Get(jsonflags.StringifyNumbers))
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
if err := xe.AppendRaw(k, !m.hasCustomFormat(), m.appendMarshal); err != nil {
return &SemanticError{action: "marshal", GoType: t, Err: err}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
}
if !isSyntacticError(err) && !export.IsIOError(err) {
err = newMarshalErrorBefore(enc, t, err)
}
return err
}
return nil
}
@@ -127,10 +140,13 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
var u timeArshaler
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
if !u.initFormat(uo.Format) {
return newInvalidFormatError("unmarshal", t, uo.Format)
return newInvalidFormatError(dec, t, uo)
}
} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
u.looseRFC3339 = true
}
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
var flags jsonwire.ValueFlags
tt := va.Addr().Interface().(*time.Time)
val, err := xd.ReadValue(&flags)
@@ -139,30 +155,37 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
}
switch k := val.Kind(); k {
case 'n':
*tt = time.Time{}
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
*tt = time.Time{}
}
return nil
case '"':
if u.isNumeric() && !uo.Flags.Get(jsonflags.StringifyNumbers) {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
if !stringify {
break
}
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if err := u.unmarshal(val); err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
return newUnmarshalErrorAfter(dec, t, err)
}
*tt = u.tt
return nil
case '0':
if !u.isNumeric() {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
if stringify {
break
}
if err := u.unmarshal(val); err != nil {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: err}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
return newUnmarshalErrorAfter(dec, t, err)
}
*tt = u.tt
return nil
default:
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t}
}
return newUnmarshalErrorAfter(dec, t, nil)
}
}
return fncs
@@ -175,8 +198,7 @@ type durationArshaler struct {
// - 0 uses time.Duration.String
// - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the duration as
// nanoseconds, microseconds, milliseconds, or seconds.
// - 60 uses a "H:MM:SS.SSSSSSSSS" encoding
base uint
base uint64
}
func (a *durationArshaler) initFormat(format string) (ok bool) {
@@ -191,8 +213,6 @@ func (a *durationArshaler) initFormat(format string) (ok bool) {
a.base = 1e3
case "nano":
a.base = 1e0
case "base60": // see https://en.wikipedia.org/wiki/Sexagesimal#Modern_usage
a.base = 60
default:
return false
}
@@ -207,8 +227,6 @@ func (a *durationArshaler) appendMarshal(b []byte) ([]byte, error) {
switch a.base {
case 0:
return append(b, a.td.String()...), nil
case 60:
return appendDurationBase60(b, a.td), nil
default:
return appendDurationBase10(b, a.td, a.base), nil
}
@@ -218,8 +236,6 @@ func (a *durationArshaler) unmarshal(b []byte) (err error) {
switch a.base {
case 0:
a.td, err = time.ParseDuration(string(b))
case 60:
a.td, err = parseDurationBase60(b)
default:
a.td, err = parseDurationBase10(b, a.base)
}
@@ -234,8 +250,10 @@ type timeArshaler struct {
// - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the timestamp as
// seconds, milliseconds, microseconds, or nanoseconds since Unix epoch.
// - math.MaxUint uses time.Time.Format to encode the timestamp
base uint
base uint64
format string // time format passed to time.Parse
looseRFC3339 bool
}
func (a *timeArshaler) initFormat(format string) bool {
@@ -317,11 +335,7 @@ func (a *timeArshaler) hasCustomFormat() bool {
func (a *timeArshaler) appendMarshal(b []byte) ([]byte, error) {
switch a.base {
case 0:
// TODO(https://go.dev/issue/60204): Use cmp.Or(a.format, time.RFC3339Nano).
format := a.format
if format == "" {
format = time.RFC3339Nano
}
format := cmp.Or(a.format, time.RFC3339Nano)
n0 := len(b)
b = a.tt.AppendFormat(b, format)
// Not all Go timestamps can be represented as valid RFC 3339.
@@ -360,6 +374,8 @@ func (a *timeArshaler) unmarshal(b []byte) (err error) {
return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message}
}
switch {
case a.looseRFC3339:
return nil
case b[len("2006-01-02T")+1] == ':': // hour must be two digits
return newParseError(time.RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), "")
case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
@@ -384,16 +400,16 @@ func (a *timeArshaler) unmarshal(b []byte) (err error) {
// appendDurationBase10 appends d formatted as a decimal fractional number,
// where pow10 is a power-of-10 used to scale down the number.
func appendDurationBase10(b []byte, d time.Duration, pow10 uint) []byte {
func appendDurationBase10(b []byte, d time.Duration, pow10 uint64) []byte {
b, n := mayAppendDurationSign(b, d) // append sign
whole, frac := bits.Div64(0, n, uint64(pow10)) // compute whole and frac fields
b = strconv.AppendUint(b, whole, 10) // append whole field
return appendFracBase10(b, uint(frac), pow10) // append frac field
return appendFracBase10(b, frac, pow10) // append frac field
}
// parseDurationBase10 parses d from a decimal fractional number,
// where pow10 is a power-of-10 used to scale up the number.
func parseDurationBase10(b []byte, pow10 uint) (time.Duration, error) {
func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) {
suffix, neg := consumeSign(b) // consume sign
wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow
@@ -410,45 +426,6 @@ func parseDurationBase10(b []byte, pow10 uint) (time.Duration, error) {
}
}
// appendDurationBase60 appends d formatted with H:MM:SS.SSS notation.
func appendDurationBase60(b []byte, d time.Duration) []byte {
b, n := mayAppendDurationSign(b, d) // append sign
n, nsec := bits.Div64(0, n, 1e9) // compute nsec field
n, sec := bits.Div64(0, n, 60) // compute sec field
hour, min := bits.Div64(0, n, 60) // compute hour and min fields
b = strconv.AppendUint(b, hour, 10) // append hour field
b = append(b, ':', '0'+byte(min/10), '0'+byte(min%10)) // append min field
b = append(b, ':', '0'+byte(sec/10), '0'+byte(sec%10)) // append sec field
return appendFracBase10(b, uint(nsec), 1e9) // append nsec field
}
// parseDurationBase60 parses d formatted with H:MM:SS.SSS notation.
// The exact grammar is `-?(0|[1-9][0-9]*):[0-5][0-9]:[0-5][0-9]([.][0-9]+)?`.
func parseDurationBase60(b []byte) (time.Duration, error) {
checkBase60 := func(b []byte) bool {
return len(b) == 2 && ('0' <= b[0] && b[0] <= '5') && '0' <= b[1] && b[1] <= '9'
}
suffix, neg := consumeSign(b) // consume sign
hourBytes, suffix := bytesCutByte(suffix, ':', false) // consume hour field
minBytes, suffix := bytesCutByte(suffix, ':', false) // consume min field
secBytes, nsecBytes := bytesCutByte(suffix, '.', true) // consume sec and nsec fields
hour, okHour := jsonwire.ParseUint(hourBytes) // parse hour field; may overflow
min := parseDec2(minBytes) // parse min field
sec := parseDec2(secBytes) // parse sec field
nsec, okNsec := parseFracBase10(nsecBytes, 1e9) // parse nsec field
n := uint64(min)*60*1e9 + uint64(sec)*1e9 + uint64(nsec) // cannot overflow
hi, lo := bits.Mul64(hour, 60*60*1e9) // overflow if hi > 0
sum, co := bits.Add64(lo, n, 0) // overflow if co > 0
switch d := mayApplyDurationSign(sum, neg); { // overflow if neg != (d < 0)
case (!okHour && hour != math.MaxUint64) || !checkBase60(minBytes) || !checkBase60(secBytes) || !okNsec:
return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrSyntax)
case !okHour || hi > 0 || co > 0 || neg != (d < 0):
return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrRange)
default:
return d, nil
}
}
// mayAppendDurationSign appends a negative sign if n is negative.
func mayAppendDurationSign(b []byte, d time.Duration) ([]byte, uint64) {
if d < 0 {
@@ -469,7 +446,7 @@ func mayApplyDurationSign(n uint64, neg bool) time.Duration {
// appendTimeUnix appends t formatted as a decimal fractional number,
// where pow10 is a power-of-10 used to scale up the number.
func appendTimeUnix(b []byte, t time.Time, pow10 uint) []byte {
func appendTimeUnix(b []byte, t time.Time, pow10 uint64) []byte {
sec, nsec := t.Unix(), int64(t.Nanosecond())
if sec < 0 {
b = append(b, '-')
@@ -478,20 +455,20 @@ func appendTimeUnix(b []byte, t time.Time, pow10 uint) []byte {
switch {
case pow10 == 1e0: // fast case where units is in seconds
b = strconv.AppendUint(b, uint64(sec), 10)
return appendFracBase10(b, uint(nsec), 1e9)
return appendFracBase10(b, uint64(nsec), 1e9)
case uint64(sec) < 1e9: // intermediate case where units is not seconds, but no overflow
b = strconv.AppendUint(b, uint64(sec)*uint64(pow10)+uint64(uint(nsec)/(1e9/pow10)), 10)
return appendFracBase10(b, (uint(nsec)*pow10)%1e9, 1e9)
b = strconv.AppendUint(b, uint64(sec)*uint64(pow10)+uint64(uint64(nsec)/(1e9/pow10)), 10)
return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9)
default: // slow case where units is not seconds and overflow would occur
b = strconv.AppendUint(b, uint64(sec), 10)
b = appendPaddedBase10(b, uint(uint(nsec)/(1e9/pow10)), pow10)
return appendFracBase10(b, (uint(nsec)*pow10)%1e9, 1e9)
b = appendPaddedBase10(b, uint64(nsec)/(1e9/pow10), pow10)
return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9)
}
}
// parseTimeUnix parses t formatted as a decimal fractional number,
// where pow10 is a power-of-10 used to scale down the number.
func parseTimeUnix(b []byte, pow10 uint) (time.Time, error) {
func parseTimeUnix(b []byte, pow10 uint64) (time.Time, error) {
suffix, neg := consumeSign(b) // consume sign
wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow
@@ -502,14 +479,14 @@ func parseTimeUnix(b []byte, pow10 uint) (time.Time, error) {
sec = int64(whole) // check overflow later after negation
nsec = int64(frac) // cannot overflow
case okWhole: // intermediate case where units is not seconds, but no overflow
sec = int64(whole / uint64(pow10)) // check overflow later after negation
nsec = int64((uint(whole)%pow10)*(1e9/pow10) + uint(frac)) // cannot overflow
sec = int64(whole / pow10) // check overflow later after negation
nsec = int64((whole%pow10)*(1e9/pow10) + frac) // cannot overflow
case !okWhole && whole == math.MaxUint64: // slow case where units is not seconds and overflow occurred
width := int(math.Log10(float64(pow10))) // compute len(strconv.Itoa(pow10-1))
whole, okWhole = jsonwire.ParseUint(wholeBytes[:len(wholeBytes)-width]) // parse the upper whole field
mid, _ := parsePaddedBase10(wholeBytes[len(wholeBytes)-width:], pow10) // parse the lower whole field
sec = int64(whole) // check overflow later after negation
nsec = int64(uint(mid)*(1e9/pow10) + frac) // cannot overflow
nsec = int64(mid*(1e9/pow10) + frac) // cannot overflow
}
if neg {
sec, nsec = negateSecNano(sec, nsec)
@@ -535,7 +512,7 @@ func negateSecNano(sec, nsec int64) (int64, int64) {
// appendFracBase10 appends the fraction of n/max10,
// where max10 is a power-of-10 that is larger than n.
func appendFracBase10(b []byte, n, max10 uint) []byte {
func appendFracBase10(b []byte, n, max10 uint64) []byte {
if n == 0 {
return b
}
@@ -544,7 +521,7 @@ func appendFracBase10(b []byte, n, max10 uint) []byte {
// parseFracBase10 parses the fraction of n/max10,
// where max10 is a power-of-10 that is larger than n.
func parseFracBase10(b []byte, max10 uint) (n uint, ok bool) {
func parseFracBase10(b []byte, max10 uint64) (n uint64, ok bool) {
switch {
case len(b) == 0:
return 0, true
@@ -556,31 +533,31 @@ func parseFracBase10(b []byte, max10 uint) (n uint, ok bool) {
// appendPaddedBase10 appends a zero-padded encoding of n,
// where max10 is a power-of-10 that is larger than n.
func appendPaddedBase10(b []byte, n, max10 uint) []byte {
func appendPaddedBase10(b []byte, n, max10 uint64) []byte {
if n < max10/10 {
// Formatting of n is shorter than log10(max10),
// so add max10/10 to ensure the length is equal to log10(max10).
i := len(b)
b = strconv.AppendUint(b, uint64(n+max10/10), 10)
b = strconv.AppendUint(b, n+max10/10, 10)
b[i]-- // subtract the addition of max10/10
return b
}
return strconv.AppendUint(b, uint64(n), 10)
return strconv.AppendUint(b, n, 10)
}
// parsePaddedBase10 parses b as the zero-padded encoding of n,
// where max10 is a power-of-10 that is larger than n.
// Truncated suffix is treated as implicit zeros.
// Extended suffix is ignored, but verified to contain only digits.
func parsePaddedBase10(b []byte, max10 uint) (n uint, ok bool) {
pow10 := uint(1)
func parsePaddedBase10(b []byte, max10 uint64) (n uint64, ok bool) {
pow10 := uint64(1)
for pow10 < max10 {
n *= 10
if len(b) > 0 {
if b[0] < '0' || '9' < b[0] {
return n, false
}
n += uint(b[0] - '0')
n += uint64(b[0] - '0')
b = b[1:]
}
pow10 *= 10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -30,10 +30,10 @@
// of how the JSON and Go type systems correspond.
//
// Arbitrary Go types can customize their JSON representation by implementing
// [MarshalerV1], [MarshalerV2], [UnmarshalerV1], or [UnmarshalerV2].
// [Marshaler], [MarshalerTo], [Unmarshaler], or [UnmarshalerFrom].
// This provides authors of Go types with control over how their types are
// serialized as JSON. Alternatively, users can implement functions that match
// [MarshalFuncV1], [MarshalFuncV2], [UnmarshalFuncV1], or [UnmarshalFuncV2]
// [MarshalFunc], [MarshalToFunc], [UnmarshalFunc], or [UnmarshalFromFunc]
// to specify the JSON representation for arbitrary types.
// This provides callers of JSON functionality with control over
// how any arbitrary type is serialized as JSON.
@@ -54,6 +54,8 @@
// "json" struct field tag, where the tag is a comma separated list of options.
// As a special case, if the entire tag is `json:"-"`,
// then the field is ignored with regard to its JSON representation.
// Some options also have equivalent behavior controlled by a caller-specified [Options].
// Field-specified options take precedence over caller-specified options.
//
// The first option is the JSON object name override for the Go struct field.
// If the name is not specified, then the Go struct field name
@@ -81,24 +83,20 @@
// - string: The "string" option specifies that [StringifyNumbers]
// be set when marshaling or unmarshaling a struct field value.
// This causes numeric types to be encoded as a JSON number
// within a JSON string, and to be decoded from either a JSON number or
// a JSON string containing a JSON number.
// within a JSON string, and to be decoded from a JSON string
// containing the JSON number without any surrounding whitespace.
// This extra level of encoding is often necessary since
// many JSON parsers cannot precisely represent 64-bit integers.
//
// - nocase: When unmarshaling, the "nocase" option specifies that
// if the JSON object name does not exactly match the JSON name
// for any of the struct fields, then it attempts to match the struct field
// using a case-insensitive match that also ignores dashes and underscores.
// If multiple fields match,
// - case: When unmarshaling, the "case" option specifies how
// JSON object names are matched with the JSON name for Go struct fields.
// The option is a key-value pair specified as "case:value" where
// the value must either be 'ignore' or 'strict'.
// The 'ignore' value specifies that matching is case-insensitive
// where dashes and underscores are also ignored. If multiple fields match,
// the first declared field in breadth-first order takes precedence.
// This takes precedence even if [MatchCaseInsensitiveNames] is set to false.
// This cannot be specified together with the "strictcase" option.
//
// - strictcase: When unmarshaling, the "strictcase" option specifies that the
// JSON object name must exactly match the JSON name for the struct field.
// This takes precedence even if [MatchCaseInsensitiveNames] is set to true.
// This cannot be specified together with the "nocase" option.
// The 'strict' value specifies that matching is case-sensitive.
// This takes precedence over the [MatchCaseInsensitiveNames] option.
//
// - inline: The "inline" option specifies that
// the JSON representable content of this field type is to be promoted
@@ -107,9 +105,9 @@
// A Go embedded field is implicitly inlined unless an explicit JSON name
// is specified. The inlined field must be a Go struct
// (that does not implement any JSON methods), [jsontext.Value],
// map[string]T, or an unnamed pointer to such types. When marshaling,
// map[~string]T, or an unnamed pointer to such types. When marshaling,
// inlined fields from a pointer type are omitted if it is nil.
// Inlined fields of type [jsontext.Value] and map[string]T are called
// Inlined fields of type [jsontext.Value] and map[~string]T are called
// “inlined fallbacks” as they can represent all possible
// JSON object members not directly handled by the parent struct.
// Only one inlined fallback field may be specified in a struct,
@@ -119,7 +117,7 @@
// - unknown: The "unknown" option is a specialized variant
// of the inlined fallback to indicate that this Go struct field
// contains any number of unknown JSON object members. The field type must
// be a [jsontext.Value], map[string]T, or an unnamed pointer to such types.
// be a [jsontext.Value], map[~string]T, or an unnamed pointer to such types.
// If [DiscardUnknownMembers] is specified when marshaling,
// the contents of this field are ignored.
// If [RejectUnknownMembers] is specified when unmarshaling,

View File

@@ -5,15 +5,58 @@
package json
import (
"cmp"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/internal/jsonwire"
"github.com/go-json-experiment/json/jsontext"
)
// ErrUnknownName indicates that a JSON object member could not be
// unmarshaled because the name is not known to the target Go struct.
// This error is directly wrapped within a [SemanticError] when produced.
//
// The name of an unknown JSON object member can be extracted as:
//
// err := ...
// var serr json.SemanticError
// if errors.As(err, &serr) && serr.Err == json.ErrUnknownName {
// ptr := serr.JSONPointer // JSON pointer to unknown name
// name := ptr.LastToken() // unknown name itself
// ...
// }
//
// This error is only returned if [RejectUnknownMembers] is true.
var ErrUnknownName = errors.New("unknown object member name")
const errorPrefix = "json: "
func isSemanticError(err error) bool {
_, ok := err.(*SemanticError)
return ok
}
func isSyntacticError(err error) bool {
_, ok := err.(*jsontext.SyntacticError)
return ok
}
// isFatalError reports whether this error must terminate asharling.
// All errors are considered fatal unless operating under
// [jsonflags.ReportErrorsWithLegacySemantics] in which case only
// syntactic errors and I/O errors are considered fatal.
func isFatalError(err error, flags jsonflags.Flags) bool {
return !flags.Get(jsonflags.ReportErrorsWithLegacySemantics) ||
isSyntacticError(err) || export.IsIOError(err)
}
// SemanticError describes an error determining the meaning
// of JSON data as Go data or vice-versa.
//
@@ -28,10 +71,13 @@ type SemanticError struct {
ByteOffset int64
// JSONPointer indicates that an error occurred within this JSON value
// as indicated using the JSON Pointer notation (see RFC 6901).
JSONPointer string
JSONPointer jsontext.Pointer
// JSONKind is the JSON kind that could not be handled.
JSONKind jsontext.Kind // may be zero if unknown
// JSONValue is the JSON number or string that could not be unmarshaled.
// It is not populated during marshaling.
JSONValue jsontext.Value // may be nil if irrelevant or unknown
// GoType is the Go type that could not be handled.
GoType reflect.Type // may be nil if unknown
@@ -39,18 +85,218 @@ type SemanticError struct {
Err error // may be nil
}
// coder is implemented by [jsontext.Encoder] or [jsontext.Decoder].
type coder interface{ StackPointer() jsontext.Pointer }
// newInvalidFormatError wraps err in a SemanticError because
// the current type t cannot handle the provided options format.
// This error must be called before producing or consuming the next value.
//
// If [jsonflags.ReportErrorsWithLegacySemantics] is specified,
// then this automatically skips the next value when unmarshaling
// to ensure that the value is fully consumed.
func newInvalidFormatError(c coder, t reflect.Type, o *jsonopts.Struct) error {
err := fmt.Errorf("invalid format flag %q", o.Format)
switch c := c.(type) {
case *jsontext.Encoder:
err = newMarshalErrorBefore(c, t, err)
case *jsontext.Decoder:
err = newUnmarshalErrorBeforeWithSkipping(c, o, t, err)
}
return err
}
// newMarshalErrorBefore wraps err in a SemanticError assuming that e
// is positioned right before the next token or value, which causes an error.
func newMarshalErrorBefore(e *jsontext.Encoder, t reflect.Type, err error) error {
return &SemanticError{action: "marshal", GoType: t, Err: err,
ByteOffset: e.OutputOffset() + int64(export.Encoder(e).CountNextDelimWhitespace()),
JSONPointer: jsontext.Pointer(export.Encoder(e).AppendStackPointer(nil, +1))}
}
// newUnmarshalErrorBefore wraps err in a SemanticError assuming that d
// is positioned right before the next token or value, which causes an error.
// It does not record the next JSON kind as this error is used to indicate
// the receiving Go value is invalid to unmarshal into (and not a JSON error).
func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) error {
return &SemanticError{action: "unmarshal", GoType: t, Err: err,
ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()),
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1))}
}
// newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore],
// but automatically skips the next value if
// [jsonflags.ReportErrorsWithLegacySemantics] is specified.
func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error {
err = newUnmarshalErrorBefore(d, t, err)
if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := export.Decoder(d).SkipValue(); err2 != nil {
return err2
}
}
return err
}
// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d
// is positioned right after the previous token or value, which caused an error.
func newUnmarshalErrorAfter(d *jsontext.Decoder, t reflect.Type, err error) error {
tokOrVal := export.Decoder(d).PreviousTokenOrValue()
return &SemanticError{action: "unmarshal", GoType: t, Err: err,
ByteOffset: d.InputOffset() - int64(len(tokOrVal)),
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, -1)),
JSONKind: jsontext.Value(tokOrVal).Kind()}
}
// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d
// is positioned right after the previous token or value, which caused an error.
// It also stores a copy of the last JSON value if it is a string or number.
func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err error) error {
serr := newUnmarshalErrorAfter(d, t, err).(*SemanticError)
if serr.JSONKind == '"' || serr.JSONKind == '0' {
serr.JSONValue = jsontext.Value(export.Decoder(d).PreviousTokenOrValue()).Clone()
}
return serr
}
// newUnmarshalErrorAfterWithSkipping is like [newUnmarshalErrorAfter],
// but automatically skips the remainder of the current value if
// [jsonflags.ReportErrorsWithLegacySemantics] is specified.
func newUnmarshalErrorAfterWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error {
err = newUnmarshalErrorAfter(d, t, err)
if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := export.Decoder(d).SkipValueRemainder(); err2 != nil {
return err2
}
}
return err
}
// newSemanticErrorWithPosition wraps err in a SemanticError assuming that
// the error occurred at the provided depth, and length.
// If err is already a SemanticError, then position information is only
// injected if it is currently unpopulated.
//
// If the position is unpopulated, it is ambiguous where the error occurred
// in the user code, whether it was before or after the current position.
// For the byte offset, we assume that the error occurred before the last read
// token or value when decoding, or before the next value when encoding.
// For the JSON pointer, we point to the parent object or array unless
// we can be certain that it happened with an object member.
//
// This is used to annotate errors returned by user-provided
// v2 MarshalJSON or UnmarshalJSON methods or functions.
func newSemanticErrorWithPosition(c coder, t reflect.Type, prevDepth int, prevLength int64, err error) error {
serr, _ := err.(*SemanticError)
if serr == nil {
serr = &SemanticError{Err: err}
}
var currDepth int
var currLength int64
var coderState interface{ AppendStackPointer([]byte, int) []byte }
var offset int64
switch c := c.(type) {
case *jsontext.Encoder:
e := export.Encoder(c)
serr.action = cmp.Or(serr.action, "marshal")
currDepth, currLength = e.Tokens.DepthLength()
offset = c.OutputOffset() + int64(export.Encoder(c).CountNextDelimWhitespace())
coderState = e
case *jsontext.Decoder:
d := export.Decoder(c)
serr.action = cmp.Or(serr.action, "unmarshal")
currDepth, currLength = d.Tokens.DepthLength()
tokOrVal := d.PreviousTokenOrValue()
offset = c.InputOffset() - int64(len(tokOrVal))
if (prevDepth == currDepth && prevLength == currLength) || len(tokOrVal) == 0 {
// If no Read method was called in the user-defined method or
// if the Peek method was called, then use the offset of the next value.
offset = c.InputOffset() + int64(export.Decoder(c).CountNextDelimWhitespace())
}
coderState = d
}
serr.ByteOffset = cmp.Or(serr.ByteOffset, offset)
if serr.JSONPointer == "" {
where := 0 // default to ambiguous positioning
switch {
case prevDepth == currDepth && prevLength+0 == currLength:
where = +1
case prevDepth == currDepth && prevLength+1 == currLength:
where = -1
}
serr.JSONPointer = jsontext.Pointer(coderState.AppendStackPointer(nil, where))
}
serr.GoType = cmp.Or(serr.GoType, t)
return serr
}
// collapseSemanticErrors collapses double SemanticErrors at the outer levels
// into a single SemanticError by preserving the inner error,
// but prepending the ByteOffset and JSONPointer with the outer error.
//
// For example:
//
// collapseSemanticErrors(&SemanticError{
// ByteOffset: len64(`[0,{"alpha":[0,1,`),
// JSONPointer: "/1/alpha/2",
// GoType: reflect.TypeFor[outerType](),
// Err: &SemanticError{
// ByteOffset: len64(`{"foo":"bar","fizz":[0,`),
// JSONPointer: "/fizz/1",
// GoType: reflect.TypeFor[innerType](),
// Err: ...,
// },
// })
//
// results in:
//
// &SemanticError{
// ByteOffset: len64(`[0,{"alpha":[0,1,`) + len64(`{"foo":"bar","fizz":[0,`),
// JSONPointer: "/1/alpha/2" + "/fizz/1",
// GoType: reflect.TypeFor[innerType](),
// Err: ...,
// }
//
// This is used to annotate errors returned by user-provided
// v1 MarshalJSON or UnmarshalJSON methods with precise position information
// if they themselves happened to return a SemanticError.
// Since MarshalJSON and UnmarshalJSON are not operating on the root JSON value,
// their positioning must be relative to the nested JSON value
// returned by UnmarshalJSON or passed to MarshalJSON.
// Therefore, we can construct an absolute position by concatenating
// the outer with the inner positions.
//
// Note that we do not use collapseSemanticErrors with user-provided functions
// that take in an [jsontext.Encoder] or [jsontext.Decoder] since they contain
// methods to report position relative to the root JSON value.
// We assume user-constructed errors are correctly precise about position.
func collapseSemanticErrors(err error) error {
if serr1, ok := err.(*SemanticError); ok {
if serr2, ok := serr1.Err.(*SemanticError); ok {
serr2.ByteOffset = serr1.ByteOffset + serr2.ByteOffset
serr2.JSONPointer = serr1.JSONPointer + serr2.JSONPointer
*serr1 = *serr2
}
}
return err
}
// errorModalVerb is a modal verb like "cannot" or "unable to".
//
// Once per process, Hyrum-proof the error message by deliberately
// switching between equivalent renderings of the same error message.
// The randomization is tied to the Hyrum-proofing already applied
// on map iteration in Go.
var errorModalVerb = sync.OnceValue(func() string {
for phrase := range map[string]struct{}{"cannot": {}, "unable to": {}} {
return phrase // use whichever phrase we get in the first iteration
}
return ""
})
func (e *SemanticError) Error() string {
var sb strings.Builder
sb.WriteString(errorPrefix)
// Hyrum-proof the error message by deliberately switching between
// two equivalent renderings of the same error message.
// The randomization is tied to the Hyrum-proofing already applied
// on map iteration in Go.
for phrase := range map[string]struct{}{"cannot": {}, "unable to": {}} {
sb.WriteString(phrase)
break // use whichever phrase we get in the first iteration
}
sb.WriteString(errorModalVerb())
// Format action.
var preposition string
@@ -67,7 +313,6 @@ func (e *SemanticError) Error() string {
}
// Format JSON kind.
var omitPreposition bool
switch e.JSONKind {
case 'n':
sb.WriteString(" JSON null")
@@ -82,45 +327,92 @@ func (e *SemanticError) Error() string {
case '[', ']':
sb.WriteString(" JSON array")
default:
omitPreposition = true
if e.action == "" {
preposition = ""
}
}
if len(e.JSONValue) > 0 && len(e.JSONValue) < 100 {
sb.WriteByte(' ')
sb.Write(e.JSONValue)
}
// Format Go type.
if e.GoType != nil {
if !omitPreposition {
sb.WriteString(preposition)
typeString := e.GoType.String()
if len(typeString) > 100 {
// An excessively long type string most likely occurs for
// an anonymous struct declaration with many fields.
// Reduce the noise by just printing the kind,
// and optionally prepending it with the package name
// if the struct happens to include an unexported field.
typeString = e.GoType.Kind().String()
if e.GoType.Kind() == reflect.Struct && e.GoType.Name() == "" {
for i := range e.GoType.NumField() {
if pkgPath := e.GoType.Field(i).PkgPath; pkgPath != "" {
typeString = pkgPath[strings.LastIndexByte(pkgPath, '/')+len("/"):] + ".struct"
break
}
}
}
}
sb.WriteString(" Go value of type ")
sb.WriteString(e.GoType.String())
sb.WriteString(preposition)
sb.WriteString(" Go ")
sb.WriteString(typeString)
}
// Special handling for unknown names.
if e.Err == ErrUnknownName {
sb.WriteString(": ")
sb.WriteString(ErrUnknownName.Error())
sb.WriteString(" ")
sb.WriteString(strconv.Quote(e.JSONPointer.LastToken()))
if parent := e.JSONPointer.Parent(); parent != "" {
sb.WriteString(" within ")
sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(parent), 100)))
}
return sb.String()
}
// Format where.
switch {
// Avoid printing if it overlaps with a wrapped SyntacticError.
switch serr, _ := e.Err.(*jsontext.SyntacticError); {
case e.JSONPointer != "":
sb.WriteString(" within JSON value at ")
sb.WriteString(strconv.Quote(e.JSONPointer))
if serr == nil || !e.JSONPointer.Contains(serr.JSONPointer) {
sb.WriteString(" within ")
sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(e.JSONPointer), 100)))
}
case e.ByteOffset > 0:
sb.WriteString(" after byte offset ")
sb.WriteString(strconv.FormatInt(e.ByteOffset, 10))
if serr == nil || !(e.ByteOffset <= serr.ByteOffset) {
sb.WriteString(" after offset ")
sb.WriteString(strconv.FormatInt(e.ByteOffset, 10))
}
}
// Format underlying error.
if e.Err != nil {
errString := e.Err.Error()
if isSyntacticError(e.Err) {
errString = strings.TrimPrefix(errString, "jsontext: ")
}
sb.WriteString(": ")
sb.WriteString(e.Err.Error())
sb.WriteString(errString)
}
return sb.String()
}
func (e *SemanticError) Unwrap() error {
return e.Err
}
func firstError(errs ...error) error {
for _, err := range errs {
if err != nil {
return err
}
func newDuplicateNameError(ptr jsontext.Pointer, quotedName []byte, offset int64) error {
if quotedName != nil {
name, _ := jsonwire.AppendUnquote(nil, quotedName)
ptr = ptr.AppendToken(string(name))
}
return &jsontext.SyntacticError{
ByteOffset: offset,
JSONPointer: ptr,
Err: jsontext.ErrDuplicateName,
}
return nil
}

View File

@@ -20,13 +20,11 @@ import (
"github.com/go-json-experiment/json/internal/jsonwire"
)
var errIgnoredField = errors.New("ignored field")
type isZeroer interface {
IsZero() bool
}
var isZeroerType = reflect.TypeOf((*isZeroer)(nil)).Elem()
var isZeroerType = reflect.TypeFor[isZeroer]()
type structFields struct {
flattened []structField // listed in depth-first ordering
@@ -35,6 +33,27 @@ type structFields struct {
inlinedFallback *structField
}
// reindex recomputes index to avoid bounds check during runtime.
//
// During the construction of each [structField] in [makeStructFields],
// the index field is 0-indexed. However, before it returns,
// the 0th field is stored in index0 and index stores the remainder.
func (sf *structFields) reindex() {
reindex := func(f *structField) {
f.index0 = f.index[0]
f.index = f.index[1:]
if len(f.index) == 0 {
f.index = nil // avoid pinning the backing slice
}
}
for i := range sf.flattened {
reindex(&sf.flattened[i])
}
if sf.inlinedFallback != nil {
reindex(sf.inlinedFallback)
}
}
// lookupByFoldedName looks up name by a case-insensitive match
// that also ignores the presence of dashes and underscores.
func (fs *structFields) lookupByFoldedName(name []byte) []*structField {
@@ -43,7 +62,8 @@ func (fs *structFields) lookupByFoldedName(name []byte) []*structField {
type structField struct {
id int // unique numeric ID in breadth-first ordering
index []int // index into a struct according to reflect.Type.FieldByIndex
index0 int // 0th index into a struct according to [reflect.Type.FieldByIndex]
index []int // 1st index and remainder according to [reflect.Type.FieldByIndex]
typ reflect.Type
fncs *arshaler
isZero func(addressableValue) bool
@@ -51,7 +71,13 @@ type structField struct {
fieldOptions
}
func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
var errNoExportedFields = errors.New("Go struct has no exported fields")
func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) {
orErrorf := func(serr *SemanticError, t reflect.Type, f string, a ...any) *SemanticError {
return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)})
}
// Setup a queue for a breath-first search.
var queueIndex int
type queueEntry struct {
@@ -74,14 +100,15 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
namesIndex := make(map[string]int) // index of each field with a given JSON object name in current struct
var hasAnyJSONTag bool // whether any Go struct field has a `json` tag
var hasAnyJSONField bool // whether any JSON serializable fields exist in current struct
for i := 0; i < t.NumField(); i++ {
for i := range t.NumField() {
sf := t.Field(i)
_, hasTag := sf.Tag.Lookup("json")
hasAnyJSONTag = hasAnyJSONTag || hasTag
options, ignored, err := parseFieldOptions(sf)
if err != nil {
return structFields{}, &SemanticError{GoType: t, Err: err}
} else if ignored {
serr = cmp.Or(serr, &SemanticError{GoType: t, Err: err})
}
if ignored {
continue
}
hasAnyJSONField = true
@@ -94,52 +121,55 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
fieldOptions: options,
}
if sf.Anonymous && !f.hasName {
f.inline = true // implied by use of Go embedding without an explicit name
if indirectType(f.typ).Kind() != reflect.Struct {
serr = orErrorf(serr, t, "embedded Go struct field %s of non-struct type must be explicitly given a JSON name", sf.Name)
} else {
f.inline = true // implied by use of Go embedding without an explicit name
}
}
if f.inline || f.unknown {
// Handle an inlined field that serializes to/from
// zero or more JSON object members.
if f.inline && f.unknown {
err := fmt.Errorf("Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name)
return structFields{}, &SemanticError{GoType: t, Err: err}
}
switch f.fieldOptions {
case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}:
case fieldOptions{name: f.name, quotedName: f.quotedName, unknown: true}:
case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true, unknown: true}:
serr = orErrorf(serr, t, "Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name)
f.inline = false // let `unknown` take precedence
default:
err := fmt.Errorf("Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
return structFields{}, &SemanticError{GoType: t, Err: err}
serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
if f.hasName {
continue // invalid inlined field; treat as ignored
}
f.fieldOptions = fieldOptions{name: f.name, quotedName: f.quotedName, inline: f.inline, unknown: f.unknown}
if f.inline && f.unknown {
f.inline = false // let `unknown` take precedence
}
}
// Unwrap one level of pointer indirection similar to how Go
// only allows embedding either T or *T, but not **T.
tf := f.typ
if tf.Kind() == reflect.Pointer && tf.Name() == "" {
tf = tf.Elem()
}
// Reject any types with custom serialization otherwise
// it becomes impossible to know what sub-fields to inline.
if which := implementsWhich(tf,
jsonMarshalerV2Type, jsonMarshalerV1Type, textMarshalerType,
jsonUnmarshalerV2Type, jsonUnmarshalerV1Type, textUnmarshalerType,
); which != nil && tf != jsontextValueType {
err := fmt.Errorf("inlined Go struct field %s of type %s must not implement JSON marshal or unmarshal methods", sf.Name, tf)
return structFields{}, &SemanticError{GoType: t, Err: err}
tf := indirectType(f.typ)
if implementsAny(tf, allMethodTypes...) && tf != jsontextValueType {
serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must not implement marshal or unmarshal methods", sf.Name, tf)
}
// Handle an inlined field that serializes to/from
// a finite number of JSON object members backed by a Go struct.
if tf.Kind() == reflect.Struct {
if f.unknown {
err := fmt.Errorf("inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf)
return structFields{}, &SemanticError{GoType: t, Err: err}
serr = orErrorf(serr, t, "inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf)
continue // invalid inlined field; treat as ignored
}
if qe.visitChildren {
queue = append(queue, queueEntry{tf, f.index, !seen[tf]})
}
seen[tf] = true
continue
} else if !sf.IsExported() {
serr = orErrorf(serr, t, "inlined Go struct field %s is not exported", sf.Name)
continue // invalid inlined field; treat as ignored
}
// Handle an inlined field that serializes to/from any number of
@@ -147,17 +177,22 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
switch {
case tf == jsontextValueType:
f.fncs = nil // specially handled in arshal_inlined.go
case tf.Kind() == reflect.Map && tf.Key() == stringType:
case tf.Kind() == reflect.Map && tf.Key().Kind() == reflect.String:
if implementsAny(tf.Key(), allMethodTypes...) {
serr = orErrorf(serr, t, "inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods", sf.Name, tf)
continue // invalid inlined field; treat as ignored
}
f.fncs = lookupArshaler(tf.Elem())
default:
err := fmt.Errorf("inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf)
return structFields{}, &SemanticError{GoType: t, Err: err}
serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf)
continue // invalid inlined field; treat as ignored
}
// Reject multiple inlined fallback fields within the same struct.
if inlinedFallbackIndex >= 0 {
err := fmt.Errorf("inlined Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(inlinedFallbackIndex).Name, sf.Name)
return structFields{}, &SemanticError{GoType: t, Err: err}
serr = orErrorf(serr, t, "inlined Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(inlinedFallbackIndex).Name, sf.Name)
// Still append f to inlinedFallbacks as there is still a
// check for a dominant inlined fallback before returning.
}
inlinedFallbackIndex = i
@@ -166,6 +201,24 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
// Handle normal Go struct field that serializes to/from
// a single JSON object member.
// Unexported fields cannot be serialized except for
// embedded fields of a struct type,
// which might promote exported fields of their own.
if !sf.IsExported() {
tf := indirectType(f.typ)
if !(sf.Anonymous && tf.Kind() == reflect.Struct) {
serr = orErrorf(serr, t, "Go struct field %s is not exported", sf.Name)
continue
}
// Unfortunately, methods on the unexported field
// still cannot be called.
if implementsAny(tf, allMethodTypes...) ||
(f.omitzero && implementsAny(tf, isZeroerType)) {
serr = orErrorf(serr, t, "Go struct field %s is not exported for method calls", sf.Name)
continue
}
}
// Provide a function that uses a type's IsZero method.
switch {
case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType):
@@ -194,15 +247,11 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
f.isEmpty = func(va addressableValue) bool { return va.IsNil() }
}
// Reject user-specified names with invalid UTF-8.
if !utf8.ValidString(f.name) {
err := fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, f.name)
return structFields{}, &SemanticError{GoType: t, Err: err}
}
// Reject multiple fields with same name within the same struct.
if j, ok := namesIndex[f.name]; ok {
err := fmt.Errorf("Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name)
return structFields{}, &SemanticError{GoType: t, Err: err}
serr = orErrorf(serr, t, "Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name)
// Still append f to allFields as there is still a
// check for a dominant field before returning.
}
namesIndex[f.name] = i
@@ -223,8 +272,7 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
// errors returned by errors.New would fail to serialize.
isEmptyStruct := t.NumField() == 0
if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField {
err := errors.New("Go struct has no exported fields")
return structFields{}, &SemanticError{GoType: t, Err: err}
serr = cmp.Or(serr, &SemanticError{GoType: t, Err: errNoExportedFields})
}
}
@@ -236,19 +284,11 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
// or the one that is uniquely tagged with a JSON name.
// Otherwise, no dominant field exists for the set.
flattened := allFields[:0]
slices.SortFunc(allFields, func(x, y structField) int {
switch {
case x.name != y.name:
return strings.Compare(x.name, y.name)
case len(x.index) != len(y.index):
return cmp.Compare(len(x.index), len(y.index))
case x.hasName && !y.hasName:
return -1
case !x.hasName && y.hasName:
return +1
default:
return 0 // TODO(https://go.dev/issue/61643): Compare bools better.
}
slices.SortStableFunc(allFields, func(x, y structField) int {
return cmp.Or(
strings.Compare(x.name, y.name),
cmp.Compare(len(x.index), len(y.index)),
boolsCompare(!x.hasName, !y.hasName))
})
for len(allFields) > 0 {
n := 1 // number of fields with the same exact name
@@ -279,7 +319,7 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
// Compute the mapping of fields in the byActualName map.
// Pre-fold all names so that we can lookup folded names quickly.
fs := structFields{
fs = structFields{
flattened: flattened,
byActualName: make(map[string]*structField, len(flattened)),
byFoldedName: make(map[string][]*structField, len(flattened)),
@@ -291,7 +331,7 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
}
for foldedName, fields := range fs.byFoldedName {
if len(fields) > 1 {
// The precedence order for conflicting nocase names
// The precedence order for conflicting ignoreCase names
// is by breadth-first order, rather than depth-first order.
slices.SortFunc(fields, func(x, y *structField) int {
return cmp.Compare(x.id, y.id)
@@ -302,17 +342,27 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
if n := len(inlinedFallbacks); n == 1 || (n > 1 && len(inlinedFallbacks[0].index) != len(inlinedFallbacks[1].index)) {
fs.inlinedFallback = &inlinedFallbacks[0] // dominant inlined fallback field
}
fs.reindex()
return fs, serr
}
return fs, nil
// indirectType unwraps one level of pointer indirection
// similar to how Go only allows embedding either T or *T,
// but not **T or P (which is a named pointer).
func indirectType(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Pointer && t.Name() == "" {
t = t.Elem()
}
return t
}
// matchFoldedName matches a case-insensitive name depending on the options.
// It assumes that foldName(f.name) == foldName(name).
//
// Case-insensitive matching is used if the `nocase` tag option is specified
// Case-insensitive matching is used if the `case:ignore` tag option is specified
// or the MatchCaseInsensitiveNames call option is specified
// (and the `strictcase` tag option is not specified).
// Functionally, the `nocase` and `strictcase` tag options take precedence.
// (and the `case:strict` tag option is not specified).
// Functionally, the `case:ignore` and `case:strict` tag options take precedence.
//
// The v1 definition of case-insensitivity operated under strings.EqualFold
// and would strictly compare dashes and underscores,
@@ -320,7 +370,7 @@ func makeStructFields(root reflect.Type) (structFields, *SemanticError) {
// Thus, if the MatchCaseSensitiveDelimiter call option is specified,
// the match is further restricted to using strings.EqualFold.
func (f *structField) matchFoldedName(name []byte, flags *jsonflags.Flags) bool {
if f.casing == nocase || (flags.Get(jsonflags.MatchCaseInsensitiveNames) && f.casing != strictcase) {
if f.casing == caseIgnore || (flags.Get(jsonflags.MatchCaseInsensitiveNames) && f.casing != caseStrict) {
if !flags.Get(jsonflags.MatchCaseSensitiveDelimiter) || strings.EqualFold(string(name), f.name) {
return true
}
@@ -329,21 +379,22 @@ func (f *structField) matchFoldedName(name []byte, flags *jsonflags.Flags) bool
}
const (
nocase = 1
strictcase = 2
caseIgnore = 1
caseStrict = 2
)
type fieldOptions struct {
name string
quotedName string // quoted name per RFC 8785, section 3.2.2.2.
hasName bool
casing int8 // either 0, nocase, or strictcase
inline bool
unknown bool
omitzero bool
omitempty bool
string bool
format string
name string
quotedName string // quoted name per RFC 8785, section 3.2.2.2.
hasName bool
nameNeedEscape bool
casing int8 // either 0, caseIgnore, or caseStrict
inline bool
unknown bool
omitzero bool
omitempty bool
string bool
format string
}
// parseFieldOptions parses the `json` tag in a Go struct field as
@@ -357,19 +408,19 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
return fieldOptions{}, true, nil
}
// Check whether this field is unexported.
if !sf.IsExported() {
// In contrast to v1, v2 no longer forwards exported fields from
// embedded fields of unexported types since Go reflection does not
// allow the same set of operations that are available in normal cases
// of purely exported fields.
// See https://go.dev/issue/21357 and https://go.dev/issue/24153.
if sf.Anonymous {
err = firstError(err, fmt.Errorf("embedded Go struct field %s of an unexported type must be explicitly ignored with a `json:\"-\"` tag", sf.Type.Name()))
}
// Check whether this field is unexported and not embedded,
// which Go reflection cannot mutate for the sake of serialization.
//
// An embedded field of an unexported type is still capable of
// forwarding exported fields, which may be JSON serialized.
// This technically operates on the edge of what is permissible by
// the Go language, but the most recent decision is to permit this.
//
// See https://go.dev/issue/24153 and https://go.dev/issue/32772.
if !sf.IsExported() && !sf.Anonymous {
// Tag options specified on an unexported field suggests user error.
if hasTag {
err = firstError(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag))
err = cmp.Or(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag))
}
return fieldOptions{}, true, err
}
@@ -384,21 +435,31 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool {
return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes
}))
opt := tag[:n]
if n == 0 {
// Allow a single quoted string for arbitrary names.
var err2 error
opt, n, err2 = consumeTagOption(tag)
name := tag[:n]
// If the next character is not a comma, then the name is either
// malformed (if n > 0) or a single-quoted name.
// In either case, call consumeTagOption to handle it further.
var err2 error
if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) {
name, n, err2 = consumeTagOption(tag)
if err2 != nil {
err = firstError(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
}
}
out.hasName = true
out.name = opt
if !utf8.ValidString(name) {
err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, name))
name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError
}
if err2 == nil {
out.hasName = true
out.name = name
}
tag = tag[n:]
}
b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{})
out.quotedName = string(b)
out.nameNeedEscape = jsonwire.NeedEscape(out.name)
// Handle any additional tag options (if any).
var wasFormat bool
@@ -406,11 +467,11 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
for len(tag) > 0 {
// Consume comma delimiter.
if tag[0] != ',' {
err = firstError(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0]))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0]))
} else {
tag = tag[len(","):]
if len(tag) == 0 {
err = firstError(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name))
break
}
}
@@ -418,21 +479,41 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
// Consume and process the tag option.
opt, n, err2 := consumeTagOption(tag)
if err2 != nil {
err = firstError(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
}
rawOpt := tag[:n]
tag = tag[n:]
switch {
case wasFormat:
err = firstError(err, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name))
case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "":
err = firstError(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt))
}
switch opt {
case "nocase":
out.casing |= nocase
case "strictcase":
out.casing |= strictcase
case "case":
if !strings.HasPrefix(tag, ":") {
err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `case` tag option; specify `case:ignore` or `case:strict` instead", sf.Name))
break
}
tag = tag[len(":"):]
opt, n, err2 := consumeTagOption(tag)
if err2 != nil {
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `case` tag option: %v", sf.Name, err2))
break
}
rawOpt := tag[:n]
tag = tag[n:]
if strings.HasPrefix(rawOpt, "'") {
err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead", sf.Name, rawOpt, opt))
}
switch opt {
case "ignore":
out.casing |= caseIgnore
case "strict":
out.casing |= caseStrict
default:
err = cmp.Or(err, fmt.Errorf("Go struct field %s has unknown `case:%s` tag value", sf.Name, rawOpt))
}
case "inline":
out.inline = true
case "unknown":
@@ -445,13 +526,13 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
out.string = true
case "format":
if !strings.HasPrefix(tag, ":") {
err = firstError(err, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name))
err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name))
break
}
tag = tag[len(":"):]
opt, n, err2 := consumeTagOption(tag)
if err2 != nil {
err = firstError(err, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err2))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err2))
break
}
tag = tag[n:]
@@ -462,8 +543,8 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
// This catches invalid mutants such as "omitEmpty" or "omit_empty".
normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
switch normOpt {
case "nocase", "strictcase", "inline", "unknown", "omitzero", "omitempty", "string", "format":
err = firstError(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt))
case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt))
}
// NOTE: Everything else is ignored. This does not mean it is
@@ -473,16 +554,20 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
// Reject duplicates.
switch {
case out.casing == nocase|strictcase:
err = firstError(err, fmt.Errorf("Go struct field %s cannot have both `nocase` and `structcase` tag options", sf.Name))
case out.casing == caseIgnore|caseStrict:
err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `case:ignore` and `case:strict` tag options", sf.Name))
case seenOpts[opt]:
err = firstError(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt))
err = cmp.Or(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt))
}
seenOpts[opt] = true
}
return out, false, err
}
// consumeTagOption consumes the next option,
// which is either a Go identifier or a single-quoted string.
// If the next option is invalid, it returns all of in until the next comma,
// and reports an error.
func consumeTagOption(in string) (string, int, error) {
// For legacy compatibility with v1, assume options are comma-separated.
i := strings.IndexByte(in, ',')
@@ -545,3 +630,15 @@ func consumeTagOption(in string) (string, int, error) {
func isLetterOrDigit(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)
}
// boolsCompare compares x and y, ordering false before true.
func boolsCompare(x, y bool) int {
switch {
case !x && y:
return -1
default:
return 0
case x && !y:
return +1
}
}

View File

@@ -4,6 +4,8 @@
package internal
import "errors"
// NotForPublicUse is a marker type that an API is for internal use only.
// It does not perfectly prevent usage of that API, but helps to restrict usage.
// Anything with this marker is not covered by the Go compatibility agreement.
@@ -12,3 +14,26 @@ type NotForPublicUse struct{}
// AllowInternalUse is passed from "json" to "jsontext" to authenticate
// that the caller can have access to internal functionality.
var AllowInternalUse NotForPublicUse
// Sentinel error values internally shared between jsonv1 and jsonv2.
var (
ErrCycle = errors.New("encountered a cycle")
ErrNonNilReference = errors.New("value must be passed as a non-nil pointer reference")
)
var (
// TransformMarshalError converts a v2 error into a v1 error.
// It is called only at the top-level of a Marshal function.
TransformMarshalError func(any, error) error
// NewMarshalerError constructs a jsonv1.MarshalerError.
// It is called after a user-defined Marshal method/function fails.
NewMarshalerError func(any, error, string) error
// TransformUnmarshalError converts a v2 error into a v1 error.
// It is called only at the top-level of a Unmarshal function.
TransformUnmarshalError func(any, error) error
// NewRawNumber returns new(jsonv1.Number).
NewRawNumber func() any
// RawNumberOf returns jsonv1.Number(b).
RawNumberOf func(b []byte) any
)

View File

@@ -14,7 +14,7 @@ import "github.com/go-json-experiment/json/internal"
//
// In common usage, this is OR'd with 0 or 1. For example:
// - (AllowInvalidUTF8 | 0) means "AllowInvalidUTF8 is false"
// - (Expand | Indent | 1) means "Expand and Indent are true"
// - (Multiline | Indent | 1) means "Multiline and Indent are true"
type Bools uint64
func (Bools) JSONOptions(internal.NotForPublicUse) {}
@@ -50,40 +50,59 @@ const (
AllowInvalidUTF8 |
EscapeForHTML |
EscapeForJS |
EscapeInvalidUTF8 |
PreserveRawStrings |
Deterministic |
FormatNilMapAsNull |
FormatNilSliceAsNull |
MatchCaseInsensitiveNames |
FormatByteArrayAsArray |
FormatTimeDurationAsNanosecond |
IgnoreStructErrors |
CallMethodsWithLegacySemantics |
FormatBytesWithLegacySemantics |
FormatTimeWithLegacySemantics |
MatchCaseSensitiveDelimiter |
MergeWithLegacySemantics |
OmitEmptyWithLegacyDefinition |
RejectFloatOverflow |
ReportLegacyErrorValues |
SkipUnaddressableMethods |
ReportErrorsWithLegacySemantics |
StringifyWithLegacySemantics |
UnmarshalArrayFromAnyLength
// AnyWhitespace reports whether the encoded output might have any whitespace.
AnyWhitespace = Multiline | SpaceAfterColon | SpaceAfterComma
// WhitespaceFlags is the set of flags related to whitespace formatting.
// In contrast to AnyWhitespace, this includes Indent and IndentPrefix
// as those settings take no effect if Multiline is false.
WhitespaceFlags = AnyWhitespace | Indent | IndentPrefix
// AnyEscape is the set of flags related to escaping in a JSON string.
AnyEscape = EscapeForHTML | EscapeForJS | EscapeInvalidUTF8
// CanonicalizeNumbers is the set of flags related to raw number canonicalization.
CanonicalizeNumbers = CanonicalizeRawInts | CanonicalizeRawFloats
)
// Encoder and decoder flags.
const (
initFlag Bools = 1 << iota // reserved for the boolean value itself
AllowDuplicateNames // encode or decode
AllowInvalidUTF8 // encode or decode
WithinArshalCall // encode or decode; for internal use by json.Marshal and json.Unmarshal
OmitTopLevelNewline // encode only; for internal use by json.Marshal and json.MarshalWrite
PreserveRawStrings // encode only; for internal use by jsontext.Value.Canonicalize
CanonicalizeNumbers // encode only; for internal use by jsontext.Value.Canonicalize
EscapeForHTML // encode only
EscapeForJS // encode only
Expand // encode only
Indent // encode only; non-boolean flag
IndentPrefix // encode only; non-boolean flag
ByteLimit // encode or decode; non-boolean flag
DepthLimit // encode or decode; non-boolean flag
AllowDuplicateNames // encode or decode
AllowInvalidUTF8 // encode or decode
WithinArshalCall // encode or decode; for internal use by json.Marshal and json.Unmarshal
OmitTopLevelNewline // encode only; for internal use by json.Marshal and json.MarshalWrite
PreserveRawStrings // encode only
CanonicalizeRawInts // encode only
CanonicalizeRawFloats // encode only
ReorderRawObjects // encode only
EscapeForHTML // encode only
EscapeForJS // encode only
EscapeInvalidUTF8 // encode only; only exposed in v1
Multiline // encode only
SpaceAfterColon // encode only
SpaceAfterComma // encode only
Indent // encode only; non-boolean flag
IndentPrefix // encode only; non-boolean flag
ByteLimit // encode or decode; non-boolean flag
DepthLimit // encode or decode; non-boolean flag
maxCoderFlag
)
@@ -96,6 +115,7 @@ const (
Deterministic // marshal only
FormatNilMapAsNull // marshal only
FormatNilSliceAsNull // marshal only
OmitZeroStructFields // marshal only
MatchCaseInsensitiveNames // marshal or unmarshal
DiscardUnknownMembers // marshal only
RejectUnknownMembers // unmarshal only
@@ -109,18 +129,17 @@ const (
const (
_ Bools = (maxArshalV2Flag >> 1) << iota
FormatByteArrayAsArray // marshal or unmarshal
FormatTimeDurationAsNanosecond // marshal or unmarshal
IgnoreStructErrors // marshal or unmarshal
MatchCaseSensitiveDelimiter // marshal or unmarshal
MergeWithLegacySemantics // unmarshal
OmitEmptyWithLegacyDefinition // marshal
RejectFloatOverflow // unmarshal
ReportLegacyErrorValues // marshal or unmarshal
SkipUnaddressableMethods // marshal or unmarshal
StringifyWithLegacySemantics // marshal or unmarshal
UnmarshalAnyWithRawNumber // unmarshal; for internal use by jsonv1.Decoder.UseNumber
UnmarshalArrayFromAnyLength // unmarshal
CallMethodsWithLegacySemantics // marshal or unmarshal
FormatBytesWithLegacySemantics // marshal or unmarshal
FormatTimeWithLegacySemantics // marshal or unmarshal
MatchCaseSensitiveDelimiter // marshal or unmarshal
MergeWithLegacySemantics // unmarshal
OmitEmptyWithLegacyDefinition // marshal
ReportErrorsWithLegacySemantics // marshal or unmarshal
StringifyWithLegacySemantics // marshal or unmarshal
StringifyBoolsAndStrings // marshal or unmarshal; for internal use by jsonv2.makeStructArshaler
UnmarshalAnyWithRawNumber // unmarshal; for internal use by jsonv1.Decoder.UseNumber
UnmarshalArrayFromAnyLength // unmarshal
maxArshalV1Flag
)

View File

@@ -46,30 +46,17 @@ type ArshalValues struct {
// DefaultOptionsV2 is the set of all options that define default v2 behavior.
var DefaultOptionsV2 = Struct{
Flags: jsonflags.Flags{
Presence: uint64(jsonflags.AllFlags),
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
Values: uint64(0),
},
CoderValues: CoderValues{Indent: "\t"}, // Indent is set, but Expand is set to false
}
// DefaultOptionsV1 is the set of all options that define default v1 behavior.
var DefaultOptionsV1 = Struct{
Flags: jsonflags.Flags{
Presence: uint64(jsonflags.AllFlags),
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
Values: uint64(jsonflags.DefaultV1Flags),
},
CoderValues: CoderValues{Indent: "\t"}, // Indent is set, but Expand is set to false
}
// CopyCoderOptions copies coder-specific options from src to dst.
// This is used by json.MarshalEncode and json.UnmarshalDecode since those
// functions ignore any coder-specific options and uses the options from the
// Encoder or Decoder that is passed in.
func (dst *Struct) CopyCoderOptions(src *Struct) {
srcFlags := src.Flags
srcFlags.Clear(^jsonflags.AllCoderFlags)
dst.Flags.Join(srcFlags)
dst.CoderValues = src.CoderValues
}
func (*Struct) JSONOptions(internal.NotForPublicUse) {}
@@ -125,50 +112,73 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
var JoinUnknownOption = func(*Struct, Options) { panic("unknown option") }
func (dst *Struct) Join(srcs ...Options) {
dst.join(false, srcs...)
}
func (dst *Struct) JoinWithoutCoderOptions(srcs ...Options) {
dst.join(true, srcs...)
}
func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) {
for _, src := range srcs {
switch src := src.(type) {
case nil:
continue
case jsonflags.Bools:
if excludeCoderOptions {
src &= ^jsonflags.AllCoderFlags
}
dst.Flags.Set(src)
case Indent:
dst.Flags.Set(jsonflags.Expand | jsonflags.Indent | 1)
if excludeCoderOptions {
continue
}
dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1)
dst.Indent = string(src)
case IndentPrefix:
dst.Flags.Set(jsonflags.Expand | jsonflags.IndentPrefix | 1)
if excludeCoderOptions {
continue
}
dst.Flags.Set(jsonflags.Multiline | jsonflags.IndentPrefix | 1)
dst.IndentPrefix = string(src)
case ByteLimit:
if excludeCoderOptions {
continue
}
dst.Flags.Set(jsonflags.ByteLimit | 1)
dst.ByteLimit = int64(src)
case DepthLimit:
if excludeCoderOptions {
continue
}
dst.Flags.Set(jsonflags.DepthLimit | 1)
dst.DepthLimit = int(src)
case *Struct:
dst.Flags.Join(src.Flags)
if src.Flags.Has(jsonflags.NonBooleanFlags) {
if src.Flags.Has(jsonflags.Indent) {
srcFlags := src.Flags // shallow copy the flags
if excludeCoderOptions {
srcFlags.Clear(jsonflags.AllCoderFlags)
}
dst.Flags.Join(srcFlags)
if srcFlags.Has(jsonflags.NonBooleanFlags) {
if srcFlags.Has(jsonflags.Indent) {
dst.Indent = src.Indent
}
if src.Flags.Has(jsonflags.IndentPrefix) {
if srcFlags.Has(jsonflags.IndentPrefix) {
dst.IndentPrefix = src.IndentPrefix
}
if src.Flags.Has(jsonflags.ByteLimit) {
if srcFlags.Has(jsonflags.ByteLimit) {
dst.ByteLimit = src.ByteLimit
}
if src.Flags.Has(jsonflags.DepthLimit) {
if srcFlags.Has(jsonflags.DepthLimit) {
dst.DepthLimit = src.DepthLimit
}
if src.Flags.Has(jsonflags.Marshalers) {
if srcFlags.Has(jsonflags.Marshalers) {
dst.Marshalers = src.Marshalers
}
if src.Flags.Has(jsonflags.Unmarshalers) {
if srcFlags.Has(jsonflags.Unmarshalers) {
dst.Unmarshalers = src.Unmarshalers
}
}
if src.Format != "" {
dst.Format = src.Format
dst.FormatDepth = src.FormatDepth
}
default:
JoinUnknownOption(dst, src)
}

View File

@@ -74,7 +74,7 @@ func ConsumeTrue(b []byte) int {
func ConsumeLiteral(b []byte, lit string) (n int, err error) {
for i := 0; i < len(b) && i < len(lit); i++ {
if b[i] != lit[i] {
return i, NewInvalidCharacterError(b[i:], "within literal "+lit+" (expecting "+strconv.QuoteRune(rune(lit[i]))+")")
return i, NewInvalidCharacterError(b[i:], "in literal "+lit+" (expecting "+strconv.QuoteRune(rune(lit[i]))+")")
}
}
if len(b) < len(lit) {
@@ -240,7 +240,7 @@ func ConsumeStringResumable(flags *ValueFlags, b []byte, resumeOffset int, valid
// Handle invalid control characters.
case r < ' ':
flags.Join(stringNonVerbatim | stringNonCanonical)
return n, NewInvalidCharacterError(b[n:], "within string (expecting non-control character)")
return n, NewInvalidCharacterError(b[n:], "in string (expecting non-control character)")
default:
panic("BUG: unhandled character " + QuoteRune(b[n:]))
}
@@ -374,7 +374,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
// Handle invalid control characters.
case r < ' ':
dst = append(dst, src[i:n]...)
return dst, NewInvalidCharacterError(src[n:], "within string (expecting non-control character)")
return dst, NewInvalidCharacterError(src[n:], "in string (expecting non-control character)")
default:
panic("BUG: unhandled character " + QuoteRune(src[n:]))
}
@@ -386,7 +386,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
// hasEscapedUTF16Prefix reports whether b is possibly
// the truncated prefix of a \uFFFF escape sequence.
func hasEscapedUTF16Prefix[Bytes ~[]byte | ~string](b Bytes, lowerSurrogateHalf bool) bool {
for i := 0; i < len(b); i++ {
for i := range len(b) {
switch c := b[i]; {
case i == 0 && c != '\\':
return false
@@ -513,7 +513,7 @@ beforeInteger:
}
state = withinIntegerDigits
default:
return n, state, NewInvalidCharacterError(b[n:], "within number (expecting digit)")
return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)")
}
// Consume optional fractional component.
@@ -527,7 +527,7 @@ beforeFractional:
case '0' <= b[n] && b[n] <= '9':
n++
default:
return n, state, NewInvalidCharacterError(b[n:], "within number (expecting digit)")
return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)")
}
for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') {
n++
@@ -549,7 +549,7 @@ beforeExponent:
case '0' <= b[n] && b[n] <= '9':
n++
default:
return n, state, NewInvalidCharacterError(b[n:], "within number (expecting digit)")
return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)")
}
for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') {
n++
@@ -567,7 +567,7 @@ func parseHexUint16[Bytes ~[]byte | ~string](b Bytes) (v uint16, ok bool) {
if len(b) != 4 {
return 0, false
}
for i := 0; i < 4; i++ {
for i := range 4 {
c := b[i]
switch {
case '0' <= c && c <= '9':
@@ -610,19 +610,6 @@ func ParseUint(b []byte) (v uint64, ok bool) {
// then we return MaxFloat since any finite value will always be infinitely
// more accurate at representing another finite value than an infinite value.
func ParseFloat(b []byte, bits int) (v float64, ok bool) {
// Fast path for exact integer numbers which fit in the
// 24-bit or 53-bit significand of a float32 or float64.
var negLen int // either 0 or 1
if len(b) > 0 && b[0] == '-' {
negLen = 1
}
u, ok := ParseUint(b[negLen:])
if ok && ((bits == 32 && u <= 1<<24) || (bits == 64 && u <= 1<<53)) {
return math.Copysign(float64(u), float64(-1*negLen)), true
}
// Note that the []byte->string conversion unfortunately allocates.
// See https://go.dev/issue/42429 for more information.
fv, err := strconv.ParseFloat(string(b), bits)
if math.IsInf(fv, 0) {
switch {

View File

@@ -66,35 +66,41 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflag
dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`))
dst = append(dst, '"')
for uint(len(src)) > uint(n) {
// Handle single-byte ASCII.
if c := src[n]; c < utf8.RuneSelf {
// Handle single-byte ASCII.
n++
if escapeASCII[c] > 0 {
if (c == '<' || c == '>' || c == '&') && !flags.Get(jsonflags.EscapeForHTML) {
continue
}
if escapeASCII[c] == 0 {
continue // no escaping possibly needed
}
// Handle escaping of single-byte ASCII.
if !(c == '<' || c == '>' || c == '&') || flags.Get(jsonflags.EscapeForHTML) {
dst = append(dst, src[i:n-1]...)
dst = appendEscapedASCII(dst, c)
i = n
}
continue
}
// Handle multi-byte Unicode.
switch r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:]))); {
case r == utf8.RuneError && rn == 1:
hasInvalidUTF8 = true
dst = append(dst, src[i:n]...)
dst = append(dst, "\ufffd"...)
n += rn
i = n
case (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS):
dst = append(dst, src[i:n]...)
dst = appendEscapedUnicode(dst, r)
n += rn
i = n
default:
} else {
// Handle multi-byte Unicode.
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:])))
n += rn
if r != utf8.RuneError && r != '\u2028' && r != '\u2029' {
continue // no escaping possibly needed
}
// Handle escaping of multi-byte Unicode.
switch {
case isInvalidUTF8(r, rn):
hasInvalidUTF8 = true
dst = append(dst, src[i:n-rn]...)
if flags.Get(jsonflags.EscapeInvalidUTF8) {
dst = append(dst, `\ufffd`...)
} else {
dst = append(dst, "\ufffd"...)
}
i = n
case (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS):
dst = append(dst, src[i:n-rn]...)
dst = appendEscapedUnicode(dst, r)
i = n
}
}
}
dst = append(dst, src[i:n]...)
@@ -141,7 +147,7 @@ func appendEscapedUTF16(dst []byte, x uint16) []byte {
}
// ReformatString consumes a JSON string from src and appends it to dst,
// reformatting it if necessary for the given escapeRune parameter.
// reformatting it if necessary according to the specified flags.
// It returns the appended output and the number of consumed input bytes.
func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) {
// TODO: Should this update ValueFlags as input?
@@ -150,18 +156,48 @@ func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error
if err != nil {
return dst, n, err
}
isCanonical := !flags.Get(jsonflags.EscapeForHTML | jsonflags.EscapeForJS)
if flags.Get(jsonflags.PreserveRawStrings) || (isCanonical && valFlags.IsCanonical()) {
// If the output requires no special escapes, and the input
// is already in canonical form or should be preserved verbatim,
// then directly copy the input to the output.
if !flags.Get(jsonflags.AnyEscape) &&
(valFlags.IsCanonical() || flags.Get(jsonflags.PreserveRawStrings)) {
dst = append(dst, src[:n]...) // copy the string verbatim
return dst, n, nil
}
// TODO: Implement a direct, raw-to-raw reformat for strings.
// If the escapeRune option would have resulted in no changes to the output,
// it would be faster to simply append src to dst without going through
// an intermediary representation in a separate buffer.
// Under [jsonflags.PreserveRawStrings], any pre-escaped sequences
// remain escaped, however we still need to respect the
// [jsonflags.EscapeForHTML] and [jsonflags.EscapeForJS] options.
if flags.Get(jsonflags.PreserveRawStrings) {
var i, lastAppendIndex int
for i < n {
if c := src[i]; c < utf8.RuneSelf {
if (c == '<' || c == '>' || c == '&') && flags.Get(jsonflags.EscapeForHTML) {
dst = append(dst, src[lastAppendIndex:i]...)
dst = appendEscapedASCII(dst, c)
lastAppendIndex = i + 1
}
i++
} else {
r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:]))
if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) {
dst = append(dst, src[lastAppendIndex:i]...)
dst = appendEscapedUnicode(dst, r)
lastAppendIndex = i + rn
}
i += rn
}
}
return append(dst, src[lastAppendIndex:n]...), n, nil
}
// The input contains characters that might need escaping,
// unnecessary escape sequences, or invalid UTF-8.
// Perform a round-trip unquote and quote to properly reformat
// these sequences according the current flags.
b, _ := AppendUnquote(nil, src[:n])
dst, _ = AppendQuote(dst, string(b), flags)
dst, _ = AppendQuote(dst, b, flags)
return dst, n, nil
}
@@ -204,23 +240,45 @@ func AppendFloat(dst []byte, src float64, bits int) []byte {
// ReformatNumber consumes a JSON string from src and appends it to dst,
// canonicalizing it if specified.
// It returns the appended output and the number of consumed input bytes.
func ReformatNumber(dst, src []byte, canonicalize bool) ([]byte, int, error) {
func ReformatNumber(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) {
n, err := ConsumeNumber(src)
if err != nil {
return dst, n, err
}
if !canonicalize {
if !flags.Get(jsonflags.CanonicalizeNumbers) {
dst = append(dst, src[:n]...) // copy the number verbatim
return dst, n, nil
}
// Canonicalize the number per RFC 8785, section 3.2.2.3.
// As an optimization, we can copy integer numbers below 2⁵³ verbatim.
const maxExactIntegerDigits = 16 // len(strconv.AppendUint(nil, 1<<53, 10))
if n < maxExactIntegerDigits && ConsumeSimpleNumber(src[:n]) == n {
dst = append(dst, src[:n]...) // copy the number verbatim
return dst, n, nil
// Identify the kind of number.
var isFloat bool
for _, c := range src[:n] {
if c == '.' || c == 'e' || c == 'E' {
isFloat = true // has fraction or exponent
break
}
}
// Check if need to canonicalize this kind of number.
switch {
case string(src[:n]) == "-0":
break // canonicalize -0 as 0 regardless of kind
case isFloat:
if !flags.Get(jsonflags.CanonicalizeRawFloats) {
dst = append(dst, src[:n]...) // copy the number verbatim
return dst, n, nil
}
default:
// As an optimization, we can copy integer numbers below 2⁵³ verbatim
// since the canonical form is always identical.
const maxExactIntegerDigits = 16 // len(strconv.AppendUint(nil, 1<<53, 10))
if !flags.Get(jsonflags.CanonicalizeRawInts) || n < maxExactIntegerDigits {
dst = append(dst, src[:n]...) // copy the number verbatim
return dst, n, nil
}
}
// Parse and reformat the number (which uses a canonical format).
fv, _ := strconv.ParseFloat(string(src[:n]), 64)
switch {
case fv == 0:

View File

@@ -76,13 +76,8 @@ func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF')
}
var invalidUTF8 bool
x0, y0 := x, y
for {
if len(x) == 0 || len(y) == 0 {
if len(x) == len(y) && invalidUTF8 {
return strings.Compare(string(x0), string(y0))
}
return cmp.Compare(len(x), len(y))
}
@@ -114,7 +109,14 @@ func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
if rx != ry {
return cmp.Compare(rx, ry)
}
invalidUTF8 = invalidUTF8 || (rx == utf8.RuneError && nx == 1) || (ry == utf8.RuneError && ny == 1)
// Check for invalid UTF-8, in which case,
// we just perform a byte-for-byte comparison.
if isInvalidUTF8(rx, nx) || isInvalidUTF8(ry, ny) {
if x[0] != y[0] {
return cmp.Compare(x[0], y[0])
}
}
x, y = x[nx:], y[ny:]
}
}
@@ -141,16 +143,12 @@ func truncateMaxUTF8[Bytes ~[]byte | ~string](b Bytes) Bytes {
return b
}
// NewError and ErrInvalidUTF8 are injected by the "jsontext" package,
// so that these error types use the jsontext.SyntacticError type.
var (
NewError = errors.New
ErrInvalidUTF8 = errors.New("invalid UTF-8 within string")
)
// TODO(https://go.dev/issue/70547): Use utf8.ErrInvalid instead.
var ErrInvalidUTF8 = errors.New("invalid UTF-8")
func NewInvalidCharacterError[Bytes ~[]byte | ~string](prefix Bytes, where string) error {
what := QuoteRune(prefix)
return NewError("invalid character " + what + " " + where)
return errors.New("invalid character " + what + " " + where)
}
func NewInvalidEscapeSequenceError[Bytes ~[]byte | ~string](what Bytes) error {
@@ -162,8 +160,56 @@ func NewInvalidEscapeSequenceError[Bytes ~[]byte | ~string](what Bytes) error {
return r == '`' || r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r)
}) >= 0
if needEscape {
return NewError("invalid " + label + " " + strconv.Quote(string(what)) + " within string")
return errors.New("invalid " + label + " " + strconv.Quote(string(what)) + " in string")
} else {
return NewError("invalid " + label + " `" + string(what) + "` within string")
return errors.New("invalid " + label + " `" + string(what) + "` in string")
}
}
// TruncatePointer optionally truncates the JSON pointer,
// enforcing that the length roughly does not exceed n.
func TruncatePointer(s string, n int) string {
if len(s) <= n {
return s
}
i := n / 2
j := len(s) - n/2
// Avoid truncating a name if there are multiple names present.
if k := strings.LastIndexByte(s[:i], '/'); k > 0 {
i = k
}
if k := strings.IndexByte(s[j:], '/'); k >= 0 {
j += k + len("/")
}
// Avoid truncation in the middle of a UTF-8 rune.
for i > 0 && isInvalidUTF8(utf8.DecodeLastRuneInString(s[:i])) {
i--
}
for j < len(s) && isInvalidUTF8(utf8.DecodeRuneInString(s[j:])) {
j++
}
// Determine the right middle fragment to use.
var middle string
switch strings.Count(s[i:j], "/") {
case 0:
middle = "…"
case 1:
middle = "…/…"
default:
middle = "…/…/…"
}
if strings.HasPrefix(s[i:j], "/") && middle != "…" {
middle = strings.TrimPrefix(middle, "…")
}
if strings.HasSuffix(s[i:j], "/") && middle != "…" {
middle = strings.TrimSuffix(middle, "…")
}
return s[:i] + middle + s[j:]
}
func isInvalidUTF8(r rune, rn int) bool {
return r == utf8.RuneError && rn == 1
}

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())
}
}
}

243
vendor/github.com/go-json-experiment/json/migrate.sh generated vendored Normal file
View File

@@ -0,0 +1,243 @@
#!/usr/bin/env bash
GOROOT=${1:-../go}
JSONROOT="."
# Check if the Go toolchain has a clean checkout.
if [ -n "$(cd $GOROOT; git status --porcelain)" ]; then
(cd $GOROOT; git status --porcelain)
echo "Working directory is not clean."
echo ""
echo "To cleanup, run:"
echo " (cd $GOROOT && git checkout . && git clean -fd)"
exit 1
fi
/bin/rm -rf $GOROOT/src/encoding/json/*
cp $JSONROOT/v1/* $GOROOT/src/encoding/json/
cp -r $JSONROOT/internal/ $GOROOT/src/encoding/json/internal/
mkdir $GOROOT/src/encoding/json/v2/
cp -r $JSONROOT/*.go $GOROOT/src/encoding/json/v2/
mkdir $GOROOT/src/encoding/json/jsontext/
cp -r $JSONROOT/jsontext/*.go $GOROOT/src/encoding/json/jsontext/
find $GOROOT/src/encoding/json -type f -exec sed -i 's|github[.]com/go-json-experiment/json/v1|encoding/json|g' {} +
find $GOROOT/src/encoding/json -type f -exec sed -i 's|github[.]com/go-json-experiment/json/|encoding/json/|g' {} +
find $GOROOT/src/encoding/json -type f -exec sed -i 's|github[.]com/go-json-experiment/json|encoding/json/v2|g' {} +
# Adjust for changed package path.
sed -i 's/json\.struct/v2.struct/g' $GOROOT/src/encoding/json/v2/errors_test.go
# Adjust tests that hardcode formatted error strings.
sed -i 's/}`, "Time.UnmarshalJSON: input is not a JSON string/}`, "json: cannot unmarshal JSON object into Go type time.Time/g' $GOROOT/src/time/time_test.go
sed -i 's/]`, "Time.UnmarshalJSON: input is not a JSON string/]`, "json: cannot unmarshal JSON array into Go type time.Time/g' $GOROOT/src/time/time_test.go
# Adjust for changed dependency tree.
sed -i 's|encoding/json|encoding/json/v2|g' $GOROOT/src/cmd/go/internal/imports/scan_test.go
sed -i 's|encoding/binary|internal/reflectlite|g' $GOROOT/src/cmd/go/internal/imports/scan_test.go
LINE=$(sed -n '/encoding\/json, encoding\/pem, encoding\/xml, mime;/=' $GOROOT/src/go/build/deps_test.go)
sed -i 's|encoding/json, encoding/pem, encoding/xml, mime|encoding/pem, encoding/xml, mime|g' $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 1)) i\\\\" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 2)) i\\\tSTR, errors" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 3)) i\\\t< encoding/json/internal" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 4)) i\\\t< encoding/json/internal/jsonflags" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 5)) i\\\t< encoding/json/internal/jsonopts" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 6)) i\\\t< encoding/json/internal/jsonwire" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 7)) i\\\t< encoding/json/jsontext;" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 8)) i\\\\" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+ 9)) i\\\tFMT," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+10)) i\\\tencoding/hex," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+11)) i\\\tencoding/base32," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+12)) i\\\tencoding/base64," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+13)) i\\\tencoding/binary," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+14)) i\\\tencoding/json/jsontext," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+15)) i\\\tencoding/json/internal," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+16)) i\\\tencoding/json/internal/jsonflags," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+17)) i\\\tencoding/json/internal/jsonopts," $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+18)) i\\\tencoding/json/internal/jsonwire" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+19)) i\\\t< encoding/json/v2" $GOROOT/src/go/build/deps_test.go
sed -i "$((LINE+20)) i\\\t< encoding/json;" $GOROOT/src/go/build/deps_test.go
LINE=$(sed -n '/Test-only packages can have anything they want/=' $GOROOT/src/go/build/deps_test.go)
sed -i "$((LINE+1)) i\\\tFMT, compress/gzip, embed, encoding/binary < encoding/json/internal/jsontest;" $GOROOT/src/go/build/deps_test.go
# Adjust for newly added API.
ISSUE=71497
FILE="next/$ISSUE.txt"
NEXT="$GOROOT/doc/next/6-stdlib/99-minor"
mkdir -p $NEXT/encoding/json
echo "A new [Options] type with associated constructors provide individual options" >> $NEXT/encoding/json/$ISSUE.md
echo "to configure \"encoding/json/v2\" to operate with certain historical v1 behavior." >> $NEXT/encoding/json/$ISSUE.md
echo "The [DefaultOptionsV2] option represents the set of all options needed" >> $NEXT/encoding/json/$ISSUE.md
echo "to configure \"encoding/json/v2\" to entirely operate with historical v1 behavior." >> $NEXT/encoding/json/$ISSUE.md
mkdir -p $NEXT/encoding/json/v2
echo "A new major version of \"encoding/json\" for processing JSON at a semantic level which is" >> $NEXT/encoding/json/v2/$ISSUE.md
echo "functionality that determines the meaning of JSON values as Go values and vice-versa." >> $NEXT/encoding/json/v2/$ISSUE.md
mkdir -p $NEXT/encoding/json/jsontext
echo "A new package to process JSON at a syntactic level that" >> $NEXT/encoding/json/jsontext/$ISSUE.md
echo "is concerned with processing JSON based on its grammar alone." >> $NEXT/encoding/json/jsontext/$ISSUE.md
NEXT="$GOROOT/api/next/$ISSUE.txt"
echo "pkg encoding/json, func CallMethodsWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func DefaultOptionsV1() jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func EscapeInvalidUTF8(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func FormatBytesWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func FormatTimeWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func MatchCaseSensitiveDelimiter(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func MergeWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func OmitEmptyWithLegacyDefinition(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func ReportErrorsWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func StringifyWithLegacySemantics(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, func UnmarshalArrayFromAnyLength(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, method (*Number) UnmarshalJSONFrom(*jsontext.Decoder, jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json, method (*UnmarshalTypeError) Unwrap() error #$ISSUE" >> $NEXT
echo "pkg encoding/json, method (Number) MarshalJSONTo(*jsontext.Encoder, jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json, type Marshaler = json.Marshaler #$ISSUE" >> $NEXT
echo "pkg encoding/json, type Options = jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json, type RawMessage = jsontext.Value #$ISSUE" >> $NEXT
echo "pkg encoding/json, type UnmarshalTypeError struct, Err error #$ISSUE" >> $NEXT
echo "pkg encoding/json, type Unmarshaler = json.Unmarshaler #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func AllowDuplicateNames(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func AllowInvalidUTF8(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func AppendFormat([]uint8, []uint8, ...jsonopts.Options) ([]uint8, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func AppendQuote[\$0 interface{ ~[]uint8 | ~string }]([]uint8, \$0) ([]uint8, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func AppendUnquote[\$0 interface{ ~[]uint8 | ~string }]([]uint8, \$0) ([]uint8, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func Bool(bool) Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func CanonicalizeRawFloats(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func CanonicalizeRawInts(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func EscapeForHTML(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func EscapeForJS(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func Float(float64) Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func Int(int64) Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func Multiline(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func NewDecoder(io.Reader, ...jsonopts.Options) *Decoder #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func NewEncoder(io.Writer, ...jsonopts.Options) *Encoder #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func PreserveRawStrings(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func ReorderRawObjects(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func SpaceAfterColon(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func SpaceAfterComma(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func String(string) Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func Uint(uint64) Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func WithIndent(string) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, func WithIndentPrefix(string) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) InputOffset() int64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) PeekKind() Kind #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) ReadToken() (Token, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) ReadValue() (Value, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) Reset(io.Reader, ...jsonopts.Options) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) SkipValue() error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) StackDepth() int #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) StackIndex(int) (Kind, int64) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) StackPointer() Pointer #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Decoder) UnreadBuffer() []uint8 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) OutputOffset() int64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) Reset(io.Writer, ...jsonopts.Options) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) StackDepth() int #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) StackIndex(int) (Kind, int64) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) StackPointer() Pointer #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) UnusedBuffer() []uint8 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) WriteToken(Token) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Encoder) WriteValue(Value) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*SyntacticError) Error() string #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*SyntacticError) Unwrap() error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Value) Canonicalize(...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Value) Compact(...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Value) Format(...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Value) Indent(...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (*Value) UnmarshalJSON([]uint8) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Kind) String() string #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Pointer) AppendToken(string) Pointer #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Pointer) Contains(Pointer) bool #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Pointer) IsValid() bool #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Pointer) LastToken() string #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Pointer) Parent() Pointer #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Pointer) Tokens() iter.Seq[string] #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) Bool() bool #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) Clone() Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) Float() float64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) Int() int64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) Kind() Kind #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) String() string #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Token) Uint() uint64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Value) Clone() Value #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Value) IsValid(...jsonopts.Options) bool #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Value) Kind() Kind #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Value) MarshalJSON() ([]uint8, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, method (Value) String() string #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Decoder struct #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Encoder struct #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Kind uint8 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Options = jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Pointer string #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type SyntacticError struct #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type SyntacticError struct, ByteOffset int64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type SyntacticError struct, Err error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type SyntacticError struct, JSONPointer Pointer #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Token struct #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, type Value []uint8 #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var BeginArray Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var BeginObject Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var EndArray Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var EndObject Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var ErrDuplicateName error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var ErrNonStringName error #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var False Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var Internal exporter #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var Null Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/jsontext, var True Token #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func DefaultOptionsV2() jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func Deterministic(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func DiscardUnknownMembers(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func FormatNilMapAsNull(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func FormatNilSliceAsNull(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func GetOption[\$0 interface{}](jsonopts.Options, func(\$0) jsonopts.Options) (\$0, bool) #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func JoinMarshalers(...*typedArshalers[jsontext.Encoder]) *typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func JoinOptions(...jsonopts.Options) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func JoinUnmarshalers(...*typedArshalers[jsontext.Decoder]) *typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func Marshal(interface{}, ...jsonopts.Options) ([]uint8, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func MarshalEncode(*jsontext.Encoder, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func MarshalFunc[\$0 interface{}](func(\$0) ([]uint8, error)) *typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func MarshalToFunc[\$0 interface{}](func(*jsontext.Encoder, \$0, jsonopts.Options) error) *typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func MarshalWrite(io.Writer, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func MatchCaseInsensitiveNames(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func OmitZeroStructFields(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func RejectUnknownMembers(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func StringifyNumbers(bool) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func Unmarshal([]uint8, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func UnmarshalDecode(*jsontext.Decoder, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func UnmarshalFromFunc[\$0 interface{}](func(*jsontext.Decoder, \$0, jsonopts.Options) error) *typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func UnmarshalFunc[\$0 interface{}](func([]uint8, \$0) error) *typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func UnmarshalRead(io.Reader, interface{}, ...jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func WithMarshalers(*typedArshalers[jsontext.Encoder]) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, func WithUnmarshalers(*typedArshalers[jsontext.Decoder]) jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, method (*SemanticError) Error() string #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, method (*SemanticError) Unwrap() error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Marshaler interface { MarshalJSON } #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Marshaler interface, MarshalJSON() ([]uint8, error) #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type MarshalerTo interface { MarshalJSONTo } #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type MarshalerTo interface, MarshalJSONTo(*jsontext.Encoder, jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Marshalers = typedArshalers[jsontext.Encoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Options = jsonopts.Options #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct, ByteOffset int64 #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct, Err error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct, GoType reflect.Type #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct, JSONKind jsontext.Kind #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct, JSONPointer jsontext.Pointer #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type SemanticError struct, JSONValue jsontext.Value #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Unmarshaler interface { UnmarshalJSON } #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Unmarshaler interface, UnmarshalJSON([]uint8) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type UnmarshalerFrom interface { UnmarshalJSONFrom } #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type UnmarshalerFrom interface, UnmarshalJSONFrom(*jsontext.Decoder, jsonopts.Options) error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, type Unmarshalers = typedArshalers[jsontext.Decoder] #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, var ErrUnknownName error #$ISSUE" >> $NEXT
echo "pkg encoding/json/v2, var SkipFunc error #$ISSUE" >> $NEXT
# The following declarations were moved to encoding/json/v2 or encoding/json/jsontext.
EXCEPT="$GOROOT/api/except.txt"
echo "pkg encoding/json, method (*RawMessage) UnmarshalJSON([]uint8) error" >> $EXCEPT
echo "pkg encoding/json, method (RawMessage) MarshalJSON() ([]uint8, error)" >> $EXCEPT
echo "pkg encoding/json, type Marshaler interface { MarshalJSON }" >> $EXCEPT
echo "pkg encoding/json, type Marshaler interface, MarshalJSON() ([]uint8, error)" >> $EXCEPT
echo "pkg encoding/json, type RawMessage []uint8" >> $EXCEPT
echo "pkg encoding/json, type Unmarshaler interface { UnmarshalJSON }" >> $EXCEPT
echo "pkg encoding/json, type Unmarshaler interface, UnmarshalJSON([]uint8) error" >> $EXCEPT
# Run the tests.
(cd $GOROOT/src; ./all.bash)

View File

@@ -61,6 +61,7 @@ import (
// - [Deterministic] affects marshaling only
// - [FormatNilSliceAsNull] affects marshaling only
// - [FormatNilMapAsNull] affects marshaling only
// - [OmitZeroStructFields] affects marshaling only
// - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling
// - [DiscardUnknownMembers] affects marshaling only
// - [RejectUnknownMembers] affects unmarshaling only
@@ -74,9 +75,7 @@ type Options = jsonopts.Options
// Properties set in later options override the value of previously set properties.
func JoinOptions(srcs ...Options) Options {
var dst jsonopts.Struct
for _, src := range srcs {
dst.Join(src)
}
dst.Join(srcs...)
return &dst
}
@@ -88,27 +87,25 @@ func JoinOptions(srcs ...Options) Options {
// v, ok := json.GetOption(opts, json.Deterministic)
//
// Options are most commonly introspected to alter the JSON representation of
// [MarshalerV2.MarshalJSONV2] and [MarshalerV2.MarshalJSONV2] methods, and
// [MarshalFuncV2] and [UnmarshalFuncV2] functions.
// [MarshalerTo.MarshalJSONTo] and [UnmarshalerFrom.UnmarshalJSONFrom] methods, and
// [MarshalToFunc] and [UnmarshalFromFunc] functions.
// In such cases, the presence bit should generally be ignored.
func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
return jsonopts.GetOption(opts, setter)
}
// DefaultOptionsV2 is the full set of all options that define v2 semantics.
// It is equivalent to all boolean options under [Options],
// [encoding/json.Options], and [encoding/json/jsontext.Options]
// being set to false. All non-boolean options are set to the zero value,
// except for [jsontext.WithIndent], which defaults to "\t".
// It is equivalent to all options under [Options], [encoding/json.Options],
// and [encoding/json/jsontext.Options] being set to false or the zero value,
// except for the options related to whitespace formatting.
func DefaultOptionsV2() Options {
return &jsonopts.DefaultOptionsV2
}
// StringifyNumbers specifies that numeric Go types should be marshaled
// as a JSON string containing the equivalent JSON number value.
// When unmarshaling, numeric Go types can be parsed from either
// a JSON number or a JSON string containing the JSON number
// without any surrounding whitespace.
// When unmarshaling, numeric Go types are parsed from a JSON string
// containing the JSON number without any surrounding whitespace.
//
// According to RFC 8259, section 6, a JSON implementation may choose to
// limit the representation of a JSON number to an IEEE 754 binary64 value.
@@ -168,9 +165,25 @@ func FormatNilMapAsNull(v bool) Options {
}
}
// OmitZeroStructFields specifies that a Go struct should marshal in such a way
// that all struct fields that are zero are omitted from the marshaled output
// if the value is zero as determined by the "IsZero() bool" method if present,
// otherwise based on whether the field is the zero Go value.
// This is semantically equivalent to specifying the `omitzero` tag option
// on every field in a Go struct.
//
// This only affects marshaling and is ignored when unmarshaling.
func OmitZeroStructFields(v bool) Options {
if v {
return jsonflags.OmitZeroStructFields | 1
} else {
return jsonflags.OmitZeroStructFields | 0
}
}
// MatchCaseInsensitiveNames specifies that JSON object members are matched
// against Go struct fields using a case-insensitive match of the name.
// Go struct fields explicitly marked with `strictcase` or `nocase`
// Go struct fields explicitly marked with `case:strict` or `case:ignore`
// always use case-sensitive (or case-insensitive) name matching,
// regardless of the value of this option.
//