This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+14 -108
View File
@@ -1,85 +1,20 @@
# JSON Serialization (v2)
[![GoDev](https://img.shields.io/static/v1?label=godev&message=reference&color=00add8)](https://pkg.go.dev/github.com/go-json-experiment/json)
[![Build Status](https://github.com/go-json-experiment/json/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/go-json-experiment/json/actions)
[![Build Status](https://github.com/go-json-experiment/json/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/go-json-experiment/json/actions)
This module hosts an experimental implementation of v2 `encoding/json`.
The API is unstable and breaking changes will regularly be made.
Do not depend on this in publicly available modules.
This module currently hosts a mirror of `encoding/json/v2`.
Importing this package with the `goexperiment.jsonv2` build tag
(which is enabled by default on Go 1.27) will alias the implementation
in the Go standard library.
Any commits that make breaking API or behavior changes will be marked
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 [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
* **Mostly backwards compatible:** If possible, v2 should aim to be _mostly_
compatible with v1 in terms of both API and default behavior to ease migration.
For example, the `Marshal` and `Unmarshal` functions are the most widely used
declarations in the v1 package. It seems sensible for equivalent functionality
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.
Options exist that can bring the v2 implementation to 100% compatibility,
but it will not be the default.
* **More flexible:** There is a
[long list of feature requests](https://github.com/golang/go/issues?q=is%3Aissue+is%3Aopen+encoding%2Fjson+in%3Atitle).
We should aim to provide the most flexible features that addresses most usages.
We do not want to over fit the v2 API to handle every possible use case.
Ideally, the features provided should be orthogonal in nature such that
any combination of features results in as few surprising edge cases as possible.
* **More performant:** JSON serialization is widely used and any bit of extra
performance gains will be greatly appreciated. Some rarely used behaviors of v1
may be dropped in favor of better performance. For example,
despite `Encoder` and `Decoder` operating on an `io.Writer` and `io.Reader`,
they do not operate in a truly streaming manner,
leading to a loss in performance. The v2 implementation should aim to be truly
streaming by default (see [#33714](https://golang.org/issue/33714)).
* **Easy to use (hard to misuse):** The v2 API should aim to make
the common case easy and the less common case at least possible.
The API should avoid behavior that goes contrary to user expectation,
which may result in subtle bugs (see [#36225](https://golang.org/issue/36225)).
* **v1 and v2 maintainability:** Since the v1 implementation must stay forever,
it would be beneficial if v1 could be implemented under the hood with v2,
allowing for less maintenance burden in the future. This probably implies that
behavioral changes in v2 relative to v1 need to be exposed as options.
* **Avoid unsafe:** Standard library packages generally avoid the use of
package `unsafe` even if it could provide a performance boost.
We aim to preserve this property.
## Expectations
While this module aims to possibly be the v2 implementation of `encoding/json`,
there is no guarantee that this outcome will occur. As with any major change
to the Go standard library, this will eventually go through the
[Go proposal process](https://github.com/golang/proposal#readme).
At the present moment, this is still in the design and experimentation phase
and is not ready for a formal proposal.
There are several possible outcomes from this experiment:
1. We determine that a v2 `encoding/json` would not provide sufficient benefit
over the existing v1 `encoding/json` package. Thus, we abandon this effort.
2. We propose a v2 `encoding/json` design, but it is rejected in favor of some
other design that is considered superior.
3. We propose a v2 `encoding/json` design, but rather than adding an entirely
new v2 `encoding/json` package, we decide to merge its functionality into
the existing v1 `encoding/json` package.
4. We propose a v2 `encoding/json` design and it is accepted, resulting in
its addition to the standard library.
5. Some other unforeseen outcome (among the infinite number of possibilities).
This module contained the historical prototype of `encoding/json/v2`
before it was formally adopted into the Go standard library
as an experiment in Go 1.25 and then later as a stable API in Go 1.27.
## Development
This module is primarily developed by
This module was primarily developed by
[@dsnet](https://github.com/dsnet),
[@mvdan](https://github.com/mvdan), and
[@johanbrandhorst](https://github.com/johanbrandhorst)
@@ -87,6 +22,11 @@ with feedback provided by
[@rogpeppe](https://github.com/rogpeppe),
[@ChrisHines](https://github.com/ChrisHines), and
[@rsc](https://github.com/rsc).
During the process to adopt the project into the Go standard library,
API review was provided by
[@aclements](https://github.com/aclements),
[@neild](https://github.com/neild), and
[@prattmic](https://github.com/prattmic).
Discussion about semantics occur semi-regularly, where a
[record of past meetings can be found here](https://docs.google.com/document/d/1rovrOTd-wTawGMPPlPuKhwXaYBg9VszTXR9AQQL5LfI/edit?usp=sharing).
@@ -130,40 +70,6 @@ while others affect it at the semantic layer.
Some options only affect JSON when decoding,
while others affect JSON while encoding.
## Behavior changes
The v2 `json` package changes the default behavior of `Marshal` and `Unmarshal`
relative to the v1 `json` package to be more sensible.
Some of these behavior changes have options and workarounds to opt into
behavior similar to what v1 provided.
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](/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` has no default representation in v2 (see [#71631](https://golang.org/issue/71631)) and results in an error. | |
| 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](/v1/diff_test.go) for details about every change.
## Performance
One of the goals of the v2 module is to be more performant than v1,
+258 -245
View File
@@ -11,6 +11,8 @@
// primitive data types such as booleans, strings, and numbers,
// in addition to structured data types such as objects and arrays.
//
// See the [Working with JSON] tutorial for an introduction to this package.
//
// [Marshal] and [Unmarshal] encode and decode Go values
// to/from JSON text contained within a []byte.
// [MarshalWrite] and [UnmarshalRead] operate on JSON text
@@ -55,7 +57,7 @@
// if [RejectUnknownMembers] is specified.
//
// The representation of each struct field can be customized in the
// "json" struct field tag, where the tag is a comma separated list of options.
// "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].
@@ -63,11 +65,7 @@
//
// 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
// is used as the JSON object name. JSON names containing commas or quotes,
// or names identical to "" or "-", can be specified using
// a single-quoted string literal, where the syntax is identical to
// the Go grammar for a double-quoted string literal,
// but instead uses single quotes as the delimiters.
// is used as the JSON object name.
// By default, unmarshaling uses case-sensitive matching to identify
// the Go struct field associated with a JSON object name.
//
@@ -84,60 +82,49 @@
// encoded as a JSON null, empty string, empty object, or empty array.
// This option has no effect when unmarshaling.
//
// - 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 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.
// - string: The "string" option specifies that [StringifyNumbers] be set
// when marshaling or unmarshaling a struct field value.
// This causes types that would normally be encoded as a JSON number
// to instead be encoded as a JSON number quoted within a JSON string,
// and to be decoded from a JSON string containing the JSON number
// without any surrounding whitespace.
// The "string" option only applies to the top-level of the Go struct field
// value. It is an error to apply this option to any type that does not
// encode as a JSON number.
// Note that composite types such as arrays, slices, structs, and maps do
// not encode as a JSON number, so applying this option will cause an error
// rather than affecting JSON numbers within such types.
// This extra level of encoding is often necessary since many JSON parsers
// cannot precisely represent 64-bit integers.
//
// - 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.
// The 'ignore' value specifies that matching is case-insensitive,
// and also ignores dashes and underscores. If multiple fields match,
// then the field with an exact name match is selected, otherwise an error
// is reported because the choice of field to unmarshal into is ambiguous.
// The 'strict' value specifies that matching is case-sensitive.
// This takes precedence over the [MatchCaseInsensitiveNames] option.
//
// - inline: The "inline" option specifies that
// - embed: The "embed" option specifies that
// the JSON representable content of this field type is to be promoted
// as if they were specified in the parent struct.
// as if it were specified in the parent struct.
// It is the JSON equivalent of Go struct embedding.
// A Go embedded field is implicitly inlined unless an explicit JSON name
// is specified. The inlined field must be a Go struct
// A Go embedded field is implicitly JSON embedded unless
// an explicit JSON name is specified. The embedded 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,
// 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 fallbacks” as they can represent all possible
// embedded fields from a pointer type are omitted if it is nil.
// Embedded fields of type [jsontext.Value] and map[~string]T are called
// “embedded 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,
// Only one embedded fallback field may be specified in a struct,
// while many non-fallback fields may be specified. This option
// must not be specified with any other option (including the JSON name).
//
// - 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.
// If [DiscardUnknownMembers] is specified when marshaling,
// the contents of this field are ignored.
// If [RejectUnknownMembers] is specified when unmarshaling,
// any unknown object members are rejected regardless of whether
// an inlined fallback with the "unknown" option exists. This option
// must not be specified with any other option (including the JSON name).
//
// - format: The "format" option specifies a format flag
// used to specialize the formatting of the field value.
// The option is a key-value pair specified as "format:value" where
// the value must be either a literal consisting of letters and numbers
// (e.g., "format:RFC3339") or a single-quoted string literal
// (e.g., "format:'2006-01-02'"). The interpretation of the format flag
// is determined by the struct field type.
//
// The "omitzero" and "omitempty" options are mostly semantically identical.
// The "omitzero" and "omitempty" options behave similarly.
// The former is defined in terms of the Go type system,
// while the latter in terms of the JSON type system.
// Consequently they behave differently in some circumstances.
@@ -146,14 +133,14 @@
// The "omitzero" option is useful for types with a well-defined zero value
// (e.g., [net/netip.Addr]) or have an IsZero method (e.g., [time.Time.IsZero]).
//
// Every Go struct corresponds to a list of JSON representable fields
// Every Go struct corresponds to a list of JSON-representable fields
// which is constructed by performing a breadth-first search over
// all struct fields (excluding unexported or ignored fields),
// where the search recursively descends into inlined structs.
// The set of non-inlined fields in a struct must have unique JSON names.
// where the search recursively descends into embedded structs.
// The set of non-embedded fields in a struct must have unique JSON names.
// If multiple fields all have the same JSON name, then the one
// at shallowest depth takes precedence and the other fields at deeper depths
// are excluded from the list of JSON representable fields.
// are excluded from the list of JSON-representable fields.
// If multiple fields at the shallowest depth have the same JSON name,
// but exactly one is explicitly tagged with a JSON name,
// then that field takes precedence and all others are excluded from the list.
@@ -161,7 +148,7 @@
// with embedded struct types.
//
// Marshaling or unmarshaling a non-empty struct
// without any JSON representable fields results in a [SemanticError].
// without any JSON-representable fields results in a [SemanticError].
// Unexported fields must not have any `json` tags except for `json:"-"`.
//
// # Security Considerations
@@ -173,8 +160,8 @@
//
// [For example, suppose we have two micro-services.]
// The first service is responsible for authenticating a JSON request,
// while the second service is responsible for executing the request
// (having assumed that the prior service authenticated the request).
// while the second service is responsible for executing the request,
// assuming that it was authenticated.
// If an attacker were able to maliciously craft a JSON request such that
// both services believe that the same request is from different users,
// it could bypass the authenticator with valid credentials for one user,
@@ -256,6 +243,7 @@
// The v2 API generally chooses more secure defaults than v1,
// but care should still be taken with large integers or unknown members.
//
// [Working with JSON]: https://go.dev/doc/tutorial/json
// [For example, suppose we have two micro-services.]: https://www.youtube.com/watch?v=avilmOcHKHE&t=1057s
package json
@@ -274,19 +262,21 @@ import (
// Functions or methods that operate on *T are only called when encoding
// a value of type T (by taking its address) or a non-nil value of *T.
// Marshal ensures that a value is always addressable
// (by boxing it on the heap if necessary) so that
// (by copying the value if necessary) so that
// these functions and methods can be consistently called. For performance,
// it is recommended that Marshal be passed a non-nil pointer to the value.
//
// The input value is encoded as JSON according the following rules:
// The input value is encoded as JSON according to the following rules:
//
// - If any type-specific functions in a [WithMarshalers] option match
// the value type, then those functions are called to encode the value.
// If all applicable functions return [SkipFunc],
// If all applicable functions return [errors.ErrUnsupported],
// then the value is encoded according to subsequent rules.
//
// - If the value type implements [MarshalerTo],
// then the MarshalJSONTo method is called to encode the value.
// If the method returns [errors.ErrUnsupported],
// then the input is encoded according to subsequent rules.
//
// - If the value type implements [Marshaler],
// then the MarshalJSON method is called to encode the value.
@@ -302,51 +292,31 @@ import (
// - Otherwise, the value is encoded according to the value's type
// as described in detail below.
//
// Most Go types have a default JSON representation.
// Certain types support specialized formatting according to
// a format flag optionally specified in the Go struct tag
// for the struct field that contains the current value
// (see the “JSON Representation of Go structs” section for more details).
//
// The representation of each type is as follows:
// Most Go types have a default JSON representation as follows:
//
// - A Go boolean is encoded as a JSON boolean (e.g., true or false).
// It does not support any custom format flags.
//
// - A Go string is encoded as a JSON string.
// It does not support any custom format flags.
//
// - A Go []byte or [N]byte is encoded as a JSON string containing
// the binary value encoded using RFC 4648.
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
// If the format is "base64url", then this uses RFC 4648, section 5.
// If the format is "base32", then this uses RFC 4648, section 6.
// If the format is "base32hex", then this uses RFC 4648, section 7.
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
// If the format is "array", then the bytes value is encoded as a JSON array
// where each byte is recursively JSON-encoded as each JSON array element.
// a binary value using Base 64 Encoding per RFC 4648, section 4.
//
// - A Go integer is encoded as a JSON number without fractions or exponents.
// 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 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.
// Otherwise, the presence of non-finite numbers results in a [SemanticError].
// Encoding a NaN or ±Inf results in a [SemanticError].
//
// - 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]. 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.
// Otherwise by default, a nil map is encoded as an empty JSON object.
// By default, a nil map is encoded as an empty JSON object,
// unless the [FormatNilMapAsNull] option is specified.
//
// - A Go struct is encoded as a JSON object.
// See the “JSON Representation of Go structs” section
@@ -354,53 +324,33 @@ import (
//
// - A Go slice is encoded as a JSON array, where each Go slice element
// is recursively JSON-encoded as the elements of the JSON array.
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
// regardless of whether [FormatNilSliceAsNull] is specified.
// Otherwise by default, a nil slice is encoded as an empty JSON array.
// By default, a nil slice is encoded as an empty JSON array,
// unless the [FormatNilSliceAsNull] option is specified.
//
// - A Go array is encoded as a JSON array, where each Go array element
// is recursively JSON-encoded as the elements of the JSON array.
// The JSON array length is always identical to the Go array length.
// It does not support any custom format flags.
//
// - A Go pointer is encoded as a JSON null if nil, otherwise it is
// the recursively JSON-encoded representation of the underlying value.
// Format flags are forwarded to the encoding of the underlying value.
//
// - A Go interface is encoded as a JSON null if nil, otherwise it is
// the recursively JSON-encoded representation of the underlying value.
// It does not support any custom format flags.
//
// - A Go [time.Time] is encoded as a JSON string containing the timestamp
// formatted in RFC 3339 with nanosecond precision.
// If the format matches one of the format constants declared
// in the time package (e.g., RFC1123), then that format is used.
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
// then the timestamp is encoded as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
// To avoid a fractional component, round the timestamp to the relevant unit.
// Otherwise, the format is used as-is with [time.Time.Format] if non-empty.
//
// - A Go [time.Duration] currently has no default representation and
// requires an explicit format to be specified.
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is encoded as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
// To avoid a fractional component, round the duration to the relevant unit.
// If the format is "units", it is encoded as a JSON string formatted using
// [time.Duration.String] (e.g., "1h30m" for 1 hour 30 minutes).
// If the format is "iso8601", it is encoded as a JSON string using the
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
// using only accurate units of hours, minutes, and seconds.
// results in a [SemanticError], unless the [encoding/json.FormatDurationAsNano]
// option is specified, in which case it is encoded as a JSON number
// without fractions or exponents, representing the duration in nanoseconds.
//
// - All other Go types (e.g., complex numbers, channels, and functions)
// have no default representation and result in a [SemanticError].
//
// JSON cannot represent cyclic data structures and Marshal does not handle them.
// Passing cyclic structures will result in an error.
func Marshal(in any, opts ...Options) (out []byte, err error) {
opts = mayAppendSupportFormatTag(opts)
return json.Marshal(in, opts...)
}
@@ -409,18 +359,18 @@ func Marshal(in any, opts ...Options) (out []byte, err error) {
// It does not terminate the output with a newline.
// See [Marshal] for details about the conversion of a Go value into JSON.
func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
return json.MarshalWrite(out, in, opts...)
}
// 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].
// the provided marshal or encode options (while ignoring unmarshal or decode options).
// The options provided take precedence over options already applied on
// the [jsontext.Encoder] and only apply for the duration of the marshal call.
//
// See [Marshal] for details about the conversion of a Go value into JSON.
func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
return json.MarshalEncode(out, in, opts...)
}
@@ -434,18 +384,24 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// Functions or methods that operate on *T are only called when decoding
// a value of type T (by taking its address) or a non-nil value of *T.
// Unmarshal ensures that a value is always addressable
// (by boxing it on the heap if necessary) so that
// (by copying the value if necessary) so that
// these functions and methods can be consistently called.
// If a value must be shallow copied to call a pointer-receiver
// [Unmarshaler], [UnmarshalerFrom], or [encoding.TextUnmarshaler] method,
// then any mutations performed by the method are shallow copied back
// into the destination value.
//
// The input is decoded into the output according the following rules:
// The input is decoded into the output according to the following rules:
//
// - If any type-specific functions in a [WithUnmarshalers] option match
// the value type, then those functions are called to decode the JSON
// value. If all applicable functions return [SkipFunc],
// value. If all applicable functions return [errors.ErrUnsupported],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [UnmarshalerFrom],
// then the UnmarshalJSONFrom method is called to decode the JSON value.
// If the method returns [errors.ErrUnsupported],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [Unmarshaler],
// then the UnmarshalJSON method is called to decode the JSON value.
@@ -459,10 +415,6 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// as described in detail below.
//
// Most Go types have a default JSON representation.
// Certain types support specialized formatting according to
// a format flag optionally specified in the Go struct tag
// for the struct field that contains the current value
// (see the “JSON Representation of Go structs” section for more details).
// A JSON null may be decoded into every supported Go value where
// it is equivalent to storing the zero value of the Go value.
// If the input JSON kind is not handled by the current Go value type,
@@ -472,20 +424,11 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// The representation of each type is as follows:
//
// - A Go boolean is decoded from a JSON boolean (e.g., true or false).
// It does not support any custom format flags.
//
// - A Go string is decoded from a JSON string.
// It does not support any custom format flags.
//
// - A Go []byte or [N]byte is decoded from a JSON string
// containing the binary value encoded using RFC 4648.
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
// If the format is "base64url", then this uses RFC 4648, section 5.
// If the format is "base32", then this uses RFC 4648, section 6.
// If the format is "base32hex", then this uses RFC 4648, section 7.
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
// If the format is "array", then the Go slice or array is decoded from a
// JSON array where each JSON element is recursively decoded for each byte.
// - A Go []byte or [N]byte is decoded from a JSON string containing
// a binary value using Base 64 Encoding per RFC 4648, section 4.
// When decoding into a non-nil []byte, the slice length is reset to zero
// and the decoded input is appended to it.
// When decoding into a [N]byte, the input must decode to exactly N bytes,
@@ -497,15 +440,13 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// 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 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].
// Since JSON lacks a native representation for a NaN or ±Inf,
// such values cannot be the result of decoding.
//
// - A Go map is decoded from a JSON object,
// where each JSON object name and value pair is recursively decoded
@@ -513,7 +454,6 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// 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.
// The formats "emitnull" and "emitempty" have no effect when decoding.
//
// - A Go struct is decoded from a JSON object.
// See the “JSON Representation of Go structs” section
@@ -523,20 +463,17 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// is recursively decoded and appended to the Go slice.
// Before appending into a Go slice, a new slice is allocated if it is nil,
// otherwise the slice length is reset to zero.
// The formats "emitnull" and "emitempty" have no effect when decoding.
//
// - A Go array is decoded from a JSON array, where each JSON array element
// is recursively decoded as each corresponding Go array element.
// Each Go array element is zeroed before decoding into it.
// It fails with a [SemanticError] if the JSON array does not contain
// the exact same number of elements as the Go array.
// It does not support any custom format flags.
//
// - A Go pointer is decoded based on the JSON kind and underlying Go type.
// If the input is a JSON null, then this stores a nil pointer.
// Otherwise, it allocates a new underlying value if the pointer is nil,
// and recursively JSON decodes into the underlying value.
// Format flags are forwarded to the decoding of the underlying type.
//
// - A Go interface is decoded based on the JSON kind and underlying Go type.
// If the input is a JSON null, then this stores a nil interface value.
@@ -548,28 +485,15 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// For example, unmarshaling into a nil io.Reader fails since
// there is no concrete type to populate the interface value with.
// Otherwise an underlying value exists and it recursively decodes
// the JSON input into it. It does not support any custom format flags.
// the JSON input into it.
//
// - A Go [time.Time] is decoded from a JSON string containing the time
// formatted in RFC 3339 with nanosecond precision.
// If the format matches one of the format constants declared in
// the time package (e.g., RFC1123), then that format is used for parsing.
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
// then the timestamp is decoded from an optionally fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
// Otherwise, the format is used as-is with [time.Time.Parse] if non-empty.
//
// - A Go [time.Duration] currently has no default representation and
// requires an explicit format to be specified.
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is decoded from an optionally fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
// If the format is "units", it is decoded from a JSON string parsed using
// [time.ParseDuration] (e.g., "1h30m" for 1 hour 30 minutes).
// If the format is "iso8601", it is decoded from a JSON string using the
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
// accepting only accurate units of hours, minutes, or seconds.
// results in a [SemanticError], unless the [encoding/json.FormatDurationAsNano]
// option is specified, in which case it is decoded as a JSON number
// without fractions or exponents, representing the duration in nanoseconds.
//
// - All other Go types (e.g., complex numbers, channels, and functions)
// have no default representation and result in a [SemanticError].
@@ -580,6 +504,7 @@ func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
// For JSON objects, the input object is merged into the destination value
// where matching object members recursively apply merge semantics.
func Unmarshal(in []byte, out any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
return json.Unmarshal(in, out, opts...)
}
@@ -590,33 +515,25 @@ func Unmarshal(in []byte, out any, opts ...Options) (err error) {
// without reporting an error for EOF. The output must be a non-nil pointer.
// See [Unmarshal] for details about the conversion of JSON into a Go value.
func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
return json.UnmarshalRead(in, out, opts...)
}
// 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 provided unmarshal or decode options (while ignoring marshal or encode options).
// The options provided take precedence over options already applied on
// the [jsontext.Decoder] and only apply for the duration of the unmarshal call.
//
// The input may be a stream of one or more JSON values,
// where this only unmarshals the next JSON value in the stream.
// The input may be a stream of zero or more JSON values.
// UnmarshalDecode unmarshals only the next JSON value in the stream.
// If there are no more top-level JSON values, it reports [io.EOF].
// 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) {
opts = mayAppendSupportFormatTag(opts)
return json.UnmarshalDecode(in, out, opts...)
}
// 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].
// For example, it is permissible to call [jsontext.Decoder.PeekKind],
// but not permissible to call [jsontext.Decoder.ReadToken] or
// [jsontext.Encoder.WriteToken] since such methods mutate the state.
var SkipFunc = json.SkipFunc
// 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].
@@ -627,7 +544,8 @@ type Marshalers = json.Marshalers
// 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,
// If a function returns [errors.ErrUnsupported],
// then the next applicable function is called,
// otherwise the default marshaling behavior is used.
//
// For example:
@@ -649,7 +567,8 @@ type Unmarshalers = json.Unmarshalers
// 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,
// If a function returns [errors.ErrUnsupported],
// then the next applicable function is called,
// otherwise the default unmarshaling behavior is used.
//
// For example:
@@ -667,9 +586,9 @@ func JoinUnmarshalers(us ...*Unmarshalers) *Unmarshalers {
// The function is always provided with a non-nil pointer value
// if T is an interface or pointer type.
//
// 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].
// Implementations must follow the requirements of [Marshaler].
//
// Implementations must not retain the value of T.
func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers {
return json.MarshalFunc[T](fn)
}
@@ -680,12 +599,10 @@ func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers {
// The function is always provided with a non-nil pointer value
// if T is an interface or pointer type.
//
// The function must marshal exactly one JSON value by calling write methods
// 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] and the value of T
// must not be retained outside the function call.
// Implementations must follow the requirements of [MarshalerTo].
//
// Implementations must not retain the pointer to [jsontext.Encoder] or the
// value of T.
func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
return json.MarshalToFunc[T](fn)
}
@@ -695,10 +612,9 @@ func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
// T must be an unnamed pointer or an interface type.
// The function is always provided with a non-nil pointer value.
//
// The function must unmarshal exactly one JSON value.
// 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].
// Implementations must follow the requirements of [Unmarshaler].
//
// Implementations must not retain the value of T.
func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
return json.UnmarshalFunc[T](fn)
}
@@ -708,60 +624,133 @@ func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
// T must be an unnamed pointer or an interface type.
// The function is always provided with a non-nil pointer value.
//
// The function must unmarshal exactly one JSON value by calling read methods
// 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] and the value of T
// must not be retained outside the function call.
// Implementations must follow the requirements of [UnmarshalerFrom].
//
// Implementations must not retain the pointer to [jsontext.Decoder] or the
// value of T.
func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers {
return json.UnmarshalFromFunc[T](fn)
}
// 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.
// is trying to avoid directly depending on the "jsontext" package.
//
// It is recommended that implementations return a buffer that is safe
// Implementations should return a buffer that is safe
// for the caller to retain and potentially mutate.
//
// Implementations must not return [errors.ErrUnsupported].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError].
//
// Implementations should assume [Deterministic] is true and return
// deterministic output.
type Marshaler = json.Marshaler
// 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.
// since it is both more performant and more flexible.
// 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].
// The implementation must write only one JSON value to the Encoder.
// Alternatively, it may return [errors.ErrUnsupported] without mutating
// the Encoder. The "json" package calling the method will
// use the next available JSON representation for the receiver type,
// as described in [Marshal].
// Implementations must not retain the pointer to [jsontext.Encoder].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError],
// except for IO errors.
//
// The MarshalJSONTo method should not be called directly as it may
// return sentinel errors that need special handling.
// Users should instead call [MarshalEncode], which handles such cases.
//
// Implementations should inspect the marshal options from
// [jsontext.Encoder.Options] and adjust behavior to respect the options as
// necessary.
//
// The following options may be relevant to MarshalerTo implementations:
//
// - [Deterministic]: if the implementation may produce non-deterministic output
// - [StringifyNumbers]: if the type is represented as a JSON number
//
// Several options, such as [FormatNilSliceAsNull], apply only to native Go
// types. Thus, these options are typically not directly relevant to
// MarshalerTo implementations. However, types representing a composite type
// should marshal contained types using [MarshalEncode] to ensure these options
// apply to the contained types. Similarly, [WithMarshalers] may influence
// marshaling of any contained type within a composite type.
//
// All other options are automatically handled outside of the MarshalerTo
// implementation, and thus are not relevant to implementations.
type MarshalerTo = json.MarshalerTo
// 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.
// is trying to avoid a direct dependency on the "jsontext" package.
//
// The input can be assumed to be a valid encoding of a JSON value
// if called from unmarshal functionality in this package.
// UnmarshalJSON must copy the JSON data if it is retained after returning.
// It is recommended that UnmarshalJSON implement merge semantics when
// unmarshaling into a pre-populated value.
// It is recommended that UnmarshalJSON implement merge semantics
// when unmarshaling into a pre-populated value, as described in [Unmarshal].
//
// Implementations must not retain or mutate the input []byte.
//
// Implementations must not return [errors.ErrUnsupported].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError].
type Unmarshaler = json.Unmarshaler
// 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.
// since this is both more performant and more flexible.
// 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 UnmarshalJSONFrom implement merge semantics when
// unmarshaling into a pre-populated value.
//
// unmarshaling into a pre-populated value, as described in [Unmarshal].
// Alternatively, it may return [errors.ErrUnsupported] without mutating
// the Decoder. The "json" package calling the method will
// use the next available JSON representation for the receiver type.
// Implementations must not retain the pointer to [jsontext.Decoder].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError],
// except for [jsontext.SyntacticError]s and IO errors.
//
// The UnmarshalJSONFrom method should not be called directly as it may
// return sentinel errors that need special handling.
// Users should instead call [UnmarshalDecode], which handles such cases.
//
// Implementations should inspect the unmarshal options from
// [jsontext.Decoder.Options] and adjust behavior to respect the options as
// necessary.
//
// The following options may be relevant to UnmarshalerFrom implementations:
//
// - [StringifyNumbers]: if the type is represented as a JSON number
//
// Several options, such as [FormatNilSliceAsNull], apply only to native Go
// types. Thus, these options are typically not directly relevant to
// UnmarshalerFrom implementations. However, types representing a composite
// type should unmarshal contained types using [UnmarshalDecode] to ensure
// these options apply to the contained types. Similarly, [WithUnmarshalers]
// may influence unmarshaling of any contained type within a composite type.
//
// All other options are automatically handled outside of the UnmarshalerFrom
// implementation, and thus are not relevant to implementations.
type UnmarshalerFrom = json.UnmarshalerFrom
// ErrUnknownName indicates that a JSON object member could not be
@@ -771,8 +760,8 @@ type UnmarshalerFrom = json.UnmarshalerFrom
// 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 {
// serr, ok := errors.AsType[*json.SemanticError](err)
// if ok && serr.Err == json.ErrUnknownName {
// ptr := serr.JSONPointer // JSON pointer to unknown name
// name := ptr.LastToken() // unknown name itself
// ...
@@ -782,7 +771,12 @@ type UnmarshalerFrom = json.UnmarshalerFrom
var ErrUnknownName = json.ErrUnknownName
// SemanticError describes an error determining the meaning
// of JSON data as Go data or vice-versa.
// of JSON data as Go data, or vice versa.
//
// If a [Marshaler], [MarshalerTo], [Unmarshaler], or [UnmarshalerFrom] method
// returns a SemanticError when called by the [json] package,
// then the ByteOffset, JSONPointer, and GoType fields are automatically
// populated by the calling context if they are the zero value.
//
// The contents of this error as produced by this package may change over time.
type SemanticError = json.SemanticError
@@ -796,11 +790,11 @@ type SemanticError = json.SemanticError
// [encoding/json/jsontext.Options]. Options from the other packages can
// be used interchangeably with functionality in this package.
//
// Options represent either a singular option or a set of options.
// It can be functionally thought of as a Go map of option properties
// An Options value represents either a single option or a set of options.
// It can be thought of as a Go map of option properties
// (even though the underlying implementation avoids Go maps for performance).
//
// The constructors (e.g., [Deterministic]) return a singular option value:
// The constructors (e.g., [Deterministic]) return a value for a single option:
//
// opt := Deterministic(true)
//
@@ -808,7 +802,7 @@ type SemanticError = json.SemanticError
//
// opt := Options{"Deterministic": true}
//
// [JoinOptions] composes multiple options values to together:
// [JoinOptions] composes multiple options values together:
//
// out := JoinOptions(opts...)
//
@@ -821,7 +815,7 @@ type SemanticError = json.SemanticError
// }
// }
//
// [GetOption] looks up the value of options parameter:
// [GetOption] looks up the value of an options parameter:
//
// v, ok := GetOption(opts, Deterministic)
//
@@ -838,7 +832,6 @@ type SemanticError = json.SemanticError
// - [FormatNilMapAsNull] affects marshaling only
// - [OmitZeroStructFields] affects marshaling only
// - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling
// - [DiscardUnknownMembers] affects marshaling only
// - [RejectUnknownMembers] affects unmarshaling only
// - [WithMarshalers] affects marshaling only
// - [WithUnmarshalers] affects unmarshaling only
@@ -854,6 +847,7 @@ func JoinOptions(srcs ...Options) Options {
// GetOption returns the value stored in opts with the provided setter,
// reporting whether the value is present.
// If not present, the returned value is the zero value for type T.
//
// Example usage:
//
@@ -868,18 +862,33 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
}
// DefaultOptionsV2 is the full set of all options that define v2 semantics.
// 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.
// It is equivalent to the set of options in [encoding/json.DefaultOptionsV1]
// all being set to false. All other options are not present.
func DefaultOptionsV2() Options {
return json.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 are parsed from a JSON string
// StringifyNumbers specifies that types that would normally be
// encoded as a JSON number instead be encoded as a JSON string
// containing the equivalent JSON number value.
// When unmarshaling, the value is parsed from a JSON string
// containing the JSON number without any surrounding whitespace.
//
// Specifying the `string` tag option on a Go struct field applies this option
// to the top-level JSON value for that field. When applied via the `string`
// tag option, StringifyNumbers option does not recursively apply to nested
// JSON numbers within a JSON object or array.
//
// Like all options, explicitly specifying this option in a call to [Marshal],
// [Unmarshal], etc, will apply recursively.
//
// A Go type with custom marshal/unmarshal that represents a JSON number
// should respect the StringifyNumbers option and if specified
// serialize as a JSON number within a JSON string.
// Custom marshal/unmarshal should handle nested JSON objects using
// [MarshalEncode]/[UnmarshalDecode], which will automatically apply the
// non-recursive `string` tag option behavior.
//
// 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.
// This may cause decoders to lose precision for int64 and uint64 types.
@@ -890,11 +899,18 @@ func StringifyNumbers(v bool) Options {
return json.StringifyNumbers(v)
}
// Deterministic specifies that the same input value will be serialized
// as the exact same output bytes. Different processes of
// the same program will serialize equal values to the same bytes,
// but different versions of the same program are not guaranteed
// to produce the exact same sequence of bytes.
// Deterministic specifies that marshaling the same input value will always
// serialize as the same output bytes.
//
// For example, Go maps are marshaled sorted by key.
//
// For native Go types, Determinism is guaranteed across different instances of
// identical binaries, but not across different builds of a program (such as
// different source or toolchain version, different GOOS/GOARCH, different
// build flags).
//
// A Go type with a custom marshaler should also respect the Deterministic
// option and serialize deterministically if it is true.
//
// This only affects marshaling and is ignored when unmarshaling.
func Deterministic(v bool) Options {
@@ -904,8 +920,6 @@ func Deterministic(v bool) Options {
// FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
// JSON null instead of the default representation as an empty JSON array
// (or an empty JSON string in the case of ~[]byte).
// Slice fields explicitly marked with `format:emitempty` still marshal
// as an empty JSON array.
//
// This only affects marshaling and is ignored when unmarshaling.
func FormatNilSliceAsNull(v bool) Options {
@@ -914,19 +928,17 @@ func FormatNilSliceAsNull(v bool) Options {
// FormatNilMapAsNull specifies that a nil Go map should marshal as a
// JSON null instead of the default representation as an empty JSON object.
// Map fields explicitly marked with `format:emitempty` still marshal
// as an empty JSON object.
//
// This only affects marshaling and is ignored when unmarshaling.
func FormatNilMapAsNull(v bool) Options {
return json.FormatNilMapAsNull(v)
}
// 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
// OmitZeroStructFields specifies that zero-valued fields of Go struct should be
// omitted from the marshaled output.
// A value is considered zero if its type has an "IsZero() bool" method that returns true,
// or if it lacks such a method and the value is a Go zero value.
// This option is equivalent to specifying the `omitzero` tag option
// on every field in a Go struct.
//
// This only affects marshaling and is ignored when unmarshaling.
@@ -936,30 +948,31 @@ func OmitZeroStructFields(v bool) Options {
// MatchCaseInsensitiveNames specifies that JSON object members are matched
// against Go struct fields using a case-insensitive match of the name.
// If a name matches multiple fields, the field whose name matches exactly is chosen.
// If there is none, an error is reported.
// 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.
//
// This affects either marshaling or unmarshaling.
// For marshaling, this option may alter the detection of duplicate names
// (assuming [jsontext.AllowDuplicateNames] is false) from inlined fields
// if it matches one of the declared fields in the Go struct.
//
// Matching names case-insensitively also affects duplicate name detection
// (assuming [jsontext.AllowDuplicateNames] is false) since
// variations of the same name may match the same Go struct field.
// For example, when unmarshaling, the names "foo" and "Foo" may both
// match the same Go struct field and therefore be considered a duplicate name.
// When marshaling, normally it is impossible for any two Go struct fields to
// serialize in a way where they unmarshal into the same Go struct field
// since they all have unique exact names.
// However, it is possible for an
// embedded fallback to contain a name that also matches the name for
// a Go struct field, resulting in a duplicate name error.
func MatchCaseInsensitiveNames(v bool) Options {
return json.MatchCaseInsensitiveNames(v)
}
// DiscardUnknownMembers specifies that marshaling should ignore any
// JSON object members stored in Go struct fields dedicated to storing
// unknown JSON object members.
//
// This only affects marshaling and is ignored when unmarshaling.
func DiscardUnknownMembers(v bool) Options {
return json.DiscardUnknownMembers(v)
}
// RejectUnknownMembers specifies that unknown members should be rejected
// when unmarshaling a JSON object, regardless of whether there is a field
// to store unknown members.
// when unmarshaling a JSON object.
//
// This only affects unmarshaling and is ignored when marshaling.
func RejectUnknownMembers(v bool) Options {
+106 -118
View File
@@ -9,10 +9,9 @@ package json
import (
"bytes"
"encoding"
"errors"
"io"
"reflect"
"slices"
"strings"
"sync"
"time"
@@ -32,6 +31,36 @@ var (
_ time.Duration
)
var (
// Once a JSON object has begun processing without duplicate name verification,
// it does not track the history of names that have been seen so far.
// Reject changing the setting for the current JSON object namespace,
// otherwise we would be operating with inconsistent state.
// Note that you can change the setting before processing the start
// of a different JSON object.
//
// TODO: We could loosen this restriction in certain conditions.
// If we are already checking for duplicate names,
// we can momentarily disable it for the next JSON object member name.
// However, if we are already NOT checking for duplicate names,
// we cannot momentarily enable it for the next JSON object member name
// since we already lack prior history of JSON object names.
errChangingDuplicateNames = errors.New("cannot change duplicate name checks after a JSON object has already begun processing")
// The presence of invalid UTF-8 has an interesting intersection
// with checking for duplicate names. Due to the semantic of mangling
// invalid UTF-8 as the Unicode replacement character,
// two string keys in a Go map (both with invalid UTF-8)
// may encode as the same JSON string. Thus, we forbid changing of
// invalid UTF-8 checks in the current JSON object namespace.
errChangingInvalidUTF8 = errors.New("cannot change UTF-8 checks after a JSON object has already begun processing")
// TODO(https://go.dev/issue/79559): Changing whitespace currently
// leads to strange effects and will need more careful adjustment.
// For now, we just report an error.
errChangingWhitespace = errors.New("cannot change whitespace formatting within a MarshalEncode call")
)
// export exposes internal functionality of the "jsontext" package.
var export = jsontext.Internal.Export(&internal.AllowInternalUse)
@@ -44,19 +73,21 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
// Functions or methods that operate on *T are only called when encoding
// a value of type T (by taking its address) or a non-nil value of *T.
// Marshal ensures that a value is always addressable
// (by boxing it on the heap if necessary) so that
// (by copying the value if necessary) so that
// these functions and methods can be consistently called. For performance,
// it is recommended that Marshal be passed a non-nil pointer to the value.
//
// The input value is encoded as JSON according the following rules:
// The input value is encoded as JSON according to the following rules:
//
// - If any type-specific functions in a [WithMarshalers] option match
// the value type, then those functions are called to encode the value.
// If all applicable functions return [SkipFunc],
// If all applicable functions return [errors.ErrUnsupported],
// then the value is encoded according to subsequent rules.
//
// - If the value type implements [MarshalerTo],
// then the MarshalJSONTo method is called to encode the value.
// If the method returns [errors.ErrUnsupported],
// then the input is encoded according to subsequent rules.
//
// - If the value type implements [Marshaler],
// then the MarshalJSON method is called to encode the value.
@@ -72,51 +103,31 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
// - Otherwise, the value is encoded according to the value's type
// as described in detail below.
//
// Most Go types have a default JSON representation.
// Certain types support specialized formatting according to
// a format flag optionally specified in the Go struct tag
// for the struct field that contains the current value
// (see the “JSON Representation of Go structs” section for more details).
//
// The representation of each type is as follows:
// Most Go types have a default JSON representation as follows:
//
// - A Go boolean is encoded as a JSON boolean (e.g., true or false).
// It does not support any custom format flags.
//
// - A Go string is encoded as a JSON string.
// It does not support any custom format flags.
//
// - A Go []byte or [N]byte is encoded as a JSON string containing
// the binary value encoded using RFC 4648.
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
// If the format is "base64url", then this uses RFC 4648, section 5.
// If the format is "base32", then this uses RFC 4648, section 6.
// If the format is "base32hex", then this uses RFC 4648, section 7.
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
// If the format is "array", then the bytes value is encoded as a JSON array
// where each byte is recursively JSON-encoded as each JSON array element.
// a binary value using Base 64 Encoding per RFC 4648, section 4.
//
// - A Go integer is encoded as a JSON number without fractions or exponents.
// 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 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.
// Otherwise, the presence of non-finite numbers results in a [SemanticError].
// Encoding a NaN or ±Inf results in a [SemanticError].
//
// - 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]. 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.
// Otherwise by default, a nil map is encoded as an empty JSON object.
// By default, a nil map is encoded as an empty JSON object,
// unless the [FormatNilMapAsNull] option is specified.
//
// - A Go struct is encoded as a JSON object.
// See the “JSON Representation of Go structs” section
@@ -124,53 +135,33 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
//
// - A Go slice is encoded as a JSON array, where each Go slice element
// is recursively JSON-encoded as the elements of the JSON array.
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
// regardless of whether [FormatNilSliceAsNull] is specified.
// Otherwise by default, a nil slice is encoded as an empty JSON array.
// By default, a nil slice is encoded as an empty JSON array,
// unless the [FormatNilSliceAsNull] option is specified.
//
// - A Go array is encoded as a JSON array, where each Go array element
// is recursively JSON-encoded as the elements of the JSON array.
// The JSON array length is always identical to the Go array length.
// It does not support any custom format flags.
//
// - A Go pointer is encoded as a JSON null if nil, otherwise it is
// the recursively JSON-encoded representation of the underlying value.
// Format flags are forwarded to the encoding of the underlying value.
//
// - A Go interface is encoded as a JSON null if nil, otherwise it is
// the recursively JSON-encoded representation of the underlying value.
// It does not support any custom format flags.
//
// - A Go [time.Time] is encoded as a JSON string containing the timestamp
// formatted in RFC 3339 with nanosecond precision.
// If the format matches one of the format constants declared
// in the time package (e.g., RFC1123), then that format is used.
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
// then the timestamp is encoded as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
// To avoid a fractional component, round the timestamp to the relevant unit.
// Otherwise, the format is used as-is with [time.Time.Format] if non-empty.
//
// - A Go [time.Duration] currently has no default representation and
// requires an explicit format to be specified.
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is encoded as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
// To avoid a fractional component, round the duration to the relevant unit.
// If the format is "units", it is encoded as a JSON string formatted using
// [time.Duration.String] (e.g., "1h30m" for 1 hour 30 minutes).
// If the format is "iso8601", it is encoded as a JSON string using the
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
// using only accurate units of hours, minutes, and seconds.
// results in a [SemanticError], unless the [encoding/json.FormatDurationAsNano]
// option is specified, in which case it is encoded as a JSON number
// without fractions or exponents, representing the duration in nanoseconds.
//
// - All other Go types (e.g., complex numbers, channels, and functions)
// have no default representation and result in a [SemanticError].
//
// JSON cannot represent cyclic data structures and Marshal does not handle them.
// Passing cyclic structures will result in an error.
func Marshal(in any, opts ...Options) (out []byte, err error) {
opts = mayAppendSupportFormatTag(opts)
enc := export.GetBufferedEncoder(opts...)
defer export.PutBufferedEncoder(enc)
xe := export.Encoder(enc)
@@ -187,6 +178,7 @@ func Marshal(in any, opts ...Options) (out []byte, err error) {
// It does not terminate the output with a newline.
// See [Marshal] for details about the conversion of a Go value into JSON.
func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
enc := export.GetStreamingEncoder(out, opts...)
defer export.PutStreamingEncoder(enc)
xe := export.Encoder(enc)
@@ -199,19 +191,34 @@ func MarshalWrite(out io.Writer, in any, opts ...Options) (err error) {
}
// 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].
// the provided marshal or encode options (while ignoring unmarshal or decode options).
// The options provided take precedence over options already applied on
// the [jsontext.Encoder] and only apply for the duration of the marshal call.
//
// See [Marshal] for details about the conversion of a Go value into JSON.
func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
xe := export.Encoder(out)
if len(opts) > 0 {
optsOriginal := xe.Struct
defer func() { xe.Struct = optsOriginal }()
xe.Struct.JoinWithoutCoderOptions(opts...)
xe.Struct.Join(opts...)
if xe.Tokens.Last.NeedObjectName() {
if optsOriginal.Flags.Get(jsonflags.AllowDuplicateNames) != xe.Struct.Flags.Get(jsonflags.AllowDuplicateNames) {
return newMarshalErrorBefore(out, reflect.TypeOf(in), errChangingDuplicateNames)
}
if optsOriginal.Flags.Get(jsonflags.AllowInvalidUTF8) != xe.Struct.Flags.Get(jsonflags.AllowInvalidUTF8) {
return newMarshalErrorBefore(out, reflect.TypeOf(in), errChangingInvalidUTF8)
}
}
if xe.Struct.Flags.Has(jsonflags.AnyWhitespace) {
if xe.Struct.Flags.Get(jsonflags.Multiline) {
xe.Struct.InitializeMultiline()
}
if jsonopts.ChangedWhitespace(optsOriginal, xe.Struct) {
return newMarshalErrorBefore(out, reflect.TypeOf(in), errChangingWhitespace)
}
}
}
err = marshalEncode(out, in, &xe.Struct)
if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
@@ -260,18 +267,24 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// Functions or methods that operate on *T are only called when decoding
// a value of type T (by taking its address) or a non-nil value of *T.
// Unmarshal ensures that a value is always addressable
// (by boxing it on the heap if necessary) so that
// (by copying the value if necessary) so that
// these functions and methods can be consistently called.
// If a value must be shallow copied to call a pointer-receiver
// [Unmarshaler], [UnmarshalerFrom], or [encoding.TextUnmarshaler] method,
// then any mutations performed by the method are shallow copied back
// into the destination value.
//
// The input is decoded into the output according the following rules:
// The input is decoded into the output according to the following rules:
//
// - If any type-specific functions in a [WithUnmarshalers] option match
// the value type, then those functions are called to decode the JSON
// value. If all applicable functions return [SkipFunc],
// value. If all applicable functions return [errors.ErrUnsupported],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [UnmarshalerFrom],
// then the UnmarshalJSONFrom method is called to decode the JSON value.
// If the method returns [errors.ErrUnsupported],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [Unmarshaler],
// then the UnmarshalJSON method is called to decode the JSON value.
@@ -285,10 +298,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// as described in detail below.
//
// Most Go types have a default JSON representation.
// Certain types support specialized formatting according to
// a format flag optionally specified in the Go struct tag
// for the struct field that contains the current value
// (see the “JSON Representation of Go structs” section for more details).
// A JSON null may be decoded into every supported Go value where
// it is equivalent to storing the zero value of the Go value.
// If the input JSON kind is not handled by the current Go value type,
@@ -298,20 +307,11 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// The representation of each type is as follows:
//
// - A Go boolean is decoded from a JSON boolean (e.g., true or false).
// It does not support any custom format flags.
//
// - A Go string is decoded from a JSON string.
// It does not support any custom format flags.
//
// - A Go []byte or [N]byte is decoded from a JSON string
// containing the binary value encoded using RFC 4648.
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
// If the format is "base64url", then this uses RFC 4648, section 5.
// If the format is "base32", then this uses RFC 4648, section 6.
// If the format is "base32hex", then this uses RFC 4648, section 7.
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
// If the format is "array", then the Go slice or array is decoded from a
// JSON array where each JSON element is recursively decoded for each byte.
// - A Go []byte or [N]byte is decoded from a JSON string containing
// a binary value using Base 64 Encoding per RFC 4648, section 4.
// When decoding into a non-nil []byte, the slice length is reset to zero
// and the decoded input is appended to it.
// When decoding into a [N]byte, the input must decode to exactly N bytes,
@@ -323,15 +323,13 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// 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 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].
// Since JSON lacks a native representation for a NaN or ±Inf,
// such values cannot be the result of decoding.
//
// - A Go map is decoded from a JSON object,
// where each JSON object name and value pair is recursively decoded
@@ -339,7 +337,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// 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.
// The formats "emitnull" and "emitempty" have no effect when decoding.
//
// - A Go struct is decoded from a JSON object.
// See the “JSON Representation of Go structs” section
@@ -349,20 +346,17 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// is recursively decoded and appended to the Go slice.
// Before appending into a Go slice, a new slice is allocated if it is nil,
// otherwise the slice length is reset to zero.
// The formats "emitnull" and "emitempty" have no effect when decoding.
//
// - A Go array is decoded from a JSON array, where each JSON array element
// is recursively decoded as each corresponding Go array element.
// Each Go array element is zeroed before decoding into it.
// It fails with a [SemanticError] if the JSON array does not contain
// the exact same number of elements as the Go array.
// It does not support any custom format flags.
//
// - A Go pointer is decoded based on the JSON kind and underlying Go type.
// If the input is a JSON null, then this stores a nil pointer.
// Otherwise, it allocates a new underlying value if the pointer is nil,
// and recursively JSON decodes into the underlying value.
// Format flags are forwarded to the decoding of the underlying type.
//
// - A Go interface is decoded based on the JSON kind and underlying Go type.
// If the input is a JSON null, then this stores a nil interface value.
@@ -374,28 +368,15 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// For example, unmarshaling into a nil io.Reader fails since
// there is no concrete type to populate the interface value with.
// Otherwise an underlying value exists and it recursively decodes
// the JSON input into it. It does not support any custom format flags.
// the JSON input into it.
//
// - A Go [time.Time] is decoded from a JSON string containing the time
// formatted in RFC 3339 with nanosecond precision.
// If the format matches one of the format constants declared in
// the time package (e.g., RFC1123), then that format is used for parsing.
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
// then the timestamp is decoded from an optionally fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
// Otherwise, the format is used as-is with [time.Time.Parse] if non-empty.
//
// - A Go [time.Duration] currently has no default representation and
// requires an explicit format to be specified.
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is decoded from an optionally fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
// If the format is "units", it is decoded from a JSON string parsed using
// [time.ParseDuration] (e.g., "1h30m" for 1 hour 30 minutes).
// If the format is "iso8601", it is decoded from a JSON string using the
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
// accepting only accurate units of hours, minutes, or seconds.
// results in a [SemanticError], unless the [encoding/json.FormatDurationAsNano]
// option is specified, in which case it is decoded as a JSON number
// without fractions or exponents, representing the duration in nanoseconds.
//
// - All other Go types (e.g., complex numbers, channels, and functions)
// have no default representation and result in a [SemanticError].
@@ -406,6 +387,7 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// For JSON objects, the input object is merged into the destination value
// where matching object members recursively apply merge semantics.
func Unmarshal(in []byte, out any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
dec := export.GetBufferedDecoder(in, opts...)
defer export.PutBufferedDecoder(dec)
xd := export.Decoder(dec)
@@ -423,6 +405,7 @@ func Unmarshal(in []byte, out any, opts ...Options) (err error) {
// without reporting an error for EOF. The output must be a non-nil pointer.
// See [Unmarshal] for details about the conversion of JSON into a Go value.
func UnmarshalRead(in io.Reader, out any, opts ...Options) (err error) {
opts = mayAppendSupportFormatTag(opts)
dec := export.GetStreamingDecoder(in, opts...)
defer export.PutStreamingDecoder(dec)
xd := export.Decoder(dec)
@@ -434,22 +417,30 @@ func UnmarshalRead(in io.Reader, out any, opts ...Options) (err 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 provided unmarshal or decode options (while ignoring marshal or encode options).
// The options provided take precedence over options already applied on
// the [jsontext.Decoder] and only apply for the duration of the unmarshal call.
//
// The input may be a stream of one or more JSON values,
// where this only unmarshals the next JSON value in the stream.
// The input may be a stream of zero or more JSON values.
// UnmarshalDecode unmarshals only the next JSON value in the stream.
// If there are no more top-level JSON values, it reports [io.EOF].
// 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) {
opts = mayAppendSupportFormatTag(opts)
xd := export.Decoder(in)
if len(opts) > 0 {
optsOriginal := xd.Struct
defer func() { xd.Struct = optsOriginal }()
xd.Struct.JoinWithoutCoderOptions(opts...)
xd.Struct.Join(opts...)
if xd.Tokens.Last.NeedObjectName() {
if optsOriginal.Flags.Get(jsonflags.AllowDuplicateNames) != xd.Struct.Flags.Get(jsonflags.AllowDuplicateNames) {
return newUnmarshalErrorBefore(in, reflect.TypeOf(out), errChangingDuplicateNames)
}
if optsOriginal.Flags.Get(jsonflags.AllowInvalidUTF8) != xd.Struct.Flags.Get(jsonflags.AllowInvalidUTF8) {
return newUnmarshalErrorBefore(in, reflect.TypeOf(out), errChangingInvalidUTF8)
}
}
}
err = unmarshalDecode(in, out, &xd.Struct, false)
if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
@@ -523,7 +514,7 @@ func newAddressableValue(t reflect.Type) addressableValue {
// 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
// The *jsonopts.Struct argument is guaranteed to be identical to or at least
// a strict super-set of the options in Encoder.Struct or Decoder.Struct.
// It is identical for Marshal, Unmarshal, MarshalWrite, and UnmarshalRead.
// It is a super-set for MarshalEncode and UnmarshalDecode.
@@ -572,9 +563,6 @@ func putStrings(s *stringSlice) {
if cap(*s) > 1<<10 {
*s = nil // avoid pinning arbitrarily large amounts of memory
}
clear(*s) // avoid pinning a reference to each string
stringsPools.Put(s)
}
func (ss *stringSlice) Sort() {
slices.SortFunc(*ss, func(x, y string) int { return strings.Compare(x, y) })
}
+21 -15
View File
@@ -8,7 +8,10 @@ package json
import (
"cmp"
"errors"
"math"
"reflect"
"slices"
"strconv"
"github.com/go-json-experiment/json/internal"
@@ -35,20 +38,23 @@ func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error
case string:
return enc.WriteToken(jsontext.String(val))
case float64:
if math.IsNaN(val) || math.IsInf(val, 0) {
break // use default logic below
}
return enc.WriteToken(jsontext.Float(val))
case map[string]any:
return marshalObjectAny(enc, val, mo)
case []any:
return marshalArrayAny(enc, val, mo)
default:
v := newAddressableValue(reflect.TypeOf(val))
v.Set(reflect.ValueOf(val))
marshal := lookupArshaler(v.Type()).marshal
if mo.Marshalers != nil {
marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type())
}
return marshal(enc, v, mo)
}
v := newAddressableValue(reflect.TypeOf(val))
v.Set(reflect.ValueOf(val))
marshal := lookupArshaler(v.Type()).marshal
if mo.Marshalers != nil {
marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type())
}
return marshal(enc, v, mo)
}
// unmarshalValueAny unmarshals a JSON value as a Go any.
@@ -85,9 +91,9 @@ func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error)
if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) {
return internal.RawNumberOf(val), nil
}
fv, ok := jsonwire.ParseFloat(val, 64)
if !ok {
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange)
fv, err := strconv.ParseFloat(string(val), 64)
if err != nil {
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, errors.Unwrap(err))
}
return fv, nil
default:
@@ -104,7 +110,7 @@ func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.St
if xe.Tokens.Depth() > startDetectingCyclesAfter {
v := reflect.ValueOf(obj)
if err := visitPointer(&xe.SeenPointers, v); err != nil {
return newMarshalErrorBefore(enc, anyType, err)
return newMarshalErrorBefore(enc, mapStringAnyType, err)
}
defer leavePointer(&xe.SeenPointers, v)
}
@@ -128,7 +134,7 @@ func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.St
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
return err
}
// A Go map guarantees that each entry has a unique key
// A Go map guarantees that each entry has a unique key.
// The only possibility of duplicates is due to invalid UTF-8.
if !mo.Flags.Get(jsonflags.AllowInvalidUTF8) {
xe.Tokens.Last.DisableNamespace()
@@ -149,7 +155,7 @@ func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.St
(*names)[i] = name
i++
}
names.Sort()
slices.Sort(*names)
for _, name := range *names {
if err := enc.WriteToken(jsontext.String(name)); err != nil {
return err
@@ -176,7 +182,7 @@ func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]
panic("BUG: invalid kind: " + tok.Kind().String())
}
obj := make(map[string]any)
// A Go map guarantees that each entry has a unique key
// 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()
+338 -205
View File
@@ -35,7 +35,7 @@ import (
const optimizeCommon = true
var (
// Most natural Go type that correspond with each JSON type.
// Most natural Go types that correspond with each JSON type.
anyType = reflect.TypeFor[any]() // JSON value
boolType = reflect.TypeFor[bool]() // JSON bool
stringType = reflect.TypeFor[string]() // JSON string
@@ -127,12 +127,19 @@ func makeBoolArshaler(t reflect.Type) *arshaler {
var fncs arshaler
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
var stringify bool // always false except `string` tag with StringifyWithLegacySemantics
if mo.Flags.Has(jsonflags.TagFlags) {
stringify = mo.Flags.Get(jsonflags.StringTag) && mo.Flags.Get(jsonflags.StringifyWithLegacySemantics)
if mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.StringifyWithLegacySemantics|jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
}
// Optimize for marshaling without preceding whitespace.
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() {
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !stringify && !xe.Tokens.Last.NeedObjectName() {
xe.Buf = strconv.AppendBool(xe.Tokens.MayAppendDelim(xe.Buf, 't'), va.Bool())
xe.Tokens.Last.Increment()
if xe.NeedFlush() {
@@ -141,7 +148,7 @@ func makeBoolArshaler(t reflect.Type) *arshaler {
return nil
}
if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) {
if stringify {
if va.Bool() {
return enc.WriteToken(jsontext.String("true"))
} else {
@@ -151,9 +158,15 @@ func makeBoolArshaler(t reflect.Type) *arshaler {
return enc.WriteToken(jsontext.Bool(va.Bool()))
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
var stringify bool // always false except `string` tag with StringifyWithLegacySemantics
if uo.Flags.Has(jsonflags.TagFlags) {
stringify = uo.Flags.Get(jsonflags.StringTag) && uo.Flags.Get(jsonflags.StringifyWithLegacySemantics)
if uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.StringifyWithLegacySemantics|jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
}
tok, err := dec.ReadToken()
if err != nil {
@@ -167,12 +180,12 @@ func makeBoolArshaler(t reflect.Type) *arshaler {
}
return nil
case 't', 'f':
if !uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) {
if !stringify {
va.SetBool(tok.Bool())
return nil
}
case '"':
if uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) {
if stringify {
switch tok.String() {
case "true":
va.SetBool(true)
@@ -190,7 +203,7 @@ func makeBoolArshaler(t reflect.Type) *arshaler {
return nil
}
}
return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil)
return newUnmarshalErrorAfterWithSkipping(dec, t, nil)
}
return &fncs
}
@@ -199,16 +212,23 @@ func makeStringArshaler(t reflect.Type) *arshaler {
var fncs arshaler
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
var stringify bool // always false except `string` tag with StringifyWithLegacySemantics
if mo.Flags.Has(jsonflags.TagFlags) {
stringify = mo.Flags.Get(jsonflags.StringTag) && mo.Flags.Get(jsonflags.StringifyWithLegacySemantics)
if mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.StringifyWithLegacySemantics|jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
}
// Optimize for marshaling without preceding whitespace.
s := va.String()
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() {
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !stringify && !xe.Tokens.Last.NeedObjectName() {
b := xe.Buf
b = xe.Tokens.MayAppendDelim(b, '"')
b, err := jsonwire.AppendQuote(b, s, &mo.Flags)
b, err := jsonwire.AppendQuote(b, []byte(s), &mo.Flags)
if err == nil {
xe.Buf = b
xe.Tokens.Last.Increment()
@@ -221,8 +241,8 @@ func makeStringArshaler(t reflect.Type) *arshaler {
// so let the logic below construct the proper error.
}
if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) {
b, err := jsonwire.AppendQuote(nil, s, &mo.Flags)
if stringify {
b, err := jsonwire.AppendQuote(nil, []byte(s), &mo.Flags)
if err != nil {
return newMarshalErrorBefore(enc, t, &jsontext.SyntacticError{Err: err})
}
@@ -236,8 +256,15 @@ func makeStringArshaler(t reflect.Type) *arshaler {
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
var stringify bool // always false except `string` tag with StringifyWithLegacySemantics
if uo.Flags.Has(jsonflags.TagFlags) {
stringify = uo.Flags.Get(jsonflags.StringTag) && uo.Flags.Get(jsonflags.StringifyWithLegacySemantics)
if uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.StringifyWithLegacySemantics|jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
}
var flags jsonwire.ValueFlags
val, err := xd.ReadValue(&flags)
@@ -253,7 +280,7 @@ func makeStringArshaler(t reflect.Type) *arshaler {
return nil
case '"':
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) {
if stringify {
val, err = jsontext.AppendUnquote(nil, val)
if err != nil {
return newUnmarshalErrorAfter(dec, t, err)
@@ -311,32 +338,37 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
}
xe := export.Encoder(enc)
appendEncode := appendEncodeBase64
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
switch mo.Format {
case "base64":
appendEncode = appendEncodeBase64
case "base64url":
appendEncode = appendEncodeBase64URL
case "base32":
appendEncode = appendEncodeBase32
case "base32hex":
appendEncode = appendEncodeBase32Hex
case "base16", "hex":
appendEncode = appendEncodeBase16
case "array":
mo.Format = ""
if mo.Flags.Has(jsonflags.TagFlags | jsonflags.FormatByteArrayAsArray | jsonflags.FormatBytesWithLegacySemantics | jsonflags.FormatNilSliceAsNull) {
switch {
case mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics):
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
case mo.Flags.Has(jsonflags.FormatTag):
switch mo.Format {
case "base64":
appendEncode = appendEncodeBase64
case "base64url":
appendEncode = appendEncodeBase64URL
case "base32":
appendEncode = appendEncodeBase32
case "base32hex":
appendEncode = appendEncodeBase32Hex
case "base16", "hex":
appendEncode = appendEncodeBase16
case "array":
mo.Flags.Clear(jsonflags.FormatTag)
return marshalArray(enc, va, mo)
default:
return newInvalidFormatError(enc, t)
}
case mo.Flags.Get(jsonflags.FormatByteArrayAsArray) && va.Kind() == reflect.Array:
return marshalArray(enc, va, mo)
case mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && hasMarshaler:
return marshalArray(enc, va, mo)
default:
return newInvalidFormatError(enc, t, mo)
}
} else if mo.Flags.Get(jsonflags.FormatByteArrayAsArray) && va.Kind() == reflect.Array {
return marshalArray(enc, va, mo)
} else if mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && hasMarshaler {
return marshalArray(enc, va, mo)
}
if mo.Flags.Get(jsonflags.FormatNilSliceAsNull) && va.Kind() == reflect.Slice && va.IsNil() {
// TODO: Provide a "emitempty" format override?
return enc.WriteToken(jsontext.Null)
if mo.Flags.Get(jsonflags.FormatNilSliceAsNull) && va.Kind() == reflect.Slice && va.IsNil() {
// TODO: Provide an "emitempty" format override?
return enc.WriteToken(jsontext.Null)
}
}
return xe.AppendRaw('"', true, func(b []byte) ([]byte, error) {
return appendEncode(b, va.Bytes()), nil
@@ -349,28 +381,33 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
}
xd := export.Decoder(dec)
appendDecode, encodedLen := appendDecodeBase64, encodedLenBase64
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
switch uo.Format {
case "base64":
appendDecode, encodedLen = appendDecodeBase64, encodedLenBase64
case "base64url":
appendDecode, encodedLen = appendDecodeBase64URL, encodedLenBase64URL
case "base32":
appendDecode, encodedLen = appendDecodeBase32, encodedLenBase32
case "base32hex":
appendDecode, encodedLen = appendDecodeBase32Hex, encodedLenBase32Hex
case "base16", "hex":
appendDecode, encodedLen = appendDecodeBase16, encodedLenBase16
case "array":
uo.Format = ""
if uo.Flags.Has(jsonflags.TagFlags | jsonflags.FormatByteArrayAsArray | jsonflags.FormatBytesWithLegacySemantics) {
switch {
case uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics):
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
case uo.Flags.Has(jsonflags.FormatTag):
switch uo.Format {
case "base64":
appendDecode, encodedLen = appendDecodeBase64, encodedLenBase64
case "base64url":
appendDecode, encodedLen = appendDecodeBase64URL, encodedLenBase64URL
case "base32":
appendDecode, encodedLen = appendDecodeBase32, encodedLenBase32
case "base32hex":
appendDecode, encodedLen = appendDecodeBase32Hex, encodedLenBase32Hex
case "base16", "hex":
appendDecode, encodedLen = appendDecodeBase16, encodedLenBase16
case "array":
uo.Flags.Clear(jsonflags.FormatTag)
return unmarshalArray(dec, va, uo)
default:
return newInvalidFormatError(dec, t)
}
case uo.Flags.Get(jsonflags.FormatByteArrayAsArray) && va.Kind() == reflect.Array:
return unmarshalArray(dec, va, uo)
case uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && dec.PeekKind() == '[':
return unmarshalArray(dec, va, uo)
default:
return newInvalidFormatError(dec, t, uo)
}
} else if uo.Flags.Get(jsonflags.FormatByteArrayAsArray) && va.Kind() == reflect.Array {
return unmarshalArray(dec, va, uo)
} else if uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && dec.PeekKind() == '[' {
return unmarshalArray(dec, va, uo)
}
var flags jsonwire.ValueFlags
val, err := xd.ReadValue(&flags)
@@ -409,7 +446,7 @@ func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler {
if va.Kind() == reflect.Array {
dst := va.Bytes()
clear(dst[copy(dst, b):]) // noop if len(b) <= len(dst)
clear(dst[copy(dst, b):]) // noop if len(b) >= len(dst)
if len(b) != len(dst) && !uo.Flags.Get(jsonflags.UnmarshalArrayFromAnyLength) {
err := fmt.Errorf("decoded length of %d mismatches array length of %d", len(b), len(dst))
return newUnmarshalErrorAfter(dec, t, err)
@@ -432,12 +469,13 @@ func makeIntArshaler(t reflect.Type) *arshaler {
bits := t.Bits()
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
stringify := xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
// Optimize for marshaling without preceding whitespace or string escaping.
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyNumbers) && !xe.Tokens.Last.NeedObjectName() {
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !stringify {
xe.Buf = strconv.AppendInt(xe.Tokens.MayAppendDelim(xe.Buf, '0'), va.Int(), 10)
xe.Tokens.Last.Increment()
if xe.NeedFlush() {
@@ -446,17 +484,17 @@ func makeIntArshaler(t reflect.Type) *arshaler {
return nil
}
k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
k := stringOrNumberKind(stringify)
return xe.AppendRaw(k, true, func(b []byte) ([]byte, error) {
return strconv.AppendInt(b, va.Int(), 10), nil
})
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
var flags jsonwire.ValueFlags
val, err := xd.ReadValue(&flags)
if err != nil {
@@ -474,10 +512,21 @@ func makeIntArshaler(t reflect.Type) *arshaler {
break
}
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetInt(0)
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
// For historical reasons, v1 parsed a quoted number
// according to the Go syntax and permitted a quoted null.
// See https://go.dev/issue/75619
n, err := strconv.ParseInt(string(val), 10, bits)
if err != nil {
if string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetInt(0)
}
return nil
}
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
}
va.SetInt(n)
return nil
}
fallthrough
@@ -519,12 +568,13 @@ func makeUintArshaler(t reflect.Type) *arshaler {
bits := t.Bits()
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
stringify := xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
// Optimize for marshaling without preceding whitespace or string escaping.
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyNumbers) && !xe.Tokens.Last.NeedObjectName() {
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !stringify {
xe.Buf = strconv.AppendUint(xe.Tokens.MayAppendDelim(xe.Buf, '0'), va.Uint(), 10)
xe.Tokens.Last.Increment()
if xe.NeedFlush() {
@@ -533,17 +583,17 @@ func makeUintArshaler(t reflect.Type) *arshaler {
return nil
}
k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
k := stringOrNumberKind(stringify)
return xe.AppendRaw(k, true, func(b []byte) ([]byte, error) {
return strconv.AppendUint(b, va.Uint(), 10), nil
})
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
var flags jsonwire.ValueFlags
val, err := xd.ReadValue(&flags)
if err != nil {
@@ -561,10 +611,21 @@ func makeUintArshaler(t reflect.Type) *arshaler {
break
}
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetUint(0)
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
// For historical reasons, v1 parsed a quoted number
// according to the Go syntax and permitted a quoted null.
// See https://go.dev/issue/75619
n, err := strconv.ParseUint(string(val), 10, bits)
if err != nil {
if string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetUint(0)
}
return nil
}
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
}
va.SetUint(n)
return nil
}
fallthrough
@@ -597,12 +658,13 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
bits := t.Bits()
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
stringify := xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
var allowNonFinite bool
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
if mo.Flags.Has(jsonflags.FormatTag) {
if mo.Format == "nonfinite" {
allowNonFinite = true
} else {
return newInvalidFormatError(enc, t, mo)
return newInvalidFormatError(enc, t)
}
}
@@ -616,7 +678,7 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
}
// Optimize for marshaling without preceding whitespace or string escaping.
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyNumbers) && !xe.Tokens.Last.NeedObjectName() {
if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !stringify {
xe.Buf = jsonwire.AppendFloat(xe.Tokens.MayAppendDelim(xe.Buf, '0'), fv, bits)
xe.Tokens.Last.Increment()
if xe.NeedFlush() {
@@ -625,22 +687,23 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
return nil
}
k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
k := stringOrNumberKind(stringify)
return xe.AppendRaw(k, true, func(b []byte) ([]byte, error) {
return jsonwire.AppendFloat(b, va.Float(), bits), nil
})
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
var allowNonFinite bool
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
if uo.Flags.Has(jsonflags.FormatTag) {
if uo.Format == "nonfinite" {
allowNonFinite = true
} else {
return newInvalidFormatError(dec, t, uo)
return newInvalidFormatError(dec, t)
}
}
stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
var flags jsonwire.ValueFlags
val, err := xd.ReadValue(&flags)
if err != nil {
@@ -671,10 +734,21 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
if !stringify {
break
}
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetFloat(0)
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
// For historical reasons, v1 parsed a quoted number
// according to the Go syntax and permitted a quoted null.
// See https://go.dev/issue/75619
n, err := strconv.ParseFloat(string(val), bits)
if err != nil {
if string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetFloat(0)
}
return nil
}
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
}
va.SetFloat(n)
return nil
}
if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil {
@@ -685,10 +759,10 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
if stringify && k == '0' {
break
}
fv, ok := jsonwire.ParseFloat(val, bits)
fv, err := strconv.ParseFloat(string(val), bits)
va.SetFloat(fv)
if !ok {
return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange)
if err != nil {
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
}
return nil
}
@@ -728,16 +802,19 @@ func makeMapArshaler(t reflect.Type) *arshaler {
}
emitNull := mo.Flags.Get(jsonflags.FormatNilMapAsNull)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
switch mo.Format {
case "emitnull":
emitNull = true
mo.Format = ""
case "emitempty":
emitNull = false
mo.Format = ""
default:
return newInvalidFormatError(enc, t, mo)
if mo.Flags.Has(jsonflags.TagFlags) {
if mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Has(jsonflags.FormatTag) {
switch mo.Format {
case "emitnull":
emitNull = true
case "emitempty":
emitNull = false
default:
return newInvalidFormatError(enc, t)
}
}
}
@@ -810,7 +887,7 @@ func makeMapArshaler(t reflect.Type) *arshaler {
k.SetIterKey(iter)
(*names)[i] = k.String()
}
names.Sort()
slices.Sort(*names)
for _, name := range *names {
if err := enc.WriteToken(jsontext.String(name)); err != nil {
return err
@@ -877,12 +954,16 @@ func makeMapArshaler(t reflect.Type) *arshaler {
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
switch uo.Format {
case "emitnull", "emitempty":
uo.Format = "" // only relevant for marshaling
default:
return newInvalidFormatError(dec, t, uo)
if uo.Flags.Has(jsonflags.TagFlags) {
if uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Has(jsonflags.FormatTag) {
switch uo.Format {
case "emitnull", "emitempty":
default:
return newInvalidFormatError(dec, t)
}
}
}
tok, err := dec.ReadToken()
@@ -992,7 +1073,7 @@ func makeMapArshaler(t reflect.Type) *arshaler {
}
return errUnmarshal
}
return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil)
return newUnmarshalErrorAfterWithSkipping(dec, t, nil)
}
return &fncs
}
@@ -1000,7 +1081,7 @@ func makeMapArshaler(t reflect.Type) *arshaler {
// mapKeyWithUniqueRepresentation reports whether all possible values of k
// marshal to a different JSON value, and whether all possible JSON values
// that can unmarshal into k unmarshal to different Go values.
// In other words, the representation must be a bijective.
// In other words, the representation must be a bijection.
func mapKeyWithUniqueRepresentation(k reflect.Kind, allowInvalidUTF8 bool) bool {
switch k {
case reflect.Bool,
@@ -1009,7 +1090,7 @@ func mapKeyWithUniqueRepresentation(k reflect.Kind, allowInvalidUTF8 bool) bool
return true
case reflect.String:
// For strings, we have to be careful since names with invalid UTF-8
// maybe unescape to the same Go string value.
// may unescape to the same Go string value.
return !allowInvalidUTF8
default:
// Floating-point kinds are not listed above since NaNs
@@ -1019,6 +1100,7 @@ func mapKeyWithUniqueRepresentation(k reflect.Kind, allowInvalidUTF8 bool) bool
}
var errNilField = errors.New("cannot set embedded pointer to unexported struct type")
var errInvalidStringTag = errors.New("invalid use of `string` tag option")
func makeStructArshaler(t reflect.Type) *arshaler {
// NOTE: The logic below disables namespaces for tracking duplicate names
@@ -1036,13 +1118,21 @@ func makeStructArshaler(t reflect.Type) *arshaler {
}
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
if mo.Flags.Has(jsonflags.TagFlags) {
if mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
}
once.Do(init)
if errInit != nil && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, errInit.GoType, errInit.Err)
} else if fields.errUnsupportedFormat != nil && !mo.Flags.Get(jsonflags.FormatTagSupported) {
return newMarshalErrorBefore(enc, fields.errUnsupportedFormat.GoType, fields.errUnsupportedFormat.Err)
}
if err := enc.WriteToken(jsontext.BeginObject); err != nil {
return err
}
@@ -1055,7 +1145,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
if len(f.index) > 0 {
v = v.fieldByIndex(f.index, false)
if !v.IsValid() {
continue // implies a nil inlined field
continue // implies a nil embedded field
}
}
@@ -1115,7 +1205,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
if !f.nameNeedEscape {
b = append(b, f.quotedName...)
} else {
b, _ = jsonwire.AppendQuote(b, f.name, &mo.Flags)
b, _ = jsonwire.AppendQuote(b, []byte(f.name), &mo.Flags)
}
xe.Buf = b
xe.Names.ReplaceLastQuotedOffset(n0)
@@ -1129,14 +1219,10 @@ func makeStructArshaler(t reflect.Type) *arshaler {
// Write the object member value.
flagsOriginal := mo.Flags
if f.string {
if !mo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
mo.Flags.Set(jsonflags.StringifyNumbers | 1)
} else if canLegacyStringify(f.typ) {
mo.Flags.Set(jsonflags.StringifyNumbers | jsonflags.StringifyBoolsAndStrings | 1)
}
mo.Flags.Set(jsonflags.StringTag | 1)
}
if f.format != "" {
mo.FormatDepth = xe.Tokens.Depth()
mo.Flags.Set(jsonflags.FormatTag | 1)
mo.Format = f.format
}
err := marshal(enc, v, mo)
@@ -1159,17 +1245,17 @@ func makeStructArshaler(t reflect.Type) *arshaler {
// Remember the previous written object member.
// The set of seen fields only needs to be updated to detect
// duplicate names with those from the inlined fallback.
if !mo.Flags.Get(jsonflags.AllowDuplicateNames) && fields.inlinedFallback != nil {
// duplicate names with those from the embedded fallback.
if !mo.Flags.Get(jsonflags.AllowDuplicateNames) && fields.embeddedFallback != nil {
seenIdxs.insert(uint(f.id))
}
prevIdx = f.id
}
if fields.inlinedFallback != nil && !(mo.Flags.Get(jsonflags.DiscardUnknownMembers) && fields.inlinedFallback.unknown) {
if fields.embeddedFallback != nil {
var insertUnquotedName func([]byte) bool
if !mo.Flags.Get(jsonflags.AllowDuplicateNames) {
insertUnquotedName = func(name []byte) bool {
// Check that the name from inlined fallback does not match
// Check that the name from embedded fallback does not match
// one of the previously marshaled names from known fields.
if foldedFields := fields.lookupByFoldedName(name); len(foldedFields) > 0 {
if f := fields.byActualName[string(name)]; f != nil {
@@ -1183,11 +1269,11 @@ func makeStructArshaler(t reflect.Type) *arshaler {
}
// Check that the name does not match any other name
// previously marshaled from the inlined fallback.
// previously marshaled from the embedded fallback.
return xe.Namespaces.Last().InsertUnquoted(name)
}
}
if err := marshalInlinedFallbackAll(enc, va, mo, fields.inlinedFallback, insertUnquotedName); err != nil {
if err := marshalEmbeddedFallbackAll(enc, va, mo, fields.embeddedFallback, insertUnquotedName); err != nil {
return err
}
}
@@ -1198,8 +1284,13 @@ func makeStructArshaler(t reflect.Type) *arshaler {
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
if uo.Flags.Has(jsonflags.TagFlags) {
if uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
}
tok, err := dec.ReadToken()
if err != nil {
@@ -1216,7 +1307,10 @@ func makeStructArshaler(t reflect.Type) *arshaler {
once.Do(init)
if errInit != nil && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorAfter(dec, errInit.GoType, errInit.Err)
} else if fields.errUnsupportedFormat != nil && !uo.Flags.Get(jsonflags.FormatTagSupported) {
return newUnmarshalErrorAfter(dec, fields.errUnsupportedFormat.GoType, fields.errUnsupportedFormat.Err)
}
var seenIdxs uintSet
xd.Tokens.Last.DisableNamespace()
var errUnmarshal error
@@ -1230,14 +1324,20 @@ func makeStructArshaler(t reflect.Type) *arshaler {
name := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
f := fields.byActualName[string(name)]
if f == nil {
var numMatch int
for _, f2 := range fields.lookupByFoldedName(name) {
if f2.matchFoldedName(name, &uo.Flags) {
f = f2
break
if f == nil {
f = f2 // use first seen name by breadth-first order
}
numMatch++
}
}
if numMatch > 1 && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorAfter(dec, t, errAmbiguousName)
}
if f == nil {
if uo.Flags.Get(jsonflags.RejectUnknownMembers) && (fields.inlinedFallback == nil || fields.inlinedFallback.unknown) {
if uo.Flags.Get(jsonflags.RejectUnknownMembers) && fields.embeddedFallback == nil {
err := newUnmarshalErrorAfter(dec, t, ErrUnknownName)
if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err
@@ -1249,14 +1349,14 @@ func makeStructArshaler(t reflect.Type) *arshaler {
return newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(val))
}
if fields.inlinedFallback == nil {
if fields.embeddedFallback == nil {
// Skip unknown value since we have no place to store it.
if err := dec.SkipValue(); err != nil {
return err
}
} else {
// Marshal into value capable of storing arbitrary object members.
if err := unmarshalInlinedFallbackNext(dec, va, uo, fields.inlinedFallback, val, name); err != nil {
// Unmarshal into a value capable of storing arbitrary object members.
if err := unmarshalEmbeddedFallbackNext(dec, va, uo, fields.embeddedFallback, val, name); err != nil {
if isFatalError(err, uo.Flags) {
return err
}
@@ -1278,14 +1378,10 @@ func makeStructArshaler(t reflect.Type) *arshaler {
}
flagsOriginal := uo.Flags
if f.string {
if !uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
uo.Flags.Set(jsonflags.StringifyNumbers | 1)
} else if canLegacyStringify(f.typ) {
uo.Flags.Set(jsonflags.StringifyNumbers | jsonflags.StringifyBoolsAndStrings | 1)
}
uo.Flags.Set(jsonflags.StringTag | 1)
}
if f.format != "" {
uo.FormatDepth = xd.Tokens.Depth()
uo.Flags.Set(jsonflags.FormatTag | 1)
uo.Format = f.format
}
v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable
@@ -1317,7 +1413,7 @@ func makeStructArshaler(t reflect.Type) *arshaler {
}
return errUnmarshal
}
return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil)
return newUnmarshalErrorAfterWithSkipping(dec, t, nil)
}
return &fncs
}
@@ -1366,25 +1462,6 @@ func isLegacyEmpty(v addressableValue) bool {
return false
}
// canLegacyStringify reports whether t can be stringified according to v1,
// where t is a bool, string, or number (or unnamed pointer to such).
// In v1, the `string` option does not apply recursively to nested types within
// a composite Go type (e.g., an array, slice, struct, map, or interface).
func canLegacyStringify(t reflect.Type) bool {
// Based on encoding/json.typeFields#L1126-L1143@v1.23.0
if t.Name() == "" && t.Kind() == reflect.Ptr {
t = t.Elem()
}
switch t.Kind() {
case reflect.Bool, reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64:
return true
}
return false
}
func makeSliceArshaler(t reflect.Type) *arshaler {
var fncs arshaler
var (
@@ -1405,16 +1482,19 @@ func makeSliceArshaler(t reflect.Type) *arshaler {
}
emitNull := mo.Flags.Get(jsonflags.FormatNilSliceAsNull)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
switch mo.Format {
case "emitnull":
emitNull = true
mo.Format = ""
case "emitempty":
emitNull = false
mo.Format = ""
default:
return newInvalidFormatError(enc, t, mo)
if mo.Flags.Has(jsonflags.TagFlags) {
if mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Has(jsonflags.FormatTag) {
switch mo.Format {
case "emitnull":
emitNull = true
case "emitempty":
emitNull = false
default:
return newInvalidFormatError(enc, t)
}
}
}
@@ -1456,13 +1536,16 @@ func makeSliceArshaler(t reflect.Type) *arshaler {
}
emptySlice := reflect.MakeSlice(t, 0, 0)
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
switch uo.Format {
case "emitnull", "emitempty":
uo.Format = "" // only relevant for marshaling
default:
return newInvalidFormatError(dec, t, uo)
if uo.Flags.Has(jsonflags.TagFlags) {
if uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Has(jsonflags.FormatTag) {
switch uo.Format {
case "emitnull", "emitempty":
default:
return newInvalidFormatError(dec, t)
}
}
}
@@ -1518,7 +1601,7 @@ func makeSliceArshaler(t reflect.Type) *arshaler {
}
return errUnmarshal
}
return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil)
return newUnmarshalErrorAfterWithSkipping(dec, t, nil)
}
return &fncs
}
@@ -1537,9 +1620,13 @@ func makeArrayArshaler(t reflect.Type) *arshaler {
}
n := t.Len()
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
if mo.Flags.Has(jsonflags.TagFlags) {
if mo.Flags.Get(jsonflags.StringTag) && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
}
once.Do(init)
if err := enc.WriteToken(jsontext.BeginArray); err != nil {
@@ -1561,9 +1648,13 @@ func makeArrayArshaler(t reflect.Type) *arshaler {
return nil
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
if uo.Flags.Has(jsonflags.TagFlags) {
if uo.Flags.Get(jsonflags.StringTag) && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
}
tok, err := dec.ReadToken()
if err != nil {
@@ -1616,7 +1707,7 @@ func makeArrayArshaler(t reflect.Type) *arshaler {
}
return errUnmarshal
}
return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil)
return newUnmarshalErrorAfterWithSkipping(dec, t, nil)
}
return &fncs
}
@@ -1641,6 +1732,12 @@ func makePointerArshaler(t reflect.Type) *arshaler {
}
// NOTE: Struct.Format is forwarded to underlying marshal.
if mo.Flags.Get(jsonflags.StringTag) && mo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && t.Elem().Kind() == reflect.Pointer {
if !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
mo.Flags.Clear(jsonflags.StringTag) // the `string` tag option does not apply to nested pointers
}
if va.IsNil() {
return enc.WriteToken(jsontext.Null)
}
@@ -1661,6 +1758,12 @@ func makePointerArshaler(t reflect.Type) *arshaler {
va.SetZero()
return nil
}
if uo.Flags.Get(jsonflags.StringTag) && uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && t.Elem().Kind() == reflect.Pointer {
if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
uo.Flags.Clear(jsonflags.StringTag) // the `string` tag option does not apply to nested pointers
}
once.Do(init)
unmarshal := valFncs.unmarshal
if uo.Unmarshalers != nil {
@@ -1673,8 +1776,7 @@ func makePointerArshaler(t reflect.Type) *arshaler {
if err := unmarshal(dec, v, uo); err != nil {
return err
}
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) &&
uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringifyBoolsAndStrings) {
if uo.Flags.Get(jsonflags.StringTag) && uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
// A JSON null quoted within a JSON string should take effect
// within the pointer value, rather than the indirect value.
//
@@ -1704,9 +1806,18 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler {
}
}
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
return newInvalidFormatError(enc, t, mo)
if mo.Flags.Has(jsonflags.TagFlags) {
if mo.Flags.Get(jsonflags.StringTag) {
if !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
if mo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
mo.Flags.Clear(jsonflags.StringTag) // the `string` tag option does not apply to interface types
}
}
if mo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(enc, t)
}
}
if va.IsNil() {
return enc.WriteToken(jsontext.Null)
@@ -1737,16 +1848,25 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler {
}
// Optimize for the any type if there are no special options.
if optimizeCommon &&
t == anyType && !mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringifyBoolsAndStrings) && mo.Format == "" &&
t == anyType && !mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.TagFlags) &&
(mo.Marshalers == nil || !mo.Marshalers.(*Marshalers).fromAny) {
return marshalValueAny(enc, va.Elem().Interface(), mo)
}
return marshal(enc, v, mo)
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
return newInvalidFormatError(dec, t, uo)
if uo.Flags.Has(jsonflags.TagFlags) {
if uo.Flags.Get(jsonflags.StringTag) {
if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
uo.Flags.Clear(jsonflags.StringTag) // the `string` tag option does not apply to interface types
}
}
if uo.Flags.Has(jsonflags.FormatTag) {
return newInvalidFormatError(dec, t)
}
}
if uo.Flags.Get(jsonflags.MergeWithLegacySemantics) && !va.IsNil() {
// Legacy merge behavior is difficult to explain.
@@ -1782,7 +1902,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler {
// Duplicate name check must be enforced since unmarshalValueAny
// does not implement merge semantics.
if optimizeCommon &&
t == anyType && !uo.Flags.Get(jsonflags.AllowDuplicateNames) && uo.Format == "" &&
t == anyType && !uo.Flags.Get(jsonflags.AllowDuplicateNames|jsonflags.FormatTag) &&
(uo.Unmarshalers == nil || !uo.Unmarshalers.(*Unmarshalers).fromAny) {
v, err := unmarshalValueAny(dec, uo)
// We must check for nil interface values up front.
@@ -1795,7 +1915,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler {
k := dec.PeekKind()
if !isAnyType(t) {
return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, internal.ErrNilInterface)
return newUnmarshalErrorBeforeWithSkipping(dec, t, internal.ErrNilInterface)
}
switch k {
case 'f', 't':
@@ -1838,7 +1958,7 @@ func makeInterfaceArshaler(t reflect.Type) *arshaler {
return &fncs
}
// isAnyType reports wether t is equivalent to the any interface type.
// isAnyType reports whether t is equivalent to the any interface type.
func isAnyType(t reflect.Type) bool {
// This is forward compatible if the Go language permits type sets within
// ordinary interfaces where an interface with zero methods does not
@@ -1853,6 +1973,19 @@ func makeInvalidArshaler(t reflect.Type) *arshaler {
return newMarshalErrorBefore(enc, t, nil)
}
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
// Under legacy error semantics, unmarshal continues on even with errors.
// Thus, always consume the value first.
// As a special-case, null is permitted for unsupported types.
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
switch val, err := dec.ReadValue(); {
case err != nil:
return err
case val.Kind() == 'n':
return nil
default:
return newUnmarshalErrorAfter(dec, t, nil)
}
}
return newUnmarshalErrorBefore(dec, t, nil)
}
return &fncs
@@ -11,6 +11,7 @@ import (
"errors"
"io"
"reflect"
"slices"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
@@ -18,30 +19,30 @@ import (
"github.com/go-json-experiment/json/jsontext"
)
// This package supports "inlining" a Go struct field, where the contents
// This package supports "embedding" a Go struct field, where the contents
// of the serialized field (which must be a JSON object) are treated as if
// they are part of the parent Go struct (which represents a JSON object).
//
// Generally, inlined fields are of a Go struct type, where the fields of the
// Generally, embedded fields are of a Go struct type, where the fields of the
// nested struct are virtually hoisted up to the parent struct using rules
// similar to how Go embedding works (but operating within the JSON namespace).
//
// However, inlined fields may also be of a Go map type with a string key or
// a jsontext.Value. Such inlined fields are called "fallback" fields since they
// However, embedded fields may also be of a Go map type with a string key or
// a jsontext.Value. Such embedded fields are called "fallback" fields since they
// represent any arbitrary JSON object member. Explicitly named fields take
// precedence over the inlined fallback. Only one inlined fallback is allowed.
// precedence over the embedded fallback. Only one embedded fallback is allowed.
var errRawInlinedNotObject = errors.New("inlined raw value must be a JSON object")
var errRawEmbedNotObject = errors.New("embedded 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 {
// marshalEmbeddedFallbackAll marshals all the members in an embedded fallback.
func marshalEmbeddedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct, f *structField, insertUnquotedName func([]byte) bool) error {
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
return nil // implies a nil embedded field
}
}
v = v.indirect(false)
@@ -50,8 +51,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
}
if v.Type() == jsontextValueType {
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
b := *v.Addr().Interface().(*jsontext.Value)
b, _ := reflect.TypeAssert[jsontext.Value](v.Value)
if len(b) == 0 { // TODO: Should this be nil? What if it were all whitespace?
return nil
}
@@ -69,7 +69,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
return newMarshalErrorBefore(enc, v.Type(), err)
}
if tok.Kind() != '{' {
return newMarshalErrorBefore(enc, v.Type(), errRawInlinedNotObject)
return newMarshalErrorBefore(enc, v.Type(), errRawEmbedNotObject)
}
for dec.PeekKind() != '}' {
// Parse the JSON object name.
@@ -113,7 +113,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
mk := newAddressableValue(m.Type().Key())
mv := newAddressableValue(m.Type().Elem())
marshalKey := func(mk addressableValue) error {
b, err := jsonwire.AppendQuote(enc.AvailableBuffer(), mk.String(), &mo.Flags)
b, err := jsonwire.AppendQuote(enc.AvailableBuffer(), []byte(mk.String()), &mo.Flags)
if err != nil {
return newMarshalErrorBefore(enc, m.Type().Key(), err)
}
@@ -147,7 +147,7 @@ func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *j
mk.SetIterKey(iter)
(*names)[i] = mk.String()
}
names.Sort()
slices.Sort(*names)
for _, name := range *names {
mk.SetString(name)
if err := marshalKey(mk); err != nil {
@@ -165,8 +165,8 @@ 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 {
// unmarshalEmbeddedFallbackNext unmarshals only the next member in an embedded fallback.
func unmarshalEmbeddedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct, f *structField, quotedName, unquotedName []byte) error {
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)
@@ -174,7 +174,7 @@ func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo
v = v.indirect(true)
if v.Type() == jsontextValueType {
b := v.Addr().Interface().(*jsontext.Value)
b, _ := reflect.TypeAssert[*jsontext.Value](v.Addr())
if len(*b) == 0 { // TODO: Should this be nil? What if it were all whitespace?
*b = append(*b, '{')
} else {
@@ -188,7 +188,7 @@ func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo
*b = append(*b, ',')
}
} else {
return newUnmarshalErrorAfterWithSkipping(dec, uo, v.Type(), errRawInlinedNotObject)
return newUnmarshalErrorAfterWithSkipping(dec, v.Type(), errRawEmbedNotObject)
}
}
*b = append(*b, quotedName...)
+40 -51
View File
@@ -9,6 +9,7 @@ package json
import (
"errors"
"fmt"
"io"
"reflect"
"sync"
@@ -18,16 +19,7 @@ import (
"github.com/go-json-experiment/json/jsontext"
)
// 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].
// For example, it is permissible to call [jsontext.Decoder.PeekKind],
// but not permissible to call [jsontext.Decoder.ReadToken] or
// [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 errUnsupportedMutation = errors.New("unsupported calls must not read or write any tokens")
var errNonSingularValue = errors.New("must read or write exactly one value")
// Marshalers is a list of functions that may override the marshal behavior
@@ -40,7 +32,8 @@ type Marshalers = typedMarshalers
// 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,
// If a function returns [errors.ErrUnsupported],
// then the next applicable function is called,
// otherwise the default marshaling behavior is used.
//
// For example:
@@ -62,7 +55,8 @@ type Unmarshalers = typedUnmarshalers
// 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,
// If a function returns [errors.ErrUnsupported],
// then the next applicable function is called,
// otherwise the default unmarshaling behavior is used.
//
// For example:
@@ -150,7 +144,7 @@ func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsono
fncDefault := fnc
fnc = func(c *Coder, v addressableValue, o *jsonopts.Struct) error {
for _, fnc := range fncs {
if err := fnc(c, v, o); err != SkipFunc {
if err := fnc(c, v, o); !errors.Is(err, errors.ErrUnsupported) {
return err // may be nil or non-nil
}
}
@@ -168,18 +162,19 @@ func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsono
// The function is always provided with a non-nil pointer value
// if T is an interface or pointer type.
//
// 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].
// Implementations must follow the requirements of [Marshaler].
//
// Implementations must not retain the value of T.
func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, true)
typFnc := typedMarshaler{
typ: t,
fnc: func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
val, err := fn(va.castTo(t).Interface().(T))
v, _ := reflect.TypeAssert[T](va.castTo(t))
val, err := fn(v)
if err != nil {
err = wrapSkipFunc(err, "marshal function of type func(T) ([]byte, error)")
err = wrapErrUnsupported(err, "marshal function of type func(T) ([]byte, error)")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped
}
@@ -207,12 +202,10 @@ func MarshalFunc[T any](fn func(T) ([]byte, error)) *Marshalers {
// The function is always provided with a non-nil pointer value
// if T is an interface or pointer type.
//
// The function must marshal exactly one JSON value by calling write methods
// 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] and the value of T
// must not be retained outside the function call.
// Implementations must follow the requirements of [MarshalerTo].
//
// Implementations must not retain the pointer to [jsontext.Encoder] or the
// value of T.
func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, true)
@@ -222,18 +215,19 @@ func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
xe := export.Encoder(enc)
prevDepth, prevLength := xe.Tokens.DepthLength()
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
err := fn(enc, va.castTo(t).Interface().(T))
v, _ := reflect.TypeAssert[T](va.castTo(t))
err := fn(enc, v)
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xe.Tokens.DepthLength()
if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) {
err = errNonSingularValue
}
if err != nil {
if err == SkipFunc {
if errors.Is(err, errors.ErrUnsupported) {
if prevDepth == currDepth && prevLength == currLength {
return SkipFunc
return err // forward [errors.ErrUnsupported]
}
err = errSkipMutation
err = errUnsupportedMutation
}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalToFunc") // unlike unmarshal, always wrapped
@@ -255,10 +249,9 @@ func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers {
// T must be an unnamed pointer or an interface type.
// The function is always provided with a non-nil pointer value.
//
// The function must unmarshal exactly one JSON value.
// 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].
// Implementations must follow the requirements of [Unmarshaler].
//
// Implementations must not retain the value of T.
func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, false)
@@ -269,9 +262,10 @@ func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
if err != nil {
return err // must be a syntactic or I/O error
}
err = fn(val, va.castTo(t).Interface().(T))
v, _ := reflect.TypeAssert[T](va.castTo(t))
err = fn(val, v)
if err != nil {
err = wrapSkipFunc(err, "unmarshal function of type func([]byte, T) error")
err = wrapErrUnsupported(err, "unmarshal function of type func([]byte, T) error")
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
@@ -289,12 +283,10 @@ func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers {
// T must be an unnamed pointer or an interface type.
// The function is always provided with a non-nil pointer value.
//
// The function must unmarshal exactly one JSON value by calling read methods
// 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] and the value of T
// must not be retained outside the function call.
// Implementations must follow the requirements of [UnmarshalerFrom].
//
// Implementations must not retain the pointer to [jsontext.Decoder] or the
// value of T.
func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers {
t := reflect.TypeFor[T]()
assertCastableTo(t, false)
@@ -303,19 +295,23 @@ func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers
fnc: func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
prevDepth, prevLength := xd.Tokens.DepthLength()
if prevDepth == 1 && xd.AtEOF() {
return io.EOF // check EOF early to avoid fn reporting an EOF
}
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
err := fn(dec, va.castTo(t).Interface().(T))
v, _ := reflect.TypeAssert[T](va.castTo(t))
err := fn(dec, v)
xd.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xd.Tokens.DepthLength()
if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) {
err = errNonSingularValue
}
if err != nil {
if err == SkipFunc {
if errors.Is(err, errors.ErrUnsupported) {
if prevDepth == currDepth && prevLength == currLength {
return SkipFunc
return err // forward [errors.ErrUnsupported]
}
err = errSkipMutation
err = errUnsupportedMutation
}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil {
@@ -423,10 +419,3 @@ func castableToFromAny(to reflect.Type) bool {
}
return false
}
func wrapSkipFunc(err error, what string) error {
if err == SkipFunc {
return errors.New(what + " cannot be skipped")
}
return err
}
+117 -32
View File
@@ -9,6 +9,7 @@ package json
import (
"encoding"
"errors"
"io"
"reflect"
"github.com/go-json-experiment/json/internal"
@@ -37,62 +38,131 @@ var (
// 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.
// is trying to avoid directly depending on the "jsontext" package.
//
// It is recommended that implementations return a buffer that is safe
// Implementations should return a buffer that is safe
// for the caller to retain and potentially mutate.
//
// Implementations must not return [errors.ErrUnsupported].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError].
//
// Implementations should assume [Deterministic] is true and return
// deterministic output.
type Marshaler interface {
MarshalJSON() ([]byte, error)
}
// 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.
// since it is both more performant and more flexible.
// 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].
// The implementation must write only one JSON value to the Encoder.
// Alternatively, it may return [errors.ErrUnsupported] without mutating
// the Encoder. The "json" package calling the method will
// use the next available JSON representation for the receiver type,
// as described in [Marshal].
// Implementations must not retain the pointer to [jsontext.Encoder].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError],
// except for IO errors.
//
// The MarshalJSONTo method should not be called directly as it may
// return sentinel errors that need special handling.
// Users should instead call [MarshalEncode], which handles such cases.
//
// Implementations should inspect the marshal options from
// [jsontext.Encoder.Options] and adjust behavior to respect the options as
// necessary.
//
// The following options may be relevant to MarshalerTo implementations:
//
// - [Deterministic]: if the implementation may produce non-deterministic output
// - [StringifyNumbers]: if the type is represented as a JSON number
//
// Several options, such as [FormatNilSliceAsNull], apply only to native Go
// types. Thus, these options are typically not directly relevant to
// MarshalerTo implementations. However, types representing a composite type
// should marshal contained types using [MarshalEncode] to ensure these options
// apply to the contained types. Similarly, [WithMarshalers] may influence
// marshaling of any contained type within a composite type.
//
// All other options are automatically handled outside of the MarshalerTo
// implementation, and thus are not relevant to implementations.
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?
}
// 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.
// is trying to avoid a direct dependency on the "jsontext" package.
//
// The input can be assumed to be a valid encoding of a JSON value
// if called from unmarshal functionality in this package.
// UnmarshalJSON must copy the JSON data if it is retained after returning.
// It is recommended that UnmarshalJSON implement merge semantics when
// unmarshaling into a pre-populated value.
// It is recommended that UnmarshalJSON implement merge semantics
// when unmarshaling into a pre-populated value, as described in [Unmarshal].
//
// Implementations must not retain or mutate the input []byte.
//
// Implementations must not return [errors.ErrUnsupported].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError].
type Unmarshaler interface {
UnmarshalJSON([]byte) error
}
// 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.
// since this is both more performant and more flexible.
// 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 UnmarshalJSONFrom implement merge semantics when
// unmarshaling into a pre-populated value.
//
// unmarshaling into a pre-populated value, as described in [Unmarshal].
// Alternatively, it may return [errors.ErrUnsupported] without mutating
// the Decoder. The "json" package calling the method will
// use the next available JSON representation for the receiver type.
// Implementations must not retain the pointer to [jsontext.Decoder].
//
// If the returned error is a [SemanticError], then unpopulated fields
// of the error may be populated by [json] with additional context.
// Errors of other types are wrapped within a [SemanticError],
// except for [jsontext.SyntacticError]s and IO errors.
//
// The UnmarshalJSONFrom method should not be called directly as it may
// return sentinel errors that need special handling.
// Users should instead call [UnmarshalDecode], which handles such cases.
//
// Implementations should inspect the unmarshal options from
// [jsontext.Decoder.Options] and adjust behavior to respect the options as
// necessary.
//
// The following options may be relevant to UnmarshalerFrom implementations:
//
// - [StringifyNumbers]: if the type is represented as a JSON number
//
// Several options, such as [FormatNilSliceAsNull], apply only to native Go
// types. Thus, these options are typically not directly relevant to
// UnmarshalerFrom implementations. However, types representing a composite
// type should unmarshal contained types using [UnmarshalDecode] to ensure
// these options apply to the contained types. Similarly, [WithUnmarshalers]
// may influence unmarshaling of any contained type within a composite type.
//
// All other options are automatically handled outside of the UnmarshalerFrom
// implementation, and thus are not relevant to implementations.
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?
}
func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
@@ -111,12 +181,12 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
(needAddr && va.forcedAddr) {
return prevMarshal(enc, va, mo)
}
marshaler := va.Addr().Interface().(encoding.TextMarshaler)
marshaler, _ := reflect.TypeAssert[encoding.TextMarshaler](va.Addr())
if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) {
b2, err := marshaler.MarshalText()
return append(b, b2...), err
}); err != nil {
err = wrapSkipFunc(err, "marshal method")
err = wrapErrUnsupported(err, "MarshalText method")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped
}
@@ -137,9 +207,9 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
(needAddr && va.forcedAddr) {
return prevMarshal(enc, va, mo)
}
appender := va.Addr().Interface().(encoding.TextAppender)
appender, _ := reflect.TypeAssert[encoding.TextAppender](va.Addr())
if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil {
err = wrapSkipFunc(err, "append method")
err = wrapErrUnsupported(err, "AppendText method")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped
}
@@ -160,10 +230,10 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) {
return prevMarshal(enc, va, mo)
}
marshaler := va.Addr().Interface().(Marshaler)
marshaler, _ := reflect.TypeAssert[Marshaler](va.Addr())
val, err := marshaler.MarshalJSON()
if err != nil {
err = wrapSkipFunc(err, "marshal method")
err = wrapErrUnsupported(err, "MarshalJSON method")
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
}
@@ -194,14 +264,20 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
xe := export.Encoder(enc)
prevDepth, prevLength := xe.Tokens.DepthLength()
xe.Flags.Set(jsonflags.WithinArshalCall | 1)
err := va.Addr().Interface().(MarshalerTo).MarshalJSONTo(enc)
marshaler, _ := reflect.TypeAssert[MarshalerTo](va.Addr())
err := marshaler.MarshalJSONTo(enc)
xe.Flags.Set(jsonflags.WithinArshalCall | 0)
currDepth, currLength := xe.Tokens.DepthLength()
if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil {
err = errNonSingularValue
}
if err != nil {
err = wrapSkipFunc(err, "marshal method")
if errors.Is(err, errors.ErrUnsupported) {
if prevDepth == currDepth && prevLength == currLength {
return prevMarshal(enc, va, mo)
}
err = errUnsupportedMutation
}
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONTo") // unlike unmarshal, always wrapped
}
@@ -233,9 +309,9 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
return newUnmarshalErrorAfter(dec, t, errNonStringValue)
}
s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler)
unmarshaler, _ := reflect.TypeAssert[encoding.TextUnmarshaler](va.Addr())
if err := unmarshaler.UnmarshalText(s); err != nil {
err = wrapSkipFunc(err, "unmarshal method")
err = wrapErrUnsupported(err, "UnmarshalText method")
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
@@ -260,9 +336,9 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
if err != nil {
return err // must be a syntactic or I/O error
}
unmarshaler := va.Addr().Interface().(Unmarshaler)
unmarshaler, _ := reflect.TypeAssert[Unmarshaler](va.Addr())
if err := unmarshaler.UnmarshalJSON(val); err != nil {
err = wrapSkipFunc(err, "unmarshal method")
err = wrapErrUnsupported(err, "UnmarshalJSON method")
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err // unlike marshal, never wrapped
}
@@ -283,15 +359,24 @@ func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler {
}
xd := export.Decoder(dec)
prevDepth, prevLength := xd.Tokens.DepthLength()
if prevDepth == 1 && xd.AtEOF() {
return io.EOF // check EOF early to avoid fn reporting an EOF
}
xd.Flags.Set(jsonflags.WithinArshalCall | 1)
err := va.Addr().Interface().(UnmarshalerFrom).UnmarshalJSONFrom(dec)
unmarshaler, _ := reflect.TypeAssert[UnmarshalerFrom](va.Addr())
err := unmarshaler.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 errors.Is(err, errors.ErrUnsupported) {
if prevDepth == currDepth && prevLength == currLength {
return prevUnmarshal(dec, va, uo)
}
err = errUnsupportedMutation
}
if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil {
return err2
+42 -24
View File
@@ -46,20 +46,26 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
xe := export.Encoder(enc)
var m durationArshaler
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
if mo.Flags.Has(jsonflags.FormatTag) {
if !m.initFormat(mo.Format) {
return newInvalidFormatError(enc, t, mo)
return newInvalidFormatError(enc, t)
}
} else if mo.Flags.Get(jsonflags.FormatDurationAsNano) {
return marshalNano(enc, va, mo)
} else {
// TODO(https://go.dev/issue/71631): Decide on default duration representation.
return newMarshalErrorBefore(enc, t, errors.New("no default representation (see https://go.dev/issue/71631); specify an explicit format"))
var workaround string
if mo.Flags.Get(jsonflags.FormatTagSupported) {
workaround = "; specify an explicit format"
}
return newMarshalErrorBefore(enc, t, errors.New("no default representation"+workaround))
}
if mo.Flags.Get(jsonflags.StringTag) && !m.isNumeric() && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
m.td = *va.Addr().Interface().(*time.Duration)
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
m.td, _ = reflect.TypeAssert[time.Duration](va.Value)
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag))
if err := xe.AppendRaw(k, true, m.appendMarshal); err != nil {
if !isSyntacticError(err) && !export.IsIOError(err) {
err = newMarshalErrorBefore(enc, t, err)
@@ -72,20 +78,27 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
xd := export.Decoder(dec)
var u durationArshaler
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
if uo.Flags.Has(jsonflags.FormatTag) {
if !u.initFormat(uo.Format) {
return newInvalidFormatError(dec, t, uo)
return newInvalidFormatError(dec, t)
}
} else if uo.Flags.Get(jsonflags.FormatDurationAsNano) {
return unmarshalNano(dec, va, uo)
} else {
// TODO(https://go.dev/issue/71631): Decide on default duration representation.
return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errors.New("no default representation (see https://go.dev/issue/71631); specify an explicit format"))
var workaround string
if uo.Flags.Get(jsonflags.FormatTagSupported) {
workaround = "; specify an explicit format"
}
return newUnmarshalErrorBeforeWithSkipping(dec, t, errors.New("no default representation"+workaround))
}
if uo.Flags.Get(jsonflags.StringTag) && !u.isNumeric() && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
var flags jsonwire.ValueFlags
td := va.Addr().Interface().(*time.Duration)
td, _ := reflect.TypeAssert[*time.Duration](va.Addr())
val, err := xd.ReadValue(&flags)
if err != nil {
return err
@@ -123,15 +136,17 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) {
xe := export.Encoder(enc)
var m timeArshaler
if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
if mo.Flags.Has(jsonflags.FormatTag) {
if !m.initFormat(mo.Format) {
return newInvalidFormatError(enc, t, mo)
return newInvalidFormatError(enc, t)
}
}
if mo.Flags.Get(jsonflags.StringTag) && !m.isNumeric() && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newMarshalErrorBefore(enc, t, errInvalidStringTag)
}
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
m.tt = *va.Addr().Interface().(*time.Time)
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
m.tt, _ = reflect.TypeAssert[time.Time](va.Value)
k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag))
if err := xe.AppendRaw(k, !m.hasCustomFormat(), m.appendMarshal); err != nil {
if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
@@ -146,17 +161,20 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) (err error) {
xd := export.Decoder(dec)
var u timeArshaler
if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
if uo.Flags.Has(jsonflags.FormatTag) {
if !u.initFormat(uo.Format) {
return newInvalidFormatError(dec, t, uo)
return newInvalidFormatError(dec, t)
}
} else if uo.Flags.Get(jsonflags.ParseTimeWithLooseRFC3339) {
u.looseRFC3339 = true
}
if uo.Flags.Get(jsonflags.StringTag) && !u.isNumeric() && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return newUnmarshalErrorBeforeWithSkipping(dec, t, errInvalidStringTag)
}
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringTag)
var flags jsonwire.ValueFlags
tt := va.Addr().Interface().(*time.Time)
tt, _ := reflect.TypeAssert[*time.Time](va.Addr())
val, err := xd.ReadValue(&flags)
if err != nil {
return err
@@ -467,7 +485,7 @@ func appendDurationISO8601(b []byte, d time.Duration) []byte {
}
// daysPerYear is the exact average number of days in a year according to
// the Gregorian calender, which has an extra day each year that is
// the Gregorian calendar, which has an extra day each year that is
// a multiple of 4, unless it is evenly divisible by 100 but not by 400.
// This does not take into account leap seconds, which are not deterministic.
const daysPerYear = 365.2425
@@ -496,7 +514,7 @@ var errInaccurateDateUnits = errors.New("inaccurate year, month, week, or day un
// between the integer part and fraction part of a number,
// as specified in ISO 8601-1:2019, section 3.2.6.
// While ISO 8601 recommends comma as the default separator,
// most formatters uses a period.
// most formatters use a period.
//
// - Leading zeros are ignored. This is not required by ISO 8601,
// but also not forbidden by the standard. Many parsers support this.
@@ -514,7 +532,7 @@ var errInaccurateDateUnits = errors.New("inaccurate year, month, week, or day un
// We follow JavaScript's grammar as JSON itself is derived from JavaScript.
// The Temporal.Duration.toJSON method is guaranteed to produce an output
// that can be parsed by this function so long as arithmetic in JavaScript
// do not use a largestUnit value higher than "hours" (which is the default).
// does not use a largestUnit value higher than "hours" (which is the default).
// Even if it does, this will do a best-effort parsing with inaccurate units,
// but report [errInaccurateDateUnits].
func parseDurationISO8601(b []byte) (time.Duration, error) {
@@ -541,7 +559,7 @@ func parseDurationISO8601(b []byte) (time.Duration, error) {
}
// Parse the number.
// A fraction allowed for the accurate units in the last part.
// A fraction is allowed for the accurate units in the last part.
whole, frac, ok := cutBytes(number, '.', ',')
if ok {
sawFrac = true
+39 -51
View File
@@ -9,6 +9,8 @@
// primitive data types such as booleans, strings, and numbers,
// in addition to structured data types such as objects and arrays.
//
// See the [Working with JSON] tutorial for an introduction to this package.
//
// [Marshal] and [Unmarshal] encode and decode Go values
// to/from JSON text contained within a []byte.
// [MarshalWrite] and [UnmarshalRead] operate on JSON text
@@ -53,7 +55,7 @@
// if [RejectUnknownMembers] is specified.
//
// The representation of each struct field can be customized in the
// "json" struct field tag, where the tag is a comma separated list of options.
// "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].
@@ -61,11 +63,7 @@
//
// 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
// is used as the JSON object name. JSON names containing commas or quotes,
// or names identical to "" or "-", can be specified using
// a single-quoted string literal, where the syntax is identical to
// the Go grammar for a double-quoted string literal,
// but instead uses single quotes as the delimiters.
// is used as the JSON object name.
// By default, unmarshaling uses case-sensitive matching to identify
// the Go struct field associated with a JSON object name.
//
@@ -82,60 +80,49 @@
// encoded as a JSON null, empty string, empty object, or empty array.
// This option has no effect when unmarshaling.
//
// - 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 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.
// - string: The "string" option specifies that [StringifyNumbers] be set
// when marshaling or unmarshaling a struct field value.
// This causes types that would normally be encoded as a JSON number
// to instead be encoded as a JSON number quoted within a JSON string,
// and to be decoded from a JSON string containing the JSON number
// without any surrounding whitespace.
// The "string" option only applies to the top-level of the Go struct field
// value. It is an error to apply this option to any type that does not
// encode as a JSON number.
// Note that composite types such as arrays, slices, structs, and maps do
// not encode as a JSON number, so applying this option will cause an error
// rather than affecting JSON numbers within such types.
// This extra level of encoding is often necessary since many JSON parsers
// cannot precisely represent 64-bit integers.
//
// - 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.
// The 'ignore' value specifies that matching is case-insensitive,
// and also ignores dashes and underscores. If multiple fields match,
// then the field with an exact name match is selected, otherwise an error
// is reported because the choice of field to unmarshal into is ambiguous.
// The 'strict' value specifies that matching is case-sensitive.
// This takes precedence over the [MatchCaseInsensitiveNames] option.
//
// - inline: The "inline" option specifies that
// - embed: The "embed" option specifies that
// the JSON representable content of this field type is to be promoted
// as if they were specified in the parent struct.
// as if it were specified in the parent struct.
// It is the JSON equivalent of Go struct embedding.
// A Go embedded field is implicitly inlined unless an explicit JSON name
// is specified. The inlined field must be a Go struct
// A Go embedded field is implicitly JSON embedded unless
// an explicit JSON name is specified. The embedded 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,
// 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 fallbacks” as they can represent all possible
// embedded fields from a pointer type are omitted if it is nil.
// Embedded fields of type [jsontext.Value] and map[~string]T are called
// “embedded 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,
// Only one embedded fallback field may be specified in a struct,
// while many non-fallback fields may be specified. This option
// must not be specified with any other option (including the JSON name).
//
// - 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.
// If [DiscardUnknownMembers] is specified when marshaling,
// the contents of this field are ignored.
// If [RejectUnknownMembers] is specified when unmarshaling,
// any unknown object members are rejected regardless of whether
// an inlined fallback with the "unknown" option exists. This option
// must not be specified with any other option (including the JSON name).
//
// - format: The "format" option specifies a format flag
// used to specialize the formatting of the field value.
// The option is a key-value pair specified as "format:value" where
// the value must be either a literal consisting of letters and numbers
// (e.g., "format:RFC3339") or a single-quoted string literal
// (e.g., "format:'2006-01-02'"). The interpretation of the format flag
// is determined by the struct field type.
//
// The "omitzero" and "omitempty" options are mostly semantically identical.
// The "omitzero" and "omitempty" options behave similarly.
// The former is defined in terms of the Go type system,
// while the latter in terms of the JSON type system.
// Consequently they behave differently in some circumstances.
@@ -144,14 +131,14 @@
// The "omitzero" option is useful for types with a well-defined zero value
// (e.g., [net/netip.Addr]) or have an IsZero method (e.g., [time.Time.IsZero]).
//
// Every Go struct corresponds to a list of JSON representable fields
// Every Go struct corresponds to a list of JSON-representable fields
// which is constructed by performing a breadth-first search over
// all struct fields (excluding unexported or ignored fields),
// where the search recursively descends into inlined structs.
// The set of non-inlined fields in a struct must have unique JSON names.
// where the search recursively descends into embedded structs.
// The set of non-embedded fields in a struct must have unique JSON names.
// If multiple fields all have the same JSON name, then the one
// at shallowest depth takes precedence and the other fields at deeper depths
// are excluded from the list of JSON representable fields.
// are excluded from the list of JSON-representable fields.
// If multiple fields at the shallowest depth have the same JSON name,
// but exactly one is explicitly tagged with a JSON name,
// then that field takes precedence and all others are excluded from the list.
@@ -159,7 +146,7 @@
// with embedded struct types.
//
// Marshaling or unmarshaling a non-empty struct
// without any JSON representable fields results in a [SemanticError].
// without any JSON-representable fields results in a [SemanticError].
// Unexported fields must not have any `json` tags except for `json:"-"`.
//
// # Security Considerations
@@ -171,8 +158,8 @@
//
// [For example, suppose we have two micro-services.]
// The first service is responsible for authenticating a JSON request,
// while the second service is responsible for executing the request
// (having assumed that the prior service authenticated the request).
// while the second service is responsible for executing the request,
// assuming that it was authenticated.
// If an attacker were able to maliciously craft a JSON request such that
// both services believe that the same request is from different users,
// it could bypass the authenticator with valid credentials for one user,
@@ -254,6 +241,7 @@
// The v2 API generally chooses more secure defaults than v1,
// but care should still be taken with large integers or unknown members.
//
// [Working with JSON]: https://go.dev/doc/tutorial/json
// [For example, suppose we have two micro-services.]: https://www.youtube.com/watch?v=avilmOcHKHE&t=1057s
package json
+60 -22
View File
@@ -10,6 +10,7 @@ import (
"cmp"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
@@ -28,8 +29,8 @@ import (
// 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 {
// serr, ok := errors.AsType[*json.SemanticError](err)
// if ok && serr.Err == json.ErrUnknownName {
// ptr := serr.JSONPointer // JSON pointer to unknown name
// name := ptr.LastToken() // unknown name itself
// ...
@@ -38,6 +39,10 @@ import (
// This error is only returned if [RejectUnknownMembers] is true.
var ErrUnknownName = errors.New("unknown object member name")
// errAmbiguousName indicates a JSON object member could be unmarshal
// into multiple candidate Go struct fields.
var errAmbiguousName = errors.New("ambiguous object member name")
const errorPrefix = "json: "
func isSemanticError(err error) bool {
@@ -50,7 +55,7 @@ func isSyntacticError(err error) bool {
return ok
}
// isFatalError reports whether this error must terminate asharling.
// isFatalError reports whether this error must terminate arshaling.
// All errors are considered fatal unless operating under
// [jsonflags.ReportErrorsWithLegacySemantics] in which case only
// syntactic errors and I/O errors are considered fatal.
@@ -60,7 +65,12 @@ func isFatalError(err error, flags jsonflags.Flags) bool {
}
// SemanticError describes an error determining the meaning
// of JSON data as Go data or vice-versa.
// of JSON data as Go data, or vice versa.
//
// If a [Marshaler], [MarshalerTo], [Unmarshaler], or [UnmarshalerFrom] method
// returns a SemanticError when called by the [json] package,
// then the ByteOffset, JSONPointer, and GoType fields are automatically
// populated by the calling context if they are the zero value.
//
// The contents of this error as produced by this package may change over time.
type SemanticError struct {
@@ -69,7 +79,7 @@ type SemanticError struct {
action string // either "marshal" or "unmarshal"
// ByteOffset indicates that an error occurred after this byte offset.
// ByteOffset indicates that an error occurred at or after this byte offset.
ByteOffset int64
// JSONPointer indicates that an error occurred within this JSON value
// as indicated using the JSON Pointer notation (see RFC 6901).
@@ -88,22 +98,25 @@ type SemanticError struct {
}
// coder is implemented by [jsontext.Encoder] or [jsontext.Decoder].
type coder interface{ StackPointer() jsontext.Pointer }
type coder interface {
StackPointer() jsontext.Pointer
Options() Options
}
// newInvalidFormatError wraps err in a SemanticError because
// newInvalidFormatError constructs 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.
// This function 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)
func newInvalidFormatError(c coder, t reflect.Type) error {
err := fmt.Errorf("invalid format flag %q", c.Options().(*jsonopts.Struct).Format)
switch c := c.(type) {
case *jsontext.Encoder:
err = newMarshalErrorBefore(c, t, err)
case *jsontext.Decoder:
err = newUnmarshalErrorBeforeWithSkipping(c, o, t, err)
err = newUnmarshalErrorBeforeWithSkipping(c, t, err)
}
return err
}
@@ -111,7 +124,7 @@ func newInvalidFormatError(c coder, t reflect.Type, o *jsonopts.Struct) error {
// 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,
return &SemanticError{action: "marshal", GoType: t, Err: toUnexpectedEOF(err),
ByteOffset: e.OutputOffset() + int64(export.Encoder(e).CountNextDelimWhitespace()),
JSONPointer: jsontext.Pointer(export.Encoder(e).AppendStackPointer(nil, +1))}
}
@@ -127,7 +140,7 @@ func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) err
if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
k = d.PeekKind()
}
return &SemanticError{action: "unmarshal", GoType: t, Err: err,
return &SemanticError{action: "unmarshal", GoType: t, Err: toUnexpectedEOF(err),
ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()),
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1)),
JSONKind: k}
@@ -136,9 +149,9 @@ func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) err
// 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 {
func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, t reflect.Type, err error) error {
err = newUnmarshalErrorBefore(d, t, err)
if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := export.Decoder(d).SkipValue(); err2 != nil {
return err2
}
@@ -150,13 +163,22 @@ func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct
// 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)),
byteOffset := d.InputOffset() - int64(len(tokOrVal))
if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
// NOTE: In v1, the offset pointed to the end of the bad token or value.
if k := jsontext.Value(tokOrVal).Kind(); k == '[' || k == '{' {
byteOffset++ // add just the '[' or '{'
} else {
byteOffset += int64(len(tokOrVal))
}
}
return &SemanticError{action: "unmarshal", GoType: t, Err: toUnexpectedEOF(err),
ByteOffset: byteOffset,
JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, -1)),
JSONKind: jsontext.Value(tokOrVal).Kind()}
}
// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d
// newUnmarshalErrorAfterWithValue 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 {
@@ -170,9 +192,9 @@ func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err er
// 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 {
func newUnmarshalErrorAfterWithSkipping(d *jsontext.Decoder, t reflect.Type, err error) error {
err = newUnmarshalErrorAfter(d, t, err)
if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if export.Decoder(d).Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
if err2 := export.Decoder(d).SkipValueRemainder(); err2 != nil {
return err2
}
@@ -199,6 +221,7 @@ func newSemanticErrorWithPosition(c coder, t reflect.Type, prevDepth int, prevLe
if serr == nil {
serr = &SemanticError{Err: err}
}
serr.Err = toUnexpectedEOF(serr.Err)
var currDepth int
var currLength int64
var coderState interface{ AppendStackPointer([]byte, int) []byte }
@@ -289,6 +312,13 @@ func collapseSemanticErrors(err error) error {
return err
}
func wrapErrUnsupported(err error, what string) error {
if errors.Is(err, errors.ErrUnsupported) {
return errors.New(what + " may not return errors.ErrUnsupported")
}
return err
}
// errorModalVerb is a modal verb like "cannot" or "unable to".
//
// Once per process, Hyrum-proof the error message by deliberately
@@ -370,9 +400,9 @@ func (e *SemanticError) Error() string {
}
// Special handling for unknown names.
if e.Err == ErrUnknownName {
if e.Err == ErrUnknownName || e.Err == errAmbiguousName {
sb.WriteString(": ")
sb.WriteString(ErrUnknownName.Error())
sb.WriteString(e.Err.Error())
sb.WriteString(" ")
sb.WriteString(strconv.Quote(e.JSONPointer.LastToken()))
if parent := e.JSONPointer.Parent(); parent != "" {
@@ -425,3 +455,11 @@ func newDuplicateNameError(ptr jsontext.Pointer, quotedName []byte, offset int64
Err: jsontext.ErrDuplicateName,
}
}
// toUnexpectedEOF converts [io.EOF] to [io.ErrUnexpectedEOF].
func toUnexpectedEOF(err error) error {
if err == io.EOF {
return io.ErrUnexpectedEOF
}
return err
}
+83 -81
View File
@@ -29,10 +29,12 @@ type isZeroer interface {
var isZeroerType = reflect.TypeFor[isZeroer]()
type structFields struct {
flattened []structField // listed in depth-first ordering
byActualName map[string]*structField
byFoldedName map[string][]*structField
inlinedFallback *structField
flattened []structField // listed in depth-first ordering
byActualName map[string]*structField
byFoldedName map[string][]*structField
embeddedFallback *structField
errUnsupportedFormat *SemanticError
}
// reindex recomputes index to avoid bounds check during runtime.
@@ -51,8 +53,8 @@ func (sf *structFields) reindex() {
for i := range sf.flattened {
reindex(&sf.flattened[i])
}
if sf.inlinedFallback != nil {
reindex(sf.inlinedFallback)
if sf.embeddedFallback != nil {
reindex(sf.embeddedFallback)
}
}
@@ -80,25 +82,25 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)})
}
// Setup a queue for a breath-first search.
// Setup a queue for a breadth-first search.
var queueIndex int
type queueEntry struct {
typ reflect.Type
index []int
visitChildren bool // whether to recursively visit inlined field in this struct
visitChildren bool // whether to recursively visit embedded field in this struct
}
queue := []queueEntry{{root, nil, true}}
seen := map[reflect.Type]bool{root: true}
// Perform a breadth-first search over all reachable fields.
// This ensures that len(f.index) will be monotonically increasing.
var allFields, inlinedFallbacks []structField
var allFields, embeddedFallbacks []structField
for queueIndex < len(queue) {
qe := queue[queueIndex]
queueIndex++
t := qe.typ
inlinedFallbackIndex := -1 // index of last inlined fallback field in current struct
embeddedFallbackIndex := -1 // index of last embedded fallback field in current struct
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
@@ -126,80 +128,73 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
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
f.embed = true // implied by use of Go embedding without an explicit name
}
}
if f.inline || f.unknown {
// Handle an inlined field that serializes to/from
var handleEmbed, handleField func()
handleEmbed = func() {
// Handle an embedded field that serializes to/from
// zero or more JSON object members.
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:
serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
if f.fieldOptions != (fieldOptions{name: f.name, quotedName: f.quotedName, embed: true}) {
serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `embed` 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
handleField()
return // invalid embedded field; treat as regular field
}
f.fieldOptions = fieldOptions{name: f.name, quotedName: f.quotedName, embed: f.embed}
}
// Reject any types with custom serialization otherwise
// it becomes impossible to know what sub-fields to inline.
// it becomes impossible to know what sub-fields to embed.
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)
serr = orErrorf(serr, t, "embedded 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
// Handle an embedded 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 {
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
return
} 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
serr = orErrorf(serr, t, "embedded Go struct field %s is not exported", sf.Name)
return // invalid embedded field; treat as ignored
}
// Handle an inlined field that serializes to/from any number of
// Handle an embedded field that serializes to/from any number of
// JSON object members back by a Go map or jsontext.Value.
switch {
case tf == jsontextValueType:
f.fncs = nil // specially handled in arshal_inlined.go
f.fncs = nil // specially handled in arshal_embedded.go
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
serr = orErrorf(serr, t, "embedded map field %s of type %s must have a string key that does not implement marshal or unmarshal methods", sf.Name, tf)
handleField()
return // invalid embedded field; treat as regular field
}
f.fncs = lookupArshaler(tf.Elem())
default:
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
serr = orErrorf(serr, t, "embedded Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value", sf.Name, tf)
handleField()
return // invalid embedded field; treat as regular field
}
// Reject multiple inlined fallback fields within the same struct.
if inlinedFallbackIndex >= 0 {
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.
// Reject multiple embedded fallback fields within the same struct.
if embeddedFallbackIndex >= 0 {
serr = orErrorf(serr, t, "embedded Go struct fields %s and %s cannot both be a Go map or jsontext.Value", t.Field(embeddedFallbackIndex).Name, sf.Name)
// Still append f to embeddedFallbacks as there is still a
// check for a dominant embedded fallback before returning.
}
inlinedFallbackIndex = i
embeddedFallbackIndex = i
inlinedFallbacks = append(inlinedFallbacks, f)
} else {
embeddedFallbacks = append(embeddedFallbacks, f)
}
handleField = func() {
// Handle normal Go struct field that serializes to/from
// a single JSON object member.
@@ -210,14 +205,14 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
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
return
}
// 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
return
}
}
@@ -260,6 +255,15 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
f.id = len(allFields)
f.fncs = lookupArshaler(sf.Type)
allFields = append(allFields, f)
if f.format != "" && fs.errUnsupportedFormat == nil {
fs.errUnsupportedFormat = &SemanticError{GoType: t, Err: fmt.Errorf("Go struct field %s has unsupported `format` tag option", sf.Name)}
}
}
if f.embed {
handleEmbed()
} else {
handleField()
}
}
@@ -325,6 +329,8 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
flattened: flattened,
byActualName: make(map[string]*structField, len(flattened)),
byFoldedName: make(map[string][]*structField, len(flattened)),
errUnsupportedFormat: fs.errUnsupportedFormat,
}
for i, f := range fs.flattened {
foldedName := string(foldName([]byte(f.name)))
@@ -333,7 +339,7 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
}
for foldedName, fields := range fs.byFoldedName {
if len(fields) > 1 {
// The precedence order for conflicting ignoreCase names
// The precedence order for conflicting case-insensitive 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)
@@ -341,8 +347,8 @@ func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError)
fs.byFoldedName[foldedName] = fields
}
}
if n := len(inlinedFallbacks); n == 1 || (n > 1 && len(inlinedFallbacks[0].index) != len(inlinedFallbacks[1].index)) {
fs.inlinedFallback = &inlinedFallbacks[0] // dominant inlined fallback field
if n := len(embeddedFallbacks); n == 1 || (n > 1 && len(embeddedFallbacks[0].index) != len(embeddedFallbacks[1].index)) {
fs.embeddedFallback = &embeddedFallbacks[0] // dominant embedded fallback field
}
fs.reindex()
return fs, serr
@@ -391,8 +397,7 @@ type fieldOptions struct {
hasName bool
nameNeedEscape bool
casing int8 // either 0, caseIgnore, or caseStrict
inline bool
unknown bool
embed bool
omitzero bool
omitempty bool
string bool
@@ -404,7 +409,6 @@ type fieldOptions struct {
// the JSON member name and other features.
func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, err error) {
tag, hasTag := sf.Tag.Lookup("json")
tagOrig := tag
// Check whether this field is explicitly ignored.
if tag == "-" {
@@ -428,10 +432,7 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
return fieldOptions{}, true, err
}
// Determine the JSON member name for this Go field. A user-specified name
// may be provided as either an identifier or a single-quoted string.
// The single-quoted string allows arbitrary characters in the name.
// See https://go.dev/issue/2718 and https://go.dev/issue/3546.
// Determine the JSON member name for this Go field.
out.name = sf.Name // always starts with an uppercase character
if len(tag) > 0 && !strings.HasPrefix(tag, ",") {
// For better compatibility with v1, accept almost any unescaped name.
@@ -445,7 +446,7 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
// 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)
name, n, err2 = consumeTagOption(tag, false)
if err2 != nil {
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
}
@@ -454,22 +455,15 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
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 name == "-" && tag[0] == '-' {
defer func() { // defer to let other errors take precedence
err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q; either "+
"use `json:\"-\"` to ignore the field or "+
"use `json:\"'-'%s` to specify %q as the name", sf.Name, out.name, strings.TrimPrefix(strconv.Quote(tagOrig), `"-`), name))
}()
}
if err2 == nil {
out.hasName = true
out.name = name
}
tag = tag[n:]
}
b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{})
b, _ := jsonwire.AppendQuote(nil, []byte(out.name), &jsonflags.Flags{})
out.quotedName = string(b)
out.nameNeedEscape = jsonwire.NeedEscape(out.name)
out.nameNeedEscape = jsonwire.NeedEscape([]byte(out.name))
// Handle any additional tag options (if any).
var wasFormat bool
@@ -487,7 +481,7 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
}
// Consume and process the tag option.
opt, n, err2 := consumeTagOption(tag)
opt, n, err2 := consumeTagOption(tag, false)
if err2 != nil {
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2))
}
@@ -506,7 +500,7 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
break
}
tag = tag[len(":"):]
opt, n, err2 := consumeTagOption(tag)
opt, n, err2 := consumeTagOption(tag, false)
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
@@ -524,10 +518,8 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
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":
out.unknown = true
case "embed":
out.embed = true
case "omitzero":
out.omitzero = true
case "omitempty":
@@ -540,10 +532,13 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
break
}
tag = tag[len(":"):]
opt, n, err2 := consumeTagOption(tag)
opt, n, err2 := consumeTagOption(tag, true)
if err2 != nil {
err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err2))
break
} else if opt == "" {
err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have empty value for `format` tag option", sf.Name))
break
}
tag = tag[n:]
out.format = opt
@@ -553,7 +548,7 @@ 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 "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
case "case", "embed", "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))
}
@@ -578,7 +573,7 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
// 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) {
func consumeTagOption(in string, allowQuoted bool) (string, int, error) {
// For legacy compatibility with v1, assume options are comma-separated.
i := strings.IndexByte(in, ',')
if i < 0 {
@@ -592,6 +587,10 @@ func consumeTagOption(in string) (string, int, error) {
return in[:n], n, nil
// Option as a single-quoted string.
case r == '\'':
if !allowQuoted {
return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter)", r)
}
// The grammar is nearly identical to a double-quoted Go string literal,
// but uses single quotes as the terminators. The reason for a custom
// grammar is because both backtick and double quotes cannot be used
@@ -633,6 +632,9 @@ func consumeTagOption(in string) (string, int, error) {
case len(in) == 0:
return in[:i], i, io.ErrUnexpectedEOF
default:
if !allowQuoted {
return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter)", r)
}
return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r)
}
}
+2 -2
View File
@@ -56,7 +56,7 @@ func makeString(c *stringCache, b []byte) string {
// hash64 returns the hash of two uint32s as a single uint32.
func hash64(lo, hi uint32) uint32 {
// If avalanche=true, this is identical to XXH32 hash on a 8B string:
// If avalanche=true, this is identical to XXH32 hash on an 8B string:
// var b [8]byte
// binary.LittleEndian.PutUint32(b[:4], lo)
// binary.LittleEndian.PutUint32(b[4:], hi)
@@ -75,7 +75,7 @@ func hash64(lo, hi uint32) uint32 {
h = bits.RotateLeft32(h, 17) * prime4
// Skip final mix (avalanche) step of XXH32 for performance reasons.
// Empirical testing shows that the improvements in unbiased distribution
// does not outweigh the extra cost in computational complexity.
// do not outweigh the extra cost in computational complexity.
const avalanche = false
if avalanche {
h ^= h >> 15
+1 -1
View File
@@ -32,7 +32,7 @@ var (
// 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.
// It is called only at the top-level of an Unmarshal function.
TransformUnmarshalError func(any, error) error
// NewRawNumber returns new(jsonv1.Number).
+21 -13
View File
@@ -43,9 +43,10 @@ const (
ByteLimit |
DepthLimit |
Marshalers |
Unmarshalers
Unmarshalers |
FormatTag
// DefaultV1Flags is the set of booleans flags that default to true under
// DefaultV1Flags is the set of boolean flags that default to true under
// v1 semantics. None of the non-boolean flags differ between v1 and v2.
DefaultV1Flags = 0 |
AllowDuplicateNames |
@@ -83,6 +84,12 @@ const (
// CanonicalizeNumbers is the set of flags related to raw number canonicalization.
CanonicalizeNumbers = CanonicalizeRawInts | CanonicalizeRawFloats
// TagFlags is the set of flags related to the presence of struct field tags.
// Tags have non-recursive effects, where the tag only applies to
// the top-level of the field value itself.
// Whenever descending into a JSON object or array, these flags are cleared.
TagFlags = StringTag | FormatTag
)
// Encoder and decoder flags.
@@ -120,10 +127,12 @@ const (
FormatNilSliceAsNull // marshal only
OmitZeroStructFields // marshal only
MatchCaseInsensitiveNames // marshal or unmarshal
DiscardUnknownMembers // marshal only
RejectUnknownMembers // unmarshal only
Marshalers // marshal only; non-boolean flag
Unmarshalers // unmarshal only; non-boolean flag
StringTag // marshal or unmarshal
FormatTag // marshal or unmarshal; non-boolean flag
FormatTagSupported // marshal or unmarshal
maxArshalV2Flag
)
@@ -143,7 +152,6 @@ const (
ParseTimeWithLooseRFC3339 // unmarshal
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
@@ -151,7 +159,7 @@ const (
)
// bitsUsed is the number of bits used in the 64-bit boolean flags
const bitsUsed = 42
const bitsUsed = 43
// Static compile check that bitsUsed and maxArshalV1Flag are in sync.
const _ = uint64((1<<bitsUsed)-maxArshalV1Flag) + uint64(maxArshalV1Flag-(1<<bitsUsed))
@@ -169,11 +177,11 @@ func (dst *Flags) Join(src Flags) {
// Copy over all source presence bits over to the destination (using OR),
// then invert the source presence bits to clear out source value (using AND-NOT),
// then copy over source value bits over to the destination (using OR).
// e.g., dst := Flags{Presence: 0b_1100_0011, Value: 0b_1000_0011}
// e.g., src := Flags{Presence: 0b_0101_1010, Value: 0b_1001_0010}
dst.Presence |= src.Presence // e.g., 0b_1100_0011 | 0b_0101_1010 -> 0b_110_11011
dst.Values &= ^src.Presence // e.g., 0b_1000_0011 & 0b_1010_0101 -> 0b_100_00001
dst.Values |= src.Values // e.g., 0b_1000_0001 | 0b_1001_0010 -> 0b_100_10011
// e.g., dst := Flags{Presence: 0b_1100_0011, Values: 0b_1000_0011}
// e.g., src := Flags{Presence: 0b_0101_1010, Values: 0b_1001_0010}
dst.Presence |= src.Presence // e.g., 0b_1100_0011 | 0b_0101_1010 -> 0b_1101_1011
dst.Values &= ^src.Presence // e.g., 0b_1000_0011 & 0b_1010_0101 -> 0b_1000_0001
dst.Values |= src.Values // e.g., 0b_1000_0001 | 0b_1001_0010 -> 0b_1001_0011
}
// Set sets both the presence and value for the provided bool (or set of bools).
@@ -182,10 +190,10 @@ func (fs *Flags) Set(f Bools) {
// then set the presence for all the identifier bits (using OR),
// then invert the identifier bits to clear out the values (using AND-NOT),
// then copy over all the identifier bits to the value if LSB is 1.
// e.g., fs := Flags{Presence: 0b_0101_0010, Value: 0b_0001_0010}
// e.g., fs := Flags{Presence: 0b_0101_0010, Values: 0b_0001_0010}
// e.g., f := 0b_1001_0001
id := uint64(f) &^ uint64(1) // e.g., 0b_1001_0001 & 0b_1111_1110 -> 0b_1001_0000
fs.Presence |= id // e.g., 0b_0101_0010 | 0b_1001_0000 -> 0b_1101_0011
fs.Presence |= id // e.g., 0b_0101_0010 | 0b_1001_0000 -> 0b_1101_0010
fs.Values &= ^id // e.g., 0b_0001_0010 & 0b_0110_1111 -> 0b_0000_0010
fs.Values |= uint64(f&1) * id // e.g., 0b_0000_0010 | 0b_1001_0000 -> 0b_1001_0010
}
@@ -207,7 +215,7 @@ func (fs Flags) Has(f Bools) bool {
// The value bit of f (i.e., the LSB) is ignored.
func (fs *Flags) Clear(f Bools) {
// Invert f to produce a mask to clear all bits in f (using AND).
// e.g., fs := Flags{Presence: 0b_0101_0010, Value: 0b_0001_0010}
// e.g., fs := Flags{Presence: 0b_0101_0010, Values: 0b_0001_0010}
// e.g., f := 0b_0001_1000
mask := uint64(^f) // e.g., 0b_0001_1000 -> 0b_1110_0111
fs.Presence &= mask // e.g., 0b_0101_0010 & 0b_1110_0111 -> 0b_0100_0010
+56 -41
View File
@@ -17,6 +17,11 @@ type Options interface {
JSONOptions(internal.NotForPublicUse)
}
// experimentalFormatTagSupporter is a special option method that allows
// [github.com/go-json-experiment/json.ExperimentalSupportFormatTag]
// to enable experimental `format` tag functionality at runtime.
type experimentalFormatTagSupporter interface{ ExperimentalSupportFormatTag() bool }
// Struct is the combination of all options in struct form.
// This is efficient to pass down the call stack and to query.
type Struct struct {
@@ -41,23 +46,22 @@ type ArshalValues struct {
Marshalers any // jsonflags.Marshalers
Unmarshalers any // jsonflags.Unmarshalers
Format string
FormatDepth int
Format string // valid if jsonflags.FormatTag is set
}
// DefaultOptionsV2 is the set of all options that define default v2 behavior.
var DefaultOptionsV2 = Struct{
Flags: jsonflags.Flags{
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
Values: uint64(0),
Presence: uint64(jsonflags.DefaultV1Flags),
Values: uint64(0), // all flags in DefaultV1Flags are false
},
}
// DefaultOptionsV1 is the set of all options that define default v1 behavior.
var DefaultOptionsV1 = Struct{
Flags: jsonflags.Flags{
Presence: uint64(jsonflags.AllFlags & ^jsonflags.WhitespaceFlags),
Values: uint64(jsonflags.DefaultV1Flags),
Presence: uint64(jsonflags.DefaultV1Flags),
Values: uint64(jsonflags.DefaultV1Flags), // all flags in DefaultV1Flags are true
},
}
@@ -82,6 +86,13 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
case jsonflags.Bools:
v := structOpts.Flags.Get(opt)
ok := structOpts.Flags.Has(opt)
if !ok && opt == jsonflags.StringifyNumbers && structOpts.Flags.Get(jsonflags.StringTag) {
return any(true).(T), true // check also whether the option is specified via a `string` tag
}
return any(v).(T), ok
case experimentalFormatTagSupporter:
v := structOpts.Flags.Get(jsonflags.FormatTagSupported)
ok := structOpts.Flags.Has(jsonflags.FormatTagSupported)
return any(v).(T), ok
case Indent:
if !structOpts.Flags.Has(jsonflags.Indent) {
@@ -114,72 +125,54 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
var JoinUnknownOption = func(Struct, Options) Struct { 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:
if excludeCoderOptions {
continue
case experimentalFormatTagSupporter:
if src.ExperimentalSupportFormatTag() {
dst.Flags.Set(jsonflags.FormatTagSupported | 1)
} else {
dst.Flags.Set(jsonflags.FormatTagSupported | 0)
}
case Indent:
dst.Flags.Set(jsonflags.Multiline | jsonflags.Indent | 1)
dst.Indent = string(src)
case IndentPrefix:
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:
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.Flags.Join(src.Flags)
if src.Flags.Has(jsonflags.NonBooleanFlags) {
if src.Flags.Has(jsonflags.Indent) {
dst.Indent = src.Indent
}
if srcFlags.Has(jsonflags.IndentPrefix) {
if src.Flags.Has(jsonflags.IndentPrefix) {
dst.IndentPrefix = src.IndentPrefix
}
if srcFlags.Has(jsonflags.ByteLimit) {
if src.Flags.Has(jsonflags.ByteLimit) {
dst.ByteLimit = src.ByteLimit
}
if srcFlags.Has(jsonflags.DepthLimit) {
if src.Flags.Has(jsonflags.DepthLimit) {
dst.DepthLimit = src.DepthLimit
}
if srcFlags.Has(jsonflags.Marshalers) {
if src.Flags.Has(jsonflags.Marshalers) {
dst.Marshalers = src.Marshalers
}
if srcFlags.Has(jsonflags.Unmarshalers) {
if src.Flags.Has(jsonflags.Unmarshalers) {
dst.Unmarshalers = src.Unmarshalers
}
if src.Flags.Has(jsonflags.FormatTag) {
dst.Format = src.Format
}
}
default:
*dst = JoinUnknownOption(*dst, src)
@@ -187,6 +180,28 @@ func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) {
}
}
// InitializeMultiline sets default options implied by Multiline.
func (s *Struct) InitializeMultiline() {
if !s.Flags.Has(jsonflags.SpaceAfterColon) {
s.Flags.Set(jsonflags.SpaceAfterColon | 1)
}
if !s.Flags.Has(jsonflags.SpaceAfterComma) {
s.Flags.Set(jsonflags.SpaceAfterComma | 0)
}
if !s.Flags.Has(jsonflags.Indent) {
s.Flags.Set(jsonflags.Indent | 1)
s.Indent = "\t"
}
}
// ChangedWhitespace reports whether whitespace values have changed.
func ChangedWhitespace(s1, s2 Struct) bool {
return s1.Flags.Get(jsonflags.Multiline) != s2.Flags.Get(jsonflags.Multiline) ||
s1.Flags.Get(jsonflags.SpaceAfterColon) != s2.Flags.Get(jsonflags.SpaceAfterColon) ||
s1.Flags.Get(jsonflags.SpaceAfterComma) != s2.Flags.Get(jsonflags.SpaceAfterComma) ||
(s2.Flags.Get(jsonflags.Multiline) && (s1.Indent != s2.Indent || s1.IndentPrefix != s2.IndentPrefix))
}
type (
Indent string // jsontext.WithIndent
IndentPrefix string // jsontext.WithIndentPrefix
@@ -0,0 +1,124 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
package jsonopts
// NOTE: While [ExperimentalSupportFormatTag] is exported,
// it is in an internal package and thus inaccessible for public use.
//
// The [github.com/go-json-experiment/json] module is kept in sync
// with the Go standard standard library and will expose this option
// in a way that public code can now directly reference.
type supportFormatTag struct {
Options
v bool
}
var (
enableFormatTag = supportFormatTag{v: true}
disableFormatTag = supportFormatTag{v: false}
)
// ExperimentalSupportFormatTag is a marker method that is specially
// recognized by the "github.com/go-json-experiment/json" runtime.
func (o *supportFormatTag) ExperimentalSupportFormatTag() bool {
return o.v
}
// ExperimentalSupportFormatTag enables support for the `format` tag.
//
// WARNING: This is an experimental feature and will be removed in the future
// as either a failed experiment or be formally included in "github.com/go-json-experiment/json"
// in some semantically similar form, in which case, users of this option
// must migrate to the officially supported feature.
//
// The `format` tag was originally part of the "github.com/go-json-experiment/json" experiment
// but support for it was removed (see https://go.dev/issue/79071) for the
// initial release of "github.com/go-json-experiment/json" in light of the anticipation that
// the Go language might support typed struct tags (https://go.dev/issue/74472).
//
// Typed struct tags are a more expressive and type-safe way to express format
// attributes than the bespoke `format` tag option that implements a miniature
// domain-specific language (DSL) within the "json" package itself.
//
// While "github.com/go-json-experiment/json" was in the experimental phase,
// some users were already depending on the `format` tag option.
// This experimental option exists to provide a temporary workaround
// until the (hopeful) inclusion of typed struct tags and support for
// formatting directives in "github.com/go-json-experiment/json" using that language mechanism.
//
// This option enables support for the `format` tag option, which specifies
// a format flag used to specialize the formatting of the field value.
// The option is a key-value pair specified as "format:value" where
// the value must be either a literal consisting of letters and numbers
// (e.g., `format:RFC3339`) or a single-quoted string literal
// (e.g., `format:'2006-01-02'`). The interpretation of the format flag
// is determined by the struct field type.
//
// Go types with alternative representations are as follows:
//
// - A Go []byte or [N]byte is usually represented as a JSON string
// containing the binary value encoded using RFC 4648.
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
// If the format is "base64url", then this uses RFC 4648, section 5.
// If the format is "base32", then this uses RFC 4648, section 6.
// If the format is "base32hex", then this uses RFC 4648, section 7.
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
// If the format is "array", then the bytes value is represented as a JSON array
// where each element recursively uses the JSON representation of each byte.
//
// - A Go float is usually represented as a JSON number.
// If the format is "nonfinite", then NaN, +Inf, and -Inf are represented as
// the JSON strings "NaN", "Infinity", and "-Infinity", respectively.
// Without the use of this format, such string values result in a [SemanticError].
//
// - A nil Go map is usually encoded using an empty JSON object.
// 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.
//
// - A nil Go slice is usually encoded using an empty JSON array.
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
// regardless of whether [FormatNilSliceAsNull] is specified.
//
// - A Go pointer usually uses the JSON representation of the underlying value.
// The format is forwarded to the marshaling and unmarshaling of the underlying type.
//
// - A Go [time.Time] is usually represented as a JSON string containing
// the timestamp formatted in RFC 3339 with nanosecond precision.
// If the format matches one of the format constants declared
// in the time package (e.g., RFC1123), then that format is used.
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
// then the timestamp is represented as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
// To avoid a fractional component when encoding,
// round the timestamp to the relevant unit.
// Otherwise if non-empty, the format is used as-is and
// encoded using [time.Time.Format] and
// decoded using [time.Time.Parse].
//
// - A Go [time.Duration] usually has no default representation.
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is represented as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
// To avoid a fractional component when encoding,
// round the duration to the relevant unit.
// If the format is "units", it is represented as a JSON string
// encoded using [time.Duration.String] and decoded using [time.ParseDuration]
// (e.g., "1h30m" for 1 hour 30 minutes).
// If the format is "iso8601", it is represented as a JSON string using the
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
// using only accurate units of hours, minutes, and seconds.
func ExperimentalSupportFormatTag(v bool) Options {
if v {
return &enableFormatTag
} else {
return &disableFormatTag
}
}
+13 -36
View File
@@ -39,7 +39,7 @@ func ConsumeWhitespace(b []byte) (n int) {
}
// ConsumeNull consumes the next JSON null literal per RFC 7159, section 3.
// It returns 0 if it is invalid, in which case consumeLiteral should be used.
// It returns 0 if it is invalid, in which case ConsumeLiteral should be used.
func ConsumeNull(b []byte) int {
// NOTE: The arguments and logic are kept simple to keep this inlinable.
const literal = "null"
@@ -50,7 +50,7 @@ func ConsumeNull(b []byte) int {
}
// ConsumeFalse consumes the next JSON false literal per RFC 7159, section 3.
// It returns 0 if it is invalid, in which case consumeLiteral should be used.
// It returns 0 if it is invalid, in which case ConsumeLiteral should be used.
func ConsumeFalse(b []byte) int {
// NOTE: The arguments and logic are kept simple to keep this inlinable.
const literal = "false"
@@ -61,7 +61,7 @@ func ConsumeFalse(b []byte) int {
}
// ConsumeTrue consumes the next JSON true literal per RFC 7159, section 3.
// It returns 0 if it is invalid, in which case consumeLiteral should be used.
// It returns 0 if it is invalid, in which case ConsumeLiteral should be used.
func ConsumeTrue(b []byte) int {
// NOTE: The arguments and logic are kept simple to keep this inlinable.
const literal = "true"
@@ -88,7 +88,7 @@ func ConsumeLiteral(b []byte, lit string) (n int, err error) {
// ConsumeSimpleString consumes the next JSON string per RFC 7159, section 7
// but is limited to the grammar for an ASCII string without escape sequences.
// It returns 0 if it is invalid or more complicated than a simple string,
// in which case consumeString should be called.
// in which case [ConsumeString] should be called.
//
// It rejects '<', '>', and '&' for compatibility reasons since these were
// always escaped in the v1 implementation. Thus, if this function reports
@@ -118,7 +118,7 @@ func ConsumeString(flags *ValueFlags, b []byte, validateUTF8 bool) (n int, err e
return ConsumeStringResumable(flags, b, 0, validateUTF8)
}
// ConsumeStringResumable is identical to consumeString but supports resuming
// ConsumeStringResumable is identical to [ConsumeString] but supports resuming
// from a previous call that returned io.ErrUnexpectedEOF.
func ConsumeStringResumable(flags *ValueFlags, b []byte, resumeOffset int, validateUTF8 bool) (n int, err error) {
// Consume the leading double quote.
@@ -254,7 +254,7 @@ func ConsumeStringResumable(flags *ValueFlags, b []byte, resumeOffset int, valid
// Any invalid UTF-8 within the string will be replaced with utf8.RuneError,
// but the error will be specified as having encountered such an error.
// The input must be an entire JSON string with no surrounding whitespace.
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, err error) {
func AppendUnquote(dst, src []byte) (v []byte, err error) {
dst = slices.Grow(dst, len(src))
// Consume the leading double quote.
@@ -292,7 +292,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
return dst, err
}
switch r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:]))); {
switch r, rn := utf8.DecodeRune(src[n:]); {
// Handle UTF-8 encoded byte sequence.
// Due to specialized handling of ASCII above, we know that
// all normal sequences at this point must be 2 bytes or larger.
@@ -364,7 +364,7 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) (v []byte, er
// Handle invalid UTF-8.
case r == utf8.RuneError:
dst = append(dst, src[i:n]...)
if !utf8.FullRuneInString(string(truncateMaxUTF8(src[n:]))) {
if !utf8.FullRune(src[n:]) {
return dst, io.ErrUnexpectedEOF
}
// NOTE: An unescaped string may be longer than the escaped string
@@ -387,7 +387,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 {
func hasEscapedUTF16Prefix(b []byte, lowerSurrogateHalf bool) bool {
for i := range len(b) {
switch c := b[i]; {
case i == 0 && c != '\\':
@@ -420,9 +420,9 @@ func UnquoteMayCopy(b []byte, isVerbatim bool) []byte {
}
// ConsumeSimpleNumber consumes the next JSON number per RFC 7159, section 6
// but is limited to the grammar for a positive integer.
// but is limited to the grammar for a non-negative integer.
// It returns 0 if it is invalid or more complicated than a simple integer,
// in which case consumeNumber should be called.
// in which case ConsumeNumber should be called.
func ConsumeSimpleNumber(b []byte) (n int) {
// NOTE: The arguments and logic are kept simple to keep this inlinable.
if len(b) > 0 {
@@ -467,7 +467,7 @@ func ConsumeNumber(b []byte) (n int, err error) {
return n, err
}
// ConsumeNumberResumable is identical to consumeNumber but supports resuming
// ConsumeNumberResumable is identical to ConsumeNumber but supports resuming
// from a previous call that returned io.ErrUnexpectedEOF.
func ConsumeNumberResumable(b []byte, resumeOffset int, state ConsumeNumberState) (n int, _ ConsumeNumberState, err error) {
// Jump to the right state when resuming from a partial consumption.
@@ -565,7 +565,7 @@ beforeExponent:
// parseHexUint16 is similar to strconv.ParseUint,
// but operates directly on []byte and is optimized for base-16.
// See https://go.dev/issue/42429.
func parseHexUint16[Bytes ~[]byte | ~string](b Bytes) (v uint16, ok bool) {
func parseHexUint16(b []byte) (v uint16, ok bool) {
if len(b) != 4 {
return 0, false
}
@@ -604,26 +604,3 @@ func ParseUint(b []byte) (v uint64, ok bool) {
}
return v, true
}
// ParseFloat parses a floating point number according to the Go float grammar.
// Note that the JSON number grammar is a strict subset.
//
// If the number overflows the finite representation of a float,
// 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) {
fv, err := strconv.ParseFloat(string(b), bits)
if math.IsInf(fv, 0) {
switch {
case bits == 32 && math.IsInf(fv, +1):
fv = +math.MaxFloat32
case bits == 64 && math.IsInf(fv, +1):
fv = +math.MaxFloat64
case bits == 32 && math.IsInf(fv, -1):
fv = -math.MaxFloat32
case bits == 64 && math.IsInf(fv, -1):
fv = -math.MaxFloat64
}
}
return fv, err == nil
}
+7 -7
View File
@@ -32,7 +32,7 @@ var escapeASCII = [...]uint8{
// NeedEscape reports whether src needs escaping of any characters.
// It conservatively assumes EscapeForHTML and EscapeForJS.
// It reports true for inputs with invalid UTF-8.
func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
func NeedEscape(src []byte) bool {
var i int
for uint(len(src)) > uint(i) {
if c := src[i]; c < utf8.RuneSelf {
@@ -41,7 +41,7 @@ func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
}
i++
} else {
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[i:])))
r, rn := utf8.DecodeRune(src[i:])
if r == utf8.RuneError || r == '\u2028' || r == '\u2029' {
return true
}
@@ -62,7 +62,7 @@ func NeedEscape[Bytes ~[]byte | ~string](src Bytes) bool {
// invalid bytes are replaced with the Unicode replacement character ('\ufffd').
// If no escape flags are set, then the shortest representable form is used,
// which is also the canonical form for strings (RFC 8785, section 3.2.2.2).
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflags.Flags) ([]byte, error) {
func AppendQuote(dst, src []byte, flags *jsonflags.Flags) ([]byte, error) {
var i, n int
var hasInvalidUTF8 bool
dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`))
@@ -82,7 +82,7 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes, flags *jsonflag
}
} else {
// Handle multi-byte Unicode.
r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:])))
r, rn := utf8.DecodeRune(src[n:])
n += rn
if r != utf8.RuneError && r != '\u2028' && r != '\u2029' {
continue // no escaping possibly needed
@@ -178,7 +178,7 @@ func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error
}
i++
} else {
r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:]))
r, rn := utf8.DecodeRune(src[i:])
if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) {
dst = append(dst, src[lastAppendIndex:i]...)
dst = appendEscapedUnicode(dst, r)
@@ -193,7 +193,7 @@ func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error
// 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.
// these sequences according to the current flags.
b, _ := AppendUnquote(nil, src[:n])
dst, _ = AppendQuote(dst, b, flags)
return dst, n, nil
@@ -235,7 +235,7 @@ func AppendFloat(dst []byte, src float64, bits int) []byte {
return dst
}
// ReformatNumber consumes a JSON string from src and appends it to dst,
// ReformatNumber consumes a JSON number 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, flags *jsonflags.Flags) ([]byte, int, error) {
+30 -38
View File
@@ -17,7 +17,7 @@ import (
"unicode/utf8"
)
// TrimSuffixWhitespace trims JSON from the end of b.
// TrimSuffixWhitespace trims JSON whitespace from the end of b.
func TrimSuffixWhitespace(b []byte) []byte {
// NOTE: The arguments and logic are kept simple to keep this inlinable.
n := len(b) - 1
@@ -59,8 +59,8 @@ func TrimSuffixByte(b []byte, c byte) []byte {
}
// QuoteRune quotes the first rune in the input.
func QuoteRune[Bytes ~[]byte | ~string](b Bytes) string {
r, n := utf8.DecodeRuneInString(string(truncateMaxUTF8(b)))
func QuoteRune(b []byte) string {
r, n := utf8.DecodeRune(b)
if r == utf8.RuneError && n == 1 {
return `'\x` + strconv.FormatUint(uint64(b[0]), 16) + `'`
}
@@ -70,7 +70,7 @@ func QuoteRune[Bytes ~[]byte | ~string](b Bytes) string {
// CompareUTF16 lexicographically compares x to y according
// to the UTF-16 codepoints of the UTF-8 encoded input strings.
// This implements the ordering specified in RFC 8785, section 3.2.3.
func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
func CompareUTF16(x, y []byte) int {
// NOTE: This is an optimized, mostly allocation-free implementation
// of CompareUTF16Simple in wire_test.go. FuzzCompareUTF16 verifies that the
// two implementations agree on the result of comparing any two strings.
@@ -93,8 +93,8 @@ func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
}
// Decode next pair of runes as UTF-8.
rx, nx := utf8.DecodeRuneInString(string(truncateMaxUTF8(x)))
ry, ny := utf8.DecodeRuneInString(string(truncateMaxUTF8(y)))
rx, nx := utf8.DecodeRune(x)
ry, ny := utf8.DecodeRune(y)
selfx := isUTF16Self(rx)
selfy := isUTF16Self(ry)
@@ -123,49 +123,41 @@ func CompareUTF16[Bytes ~[]byte | ~string](x, y Bytes) int {
}
}
// truncateMaxUTF8 truncates b such it contains at least one rune.
//
// The utf8 package currently lacks generic variants, which complicates
// generic functions that operates on either []byte or string.
// As a hack, we always call the utf8 function operating on strings,
// but always truncate the input such that the result is identical.
//
// Example usage:
//
// utf8.DecodeRuneInString(string(truncateMaxUTF8(b)))
//
// Converting a []byte to a string is stack allocated since
// truncateMaxUTF8 guarantees that the []byte is short.
func truncateMaxUTF8[Bytes ~[]byte | ~string](b Bytes) Bytes {
// TODO(https://go.dev/issue/56948): Remove this function and
// instead directly call generic utf8 functions wherever used.
if len(b) > utf8.UTFMax {
return b[:utf8.UTFMax]
}
return b
}
// 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 errors.New("invalid character " + what + " " + where)
_, n := utf8.DecodeRune([]byte(prefix))
return &InvalidTextError{"character", string(prefix[:n]), where}
}
func NewInvalidEscapeSequenceError[Bytes ~[]byte | ~string](what Bytes) error {
label := "escape sequence"
if len(what) > 6 {
label = "surrogate pair"
return &InvalidTextError{"surrogate pair", string(what), "in string"}
}
needEscape := strings.IndexFunc(string(what), func(r rune) bool {
return &InvalidTextError{"escape sequence", string(what), "in string"}
}
type InvalidTextError struct {
Label string // e.g., "character" | "escape sequence" | "surrogate pair"
What string // raw invalid text
Where string // e.g., "in string" | "at start of value"
}
func (e *InvalidTextError) Error() string {
var what string
needEscape := strings.ContainsFunc(e.What, func(r rune) bool {
return r == '`' || r == utf8.RuneError || unicode.IsSpace(r) || !unicode.IsPrint(r)
}) >= 0
if needEscape {
return errors.New("invalid " + label + " " + strconv.Quote(string(what)) + " in string")
} else {
return errors.New("invalid " + label + " `" + string(what) + "` in string")
})
switch {
case utf8.RuneCount([]byte(e.What)) == 1:
what = QuoteRune([]byte(e.What))
case needEscape:
what = strconv.Quote(e.What)
default:
what = "`" + e.What + "`"
}
return strings.TrimSuffix("invalid "+e.Label+" "+what+" "+e.Where, " ")
}
// TruncatePointer optionally truncates the JSON pointer,
+57 -38
View File
@@ -36,7 +36,7 @@
//
// - a JSON literal, string, or number
// - a JSON object (e.g., `{"name":"value"}`)
// - a JSON array (e.g., `[1,2,3,]`)
// - a JSON array (e.g., `[1,2,3]`)
//
// A JSON value is represented by the [Value] type in Go and is a []byte
// containing the raw textual representation of the value. There is some overlap
@@ -45,18 +45,18 @@
//
// The [Encoder] and [Decoder] types contain methods to read or write the next
// [Token] or [Value] in a sequence. They maintain a state machine to validate
// whether the sequence of JSON tokens and/or values produces a valid JSON.
// whether the sequence of JSON tokens and/or values produces valid JSON.
// [Options] may be passed to the [NewEncoder] or [NewDecoder] constructors
// to configure the syntactic behavior of encoding and decoding.
// to configure the behavior of encoding and decoding.
//
// # Terminology
//
// The terms "encode" and "decode" are used for syntactic functionality
// that is concerned with processing JSON based on its grammar, and
// the terms "marshal" and "unmarshal" are used for semantic functionality
// that determines the meaning of JSON values as Go values and vice-versa.
// This package (i.e., [jsontext]) deals with JSON at a syntactic layer,
// while [encoding/json/v2] deals with JSON at a semantic layer.
// that determines the meaning of JSON values as Go values and vice versa.
// This package deals with JSON syntax,
// while [encoding/json/v2] deals with JSON semantics.
// The goal is to provide a clear distinction between functionality that
// is purely concerned with encoding versus that of marshaling.
// For example, one can directly encode a stream of JSON tokens without
@@ -136,7 +136,7 @@ import (
// d.ReadValue() // {"k":"v"}
// d.ReadToken() // }
//
// The above is one of many possible sequence of calls and
// The above is one of many possible sequences of calls and
// may not represent the most sensible method to call for any given token/value.
// For example, it is probably more common to call [Decoder.ReadToken] to obtain a
// string token for object names.
@@ -151,7 +151,7 @@ func NewDecoder(r io.Reader, opts ...Options) *Decoder {
return jsontext.NewDecoder(r, opts...)
}
// Encoder is a streaming encoder from raw JSON tokens and values.
// Encoder is a streaming encoder to raw JSON tokens and values.
// It is used to write a stream of top-level JSON values,
// each terminated with a newline character.
//
@@ -176,7 +176,7 @@ func NewDecoder(r io.Reader, opts ...Options) *Decoder {
// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}
// e.WriteToken(EndObject) // }
//
// The above is one of many possible sequence of calls and
// The above is one of many possible sequences of calls and
// may not represent the most sensible method to call for any given token/value.
// For example, it is probably more common to call [Encoder.WriteToken] with a string
// for object names.
@@ -193,7 +193,7 @@ func NewEncoder(w io.Writer, opts ...Options) *Encoder {
return jsontext.NewEncoder(w, opts...)
}
// SyntacticError is a description of a syntactic error that occurred when
// SyntacticError is a description of an error that occurred when
// encoding or decoding JSON according to the grammar.
//
// The contents of this error as produced by this package may change over time.
@@ -202,7 +202,7 @@ type SyntacticError = jsontext.SyntacticError
// Options configures [NewEncoder], [Encoder.Reset], [NewDecoder],
// and [Decoder.Reset] with specific features.
// Each function takes in a variadic list of options, where properties
// set in latter options override the value of previously set properties.
// set in later options override the value of previously set properties.
//
// There is a single Options type, which is used with both encoding and decoding.
// Some options affect both operations, while others only affect one operation:
@@ -308,7 +308,7 @@ func CanonicalizeRawInts(v bool) Options {
// It is safe to canonicalize a serialized single precision number and
// parse it back as a single precision number and expect the same value.
// If a number exceeds ±1.7976931348623157e+308, which is the maximum
// finite number, then it saturated at that value and formatted as such.
// finite number, then it is saturated at that value and formatted as such.
//
// This only affects encoding and is ignored when decoding.
func CanonicalizeRawFloats(v bool) Options {
@@ -350,7 +350,7 @@ func SpaceAfterComma(v bool) Options {
// If [SpaceAfterComma] is not specified, then the default is false.
// If [WithIndent] is not specified, then the default is "\t".
//
// If set to false, then the output is a single-line,
// If set to false, then the output is a single line,
// where the only whitespace emitted is determined by the current
// values of [SpaceAfterColon] and [SpaceAfterComma].
//
@@ -363,9 +363,9 @@ func Multiline(v bool) Options {
// where each element in a JSON object or array begins on a new, indented line
// beginning with the indent prefix (see [WithIndentPrefix])
// followed by one or more copies of indent according to the nesting depth.
// The indent must only be composed of space or tab characters.
// The indent must be composed of only space and tab characters.
//
// If the intent to emit indented output without a preference for
// If the intent is to emit indented output without a preference for
// the particular indent string, then use [Multiline] instead.
//
// This only affects encoding and is ignored when decoding.
@@ -378,7 +378,7 @@ func WithIndent(indent string) Options {
// where each element in a JSON object or array begins on a new, indented line
// beginning with the indent prefix followed by one or more copies of indent
// (see [WithIndent]) according to the nesting depth.
// The prefix must only be composed of space or tab characters.
// The prefix must be composed of only space and tab characters.
//
// This only affects encoding and is ignored when decoding.
// Use of this option implies [Multiline] being set to true.
@@ -414,8 +414,8 @@ func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, erro
// The name of a duplicate JSON object member can be extracted as:
//
// err := ...
// var serr jsontext.SyntacticError
// if errors.As(err, &serr) && serr.Err == jsontext.ErrDuplicateName {
// serr, ok := errors.AsType[*jsontext.SyntacticError](err)
// if ok && serr.Err == jsontext.ErrDuplicateName {
// ptr := serr.JSONPointer // JSON pointer to duplicate name
// name := ptr.LastToken() // duplicate name itself
// ...
@@ -437,12 +437,12 @@ var ErrNonStringName = jsontext.ErrNonStringName
// either a JSON object name or an index to a JSON array element
// encoded as a base-10 integer value.
// It is impossible to distinguish between an array index and an object name
// (that happens to be an base-10 encoded integer) without also knowing
// (that happens to be a base-10 encoded integer) without also knowing
// the structure of the top-level JSON value that the pointer refers to.
//
// There is exactly one representation of a pointer to a particular value,
// so comparability of Pointer values is equivalent to checking whether
// they both point to the exact same value.
// they both point to the same value.
type Pointer = jsontext.Pointer
// Token represents a lexical JSON token, which may be one of the following:
@@ -455,6 +455,20 @@ type Pointer = jsontext.Pointer
// A Token cannot represent entire array or object values, while a [Value] can.
// There is no Token to represent commas and colons since
// these structural tokens can be inferred from the surrounding context.
//
// A Token stores data in one of two forms:
//
// - As raw JSON text: backed by the internal buffer of the [Decoder]
// and only ever produced by [Decoder.ReadToken].
// Such a token is only valid until the next call to any method on that
// [Decoder] (e.g., [Decoder.PeekKind], [Decoder.ReadToken],
// [Decoder.ReadValue], or [Decoder.SkipValue]).
// Call [Token.Clone] to copy the raw text into an independent allocation
// that persists beyond subsequent [Decoder] calls.
//
// - As a typed Go value: a self-contained representation produced by
// the constructor functions (e.g., [String], [Int], [Uint], [Float]).
// Such tokens are valid indefinitely and do not need to be cloned.
type Token = jsontext.Token
var (
@@ -479,7 +493,10 @@ func String(s string) Token {
return jsontext.String(s)
}
// Float constructs a Token representing a JSON number.
// Float constructs a Token representing a JSON number as
// a 64-bit floating-point number formatted according to
// ECMA-262, 10th edition, section 7.1.12.1 and RFC 8785, section 3.2.2.3.
// with the exception that -0 is still formatted as -0.
// The values NaN, +Inf, and -Inf will be represented
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
func Float(n float64) Token {
@@ -496,32 +513,34 @@ func Uint(n uint64) Token {
return jsontext.Uint(n)
}
// Kind represents each possible JSON token kind with a single byte,
// which is conveniently the first byte of that kind's grammar
// with the restriction that numbers always be represented with '0':
// A Kind represents the kind of a JSON token.
//
// - 'n': null
// - 'f': false
// - 't': true
// - '"': string
// - '0': number
// - '{': object begin
// - '}': object end
// - '[': array begin
// - ']': array end
//
// An invalid kind is usually represented using 0,
// but may be non-zero due to invalid JSON data.
// A Kind is a single byte, which is conveniently the first byte of that
// kind's symbol in the grammar (except for numbers, which are always represented
// with '0').
type Kind = jsontext.Kind
const (
KindInvalid = jsontext.KindInvalid
KindNull = jsontext.KindNull
KindFalse = jsontext.KindFalse
KindTrue = jsontext.KindTrue
KindString = jsontext.KindString
KindNumber = jsontext.KindNumber
KindBeginObject = jsontext.KindBeginObject
KindEndObject = jsontext.KindEndObject
KindBeginArray = jsontext.KindBeginArray
KindEndArray = jsontext.KindEndArray
)
// AppendFormat formats the JSON value in src and appends it to dst
// according to the specified options.
// See [Value.Format] for more details about the formatting behavior.
//
// The dst and src may overlap.
// If an error is reported, then the entirety of src is appended to dst.
func AppendFormat(dst, src []byte, opts ...Options) ([]byte, error) {
return jsontext.AppendFormat(dst, src, opts...)
func AppendFormat[Bytes ~[]byte | ~string](dst []byte, src Bytes, opts ...Options) ([]byte, error) {
return jsontext.AppendFormat(dst, []byte(src), opts...)
}
// Value represents a single raw JSON value, which may be one of the following:
+59 -18
View File
@@ -34,11 +34,11 @@ import (
// return the end of the consumed JSON token as a position,
// also relative to the start of Decoder.buf.
//
// - In the event of an I/O errors or state machine violations,
// - In the event of an I/O error or state machine violations,
// the implementation avoids mutating the state of Decoder
// (aside from the book-keeping needed to implement Decoder.fetch).
// For this reason, only Decoder.ReadToken and Decoder.ReadValue are
// responsible for updated Decoder.prevStart and Decoder.prevEnd.
// responsible for updating Decoder.prevStart and Decoder.prevEnd.
//
// - For performance, much of the implementation uses the pattern of calling
// the inlinable consumeXXX functions first, and if more work is necessary,
@@ -71,7 +71,7 @@ import (
// d.ReadValue() // {"k":"v"}
// d.ReadToken() // }
//
// The above is one of many possible sequence of calls and
// The above is one of many possible sequences of calls and
// may not represent the most sensible method to call for any given token/value.
// For example, it is probably more common to call [Decoder.ReadToken] to obtain a
// string token for object names.
@@ -80,7 +80,7 @@ type Decoder struct {
}
// decoderState is the low-level state of Decoder.
// It has exported fields and method for use by the "json" package.
// It has exported fields and methods for use by the "json" package.
type decoderState struct {
state
decodeBuffer
@@ -126,7 +126,7 @@ func NewDecoder(r io.Reader, opts ...Options) *Decoder {
}
// Reset resets a decoder such that it is reading afresh from r and
// configured with the provided options. Reset must not be called on an
// configured with the provided options. Reset must not be called on
// a Decoder passed to the [encoding/json/v2.UnmarshalerFrom.UnmarshalJSONFrom] method
// or the [encoding/json/v2.UnmarshalFromFunc] function.
func (d *Decoder) Reset(r io.Reader, opts ...Options) {
@@ -154,7 +154,7 @@ func (d *decoderState) reset(b []byte, r io.Reader, opts ...Options) {
d.Struct = opts2
}
// Options returns the options used to construct the encoder and
// Options returns the options used to construct the decoder and
// may additionally contain semantic options passed to a
// [encoding/json/v2.UnmarshalDecode] call.
//
@@ -166,6 +166,8 @@ func (d *Decoder) Options() Options {
return &d.s.Struct
}
func (d *decoderState) options() *jsonopts.Struct { return &d.Struct }
var errBufferWriteAfterNext = errors.New("invalid bytes.Buffer.Write call after calling bytes.Buffer.Next")
// fetch reads at least 1 byte from the underlying io.Reader.
@@ -193,7 +195,7 @@ func (d *decoderState) fetch() error {
// may overwrite the contents of the current buffer.
//
// The user is trying to use a bytes.Buffer as a pipe,
// but a bytes.Buffer is poor implementation of a pipe,
// but a bytes.Buffer is a poor implementation of a pipe,
// the purpose-built io.Pipe should be used instead.
return &ioError{action: "read", err: errBufferWriteAfterNext}
}
@@ -306,7 +308,7 @@ func (d *decodeBuffer) PreviousTokenOrValue() []byte {
// PeekKind retrieves the next token kind, but does not advance the read offset.
//
// It returns 0 if an error occurs. Any such error is cached until
// It returns [KindInvalid] if an error occurs. Any such error is cached until
// the next read call and it is the caller's responsibility to eventually
// follow up a PeekKind call with a read call.
func (d *Decoder) PeekKind() Kind {
@@ -407,10 +409,11 @@ func (d *decoderState) checkDelim(delim byte, next Kind) error {
}
// SkipValue is semantically equivalent to calling [Decoder.ReadValue] and discarding
// the result except that memory is not wasted trying to hold the entire result.
// the result, except that memory is not wasted trying to hold the entire result.
func (d *Decoder) SkipValue() error {
return d.s.SkipValue()
}
func (d *decoderState) SkipValue() error {
switch d.PeekKind() {
case '{', '[':
@@ -621,6 +624,7 @@ func (d *decoderState) ReadToken() (Token, error) {
if !d.Flags.Get(jsonflags.AllowDuplicateNames) {
d.Namespaces.push()
}
d.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
pos += 1
d.prevStart, d.prevEnd = pos, pos
return BeginObject, nil
@@ -641,6 +645,7 @@ func (d *decoderState) ReadToken() (Token, error) {
if err = d.Tokens.pushArray(); err != nil {
return Token{}, wrapSyntacticError(d, err, pos, +1)
}
d.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
pos += 1
d.prevStart, d.prevEnd = pos, pos
return BeginArray, nil
@@ -792,6 +797,12 @@ func (d *decoderState) CheckNextValue(last bool) error {
return nil
}
// AtEOF reports whether the decoder is at EOF.
func (d *decoderState) AtEOF() bool {
_, err := d.consumeWhitespace(d.prevEnd)
return err == io.ErrUnexpectedEOF
}
// CheckEOF verifies that the input has no more data.
func (d *decoderState) CheckEOF() error {
return d.checkEOF(d.prevEnd)
@@ -1121,24 +1132,54 @@ func (d *decoderState) consumeArray(flags *jsonwire.ValueFlags, pos, depth int)
// InputOffset returns the current input byte offset. It gives the location
// of the next byte immediately after the most recently returned token or value.
// The number of bytes actually read from the underlying [io.Reader] may be more
// than this offset due to internal buffering effects.
// than this offset due to internal buffering.
func (d *Decoder) InputOffset() int64 {
return d.s.previousOffsetEnd()
}
// UnreadBuffer returns the data remaining in the unread buffer,
// which may contain zero or more bytes.
// This is the data already consumed from the input [io.Reader],
// but not yet read by a [Decoder.ReadToken] or [Decoder.ReadValue] call.
// It may contain bytes that do not form valid JSON, since it has not yet
// been validated according to the JSON grammar.
// The exact amount of buffered data is an implementation detail
// of the Decoder and may change over time.
//
// It is the caller's responsibility to concatenate this buffer with
// the remainder of the input Reader to obtain the full sequence
// of bytes after the last read JSON token or value.
//
// The returned buffer must not be mutated while Decoder continues to be used.
// The buffer contents are valid until the next Peek, Read, or Skip call.
func (d *Decoder) UnreadBuffer() []byte {
return d.s.unreadBuffer()
}
// StackDepth returns the depth of the state machine for read JSON data.
// StackDepth returns the depth of the state machine for JSON data
// that has already been read.
// Each level on the stack represents a nested JSON object or array.
// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered
// It is incremented whenever a [BeginObject] or [BeginArray] token is encountered
// and decremented whenever an [EndObject] or [EndArray] token is encountered.
// The depth is zero-indexed, where zero represents the top-level JSON value.
//
// StackDepth returns 0 when not inside any object or array.
// In particular, it returns 0 before any tokens have been read,
// after any top-level value has been read, and between values
// when decoding a stream of top-level values (e.g., NDJSON).
// StackDepth returns 1 inside a top-level object or array,
// 2 inside a nested object or array, and so on.
//
// For example, consider decoding the following JSON:
//
// {"a": [1, 2], "b": {"c": 3}}
//
// While decoding, StackDepth would report the following:
//
// - At the start, StackDepth reports 0.
// - After decoding the outer '{', StackDepth reports 1.
// - After decoding the inner '[', StackDepth reports 2.
// - After decoding the inner ']', StackDepth reports 1.
// - After decoding the outer '}', StackDepth reports 0.
func (d *Decoder) StackDepth() int {
// NOTE: Keep in sync with Encoder.StackDepth.
return d.s.Tokens.Depth() - 1
@@ -1148,13 +1189,13 @@ func (d *Decoder) StackDepth() int {
// It must be a number between 0 and [Decoder.StackDepth], inclusive.
// For each level, it reports the kind:
//
// - 0 for a level of zero,
// - '{' for a level representing a JSON object, and
// - '[' for a level representing a JSON array.
// - [KindInvalid] for a level of zero,
// - [KindBeginObject] for a level representing a JSON object, and
// - [KindBeginArray] for a level representing a JSON array.
//
// It also reports the length of that JSON object or array.
// It also reports the length of that JSON object or array decoded so far.
// Each name and value in a JSON object is counted separately,
// so the effective number of members would be half the length.
// so the effective number of members is half the length.
// A complete JSON object must have an even length.
func (d *Decoder) StackIndex(i int) (Kind, int64) {
// NOTE: Keep in sync with Encoder.StackIndex.
+6 -6
View File
@@ -34,7 +34,7 @@
//
// - a JSON literal, string, or number
// - a JSON object (e.g., `{"name":"value"}`)
// - a JSON array (e.g., `[1,2,3,]`)
// - a JSON array (e.g., `[1,2,3]`)
//
// A JSON value is represented by the [Value] type in Go and is a []byte
// containing the raw textual representation of the value. There is some overlap
@@ -43,18 +43,18 @@
//
// The [Encoder] and [Decoder] types contain methods to read or write the next
// [Token] or [Value] in a sequence. They maintain a state machine to validate
// whether the sequence of JSON tokens and/or values produces a valid JSON.
// whether the sequence of JSON tokens and/or values produces valid JSON.
// [Options] may be passed to the [NewEncoder] or [NewDecoder] constructors
// to configure the syntactic behavior of encoding and decoding.
// to configure the behavior of encoding and decoding.
//
// # Terminology
//
// The terms "encode" and "decode" are used for syntactic functionality
// that is concerned with processing JSON based on its grammar, and
// the terms "marshal" and "unmarshal" are used for semantic functionality
// that determines the meaning of JSON values as Go values and vice-versa.
// This package (i.e., [jsontext]) deals with JSON at a syntactic layer,
// while [encoding/json/v2] deals with JSON at a semantic layer.
// that determines the meaning of JSON values as Go values and vice versa.
// This package deals with JSON syntax,
// while [encoding/json/v2] deals with JSON semantics.
// The goal is to provide a clear distinction between functionality that
// is purely concerned with encoding versus that of marshaling.
// For example, one can directly encode a stream of JSON tokens without
+58 -42
View File
@@ -16,7 +16,7 @@ import (
"github.com/go-json-experiment/json/internal/jsonwire"
)
// Encoder is a streaming encoder from raw JSON tokens and values.
// Encoder is a streaming encoder to raw JSON tokens and values.
// It is used to write a stream of top-level JSON values,
// each terminated with a newline character.
//
@@ -41,7 +41,7 @@ import (
// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"}
// e.WriteToken(EndObject) // }
//
// The above is one of many possible sequence of calls and
// The above is one of many possible sequences of calls and
// may not represent the most sensible method to call for any given token/value.
// For example, it is probably more common to call [Encoder.WriteToken] with a string
// for object names.
@@ -50,7 +50,7 @@ type Encoder struct {
}
// encoderState is the low-level state of Encoder.
// It has exported fields and method for use by the "json" package.
// It has exported fields and methods for use by the "json" package.
type encoderState struct {
state
encodeBuffer
@@ -96,7 +96,7 @@ func NewEncoder(w io.Writer, opts ...Options) *Encoder {
// Reset resets an encoder such that it is writing afresh to w and
// configured with the provided options. Reset must not be called on
// a Encoder passed to the [encoding/json/v2.MarshalerTo.MarshalJSONTo] method
// an Encoder passed to the [encoding/json/v2.MarshalerTo.MarshalJSONTo] method
// or the [encoding/json/v2.MarshalToFunc] function.
func (e *Encoder) Reset(w io.Writer, opts ...Options) {
switch {
@@ -124,21 +124,12 @@ func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) {
opts2 := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts
opts2.Join(opts...)
e.Struct = opts2
if e.Flags.Get(jsonflags.Multiline) {
if !e.Flags.Has(jsonflags.SpaceAfterColon) {
e.Flags.Set(jsonflags.SpaceAfterColon | 1)
}
if !e.Flags.Has(jsonflags.SpaceAfterComma) {
e.Flags.Set(jsonflags.SpaceAfterComma | 0)
}
if !e.Flags.Has(jsonflags.Indent) {
e.Flags.Set(jsonflags.Indent | 1)
e.Indent = "\t"
}
if e.Struct.Flags.Get(jsonflags.Multiline) {
e.Struct.InitializeMultiline()
}
}
// Options returns the options used to construct the decoder and
// Options returns the options used to construct the encoder and
// may additionally contain semantic options passed to a
// [encoding/json/v2.MarshalEncode] call.
//
@@ -150,6 +141,8 @@ func (e *Encoder) Options() Options {
return &e.s.Struct
}
func (e *encoderState) options() *jsonopts.Struct { return &e.Struct }
// NeedFlush determines whether to flush at this point.
func (e *encoderState) NeedFlush() bool {
// NOTE: This function is carefully written to be inlinable.
@@ -178,7 +171,7 @@ func (e *encoderState) Flush() error {
// Specialize bytes.Buffer for better performance.
if bb, ok := e.wr.(*bytes.Buffer); ok {
// If e.buf already aliases the internal buffer of bb,
// If e.Buf already aliases the internal buffer of bb,
// then the Write call simply increments the internal offset,
// otherwise Write operates as expected.
// See https://go.dev/issue/42986.
@@ -213,7 +206,7 @@ func (e *encoderState) Flush() error {
e.Buf = e.Buf[:0]
// Check whether to grow the buffer.
// Note that cap(e.buf) may already exceed maxBufferSize since
// Note that cap(e.Buf) may already exceed maxBufferSize since
// an append elsewhere already grew it to store a large token.
const maxBufferSize = 4 << 10
const growthSizeFactor = 2 // higher value is faster
@@ -229,7 +222,7 @@ func (e *encoderState) Flush() error {
return nil
}
func (d *encodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) }
func (e *encodeBuffer) offsetAt(pos int) int64 { return e.baseOffset + int64(pos) }
func (e *encodeBuffer) previousOffsetEnd() int64 { return e.baseOffset + int64(len(e.Buf)) }
func (e *encodeBuffer) unflushedBuffer() []byte { return e.Buf }
@@ -344,9 +337,10 @@ func (e *encoderState) UnwriteOnlyObjectMemberName() string {
// For example, it is an error to provide a number when the encoder
// is expecting an object name (which is always a string), or
// to provide an end object delimiter when the encoder is finishing an array.
// If the provided token is invalid, then it reports a [SyntacticError] and
// If the provided token is invalid, then WriteToken reports a [SyntacticError] and
// the internal state remains unchanged. The offset reported
// in [SyntacticError] will be relative to the [Encoder.OutputOffset].
// in [SyntacticError] will be the [Encoder.OutputOffset] plus any delimiter
// or whitespace characters that would have preceded the provided token.
func (e *Encoder) WriteToken(t Token) error {
return e.s.WriteToken(t)
}
@@ -405,6 +399,7 @@ func (e *encoderState) WriteToken(t Token) error {
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
e.Namespaces.push()
}
e.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
case '}':
b = append(b, '}')
if err = e.Tokens.popObject(); err != nil {
@@ -417,6 +412,7 @@ func (e *encoderState) WriteToken(t Token) error {
case '[':
b = append(b, '[')
err = e.Tokens.pushArray()
e.Flags.Clear(jsonflags.TagFlags) // tags only apply to current depth
case ']':
b = append(b, ']')
err = e.Tokens.popArray()
@@ -471,7 +467,7 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
if !isVerbatim {
var err error
b2 := append(e.availBuffer, b[pos+len(`"`):len(b)-len(`"`)]...)
b, err = jsonwire.AppendQuote(b[:pos], string(b2), &e.Flags)
b, err = jsonwire.AppendQuote(b[:pos], b2, &e.Flags)
e.availBuffer = b2[:0]
if err != nil {
return wrapSyntacticError(e, err, pos, +1)
@@ -482,7 +478,7 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
if e.Tokens.Last.NeedObjectName() {
if !e.Flags.Get(jsonflags.AllowDuplicateNames) {
if !e.Tokens.Last.isValidNamespace() {
return wrapSyntacticError(e, err, pos, +1)
return wrapSyntacticError(e, errInvalidNamespace, pos, +1)
}
if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) {
err = wrapWithObjectName(ErrDuplicateName, b[pos:])
@@ -522,9 +518,9 @@ func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) (
//
// The provided value kind must be consistent with the JSON grammar
// (see examples on [Encoder.WriteToken]). If the provided value is invalid,
// then it reports a [SyntacticError] and the internal state remains unchanged.
// The offset reported in [SyntacticError] will be relative to the
// [Encoder.OutputOffset] plus the offset into v of any encountered syntax error.
// then WriteValue reports a [SyntacticError] and the internal state remains unchanged.
// The offset reported in [SyntacticError] will be the [Encoder.OutputOffset]
// plus the offset into v of any encountered syntax error.
func (e *Encoder) WriteValue(v Value) error {
return e.s.WriteValue(v)
}
@@ -541,7 +537,7 @@ func (e *encoderState) WriteValue(v Value) error {
}
pos := len(b) // offset before the value
// Append the value the output.
// Append the value to the output.
var n int
n += jsonwire.ConsumeWhitespace(v[n:])
b, m, err := e.reformatValue(b, v[n:], e.Tokens.Depth())
@@ -715,7 +711,7 @@ func (e *encoderState) reformatValue(dst []byte, src Value, depth int) ([]byte,
}
// reformatObject parses a JSON object from the start of src and
// appends it to the end of src, reformatting whitespace and strings as needed.
// appends it to the end of dst, reformatting whitespace and strings as needed.
// It returns the extended dst buffer and the number of consumed input bytes.
func (e *encoderState) reformatObject(dst []byte, src Value, depth int) ([]byte, int, error) {
// Append object begin.
@@ -900,7 +896,7 @@ func (e *encoderState) reformatArray(dst []byte, src Value, depth int) ([]byte,
// OutputOffset returns the current output byte offset. It gives the location
// of the next byte immediately after the most recently written token or value.
// The number of bytes actually written to the underlying [io.Writer] may be less
// than this offset due to internal buffering effects.
// than this offset due to internal buffering.
func (e *Encoder) OutputOffset() int64 {
return e.s.previousOffsetEnd()
}
@@ -911,19 +907,21 @@ func (e *Encoder) OutputOffset() int64 {
//
// Example usage:
//
// b := d.AvailableBuffer()
// b := e.AvailableBuffer()
// b = append(b, '"')
// b = appendString(b, v) // append the string formatting of v
// b = append(b, '"')
// ... := d.WriteValue(b)
// ... := e.WriteValue(b)
//
// It is the user's responsibility to ensure that the value is valid JSON.
// Note that WriteValue expects a valid JSON value. Constructing a value in a
// raw []byte requires more care than using constructor functions like
// [String], which always return valid [Token]s or [Value]s.
func (e *Encoder) AvailableBuffer() []byte {
// NOTE: We don't return e.buf[len(e.buf):cap(e.buf)] since WriteValue would
// NOTE: We don't return e.Buf[len(e.Buf):cap(e.Buf)] since WriteValue would
// need to take special care to avoid mangling the data while reformatting.
// WriteValue can't easily identify whether the input Value aliases e.buf
// WriteValue can't easily identify whether the input Value aliases e.Buf
// without using unsafe.Pointer. Thus, we just return a different buffer.
// Should this ever alias e.buf, we need to consider how it operates with
// Should this ever alias e.Buf, we need to consider how it operates with
// the specialized performance optimization for bytes.Buffer.
n := 1 << bits.Len(uint(e.s.maxValue|63)) // fast approximation for max length
if cap(e.s.availBuffer) < n {
@@ -934,9 +932,27 @@ func (e *Encoder) AvailableBuffer() []byte {
// StackDepth returns the depth of the state machine for written JSON data.
// Each level on the stack represents a nested JSON object or array.
// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered
// It is incremented whenever a [BeginObject] or [BeginArray] token is encountered
// and decremented whenever an [EndObject] or [EndArray] token is encountered.
// The depth is zero-indexed, where zero represents the top-level JSON value.
//
// StackDepth returns 0 when not inside any object or array.
// In particular, it returns 0 before any tokens have been written,
// after any top-level value has been written, and between values
// when encoding a stream of top-level values (e.g., NDJSON).
// StackDepth returns 1 inside a top-level object or array,
// 2 inside a nested object or array, and so on.
//
// For example, consider encoding the following JSON:
//
// {"a": [1, 2], "b": {"c": 3}}
//
// While encoding, StackDepth would report the following:
//
// - At the start, StackDepth reports 0.
// - After encoding the outer '{', StackDepth reports 1.
// - After encoding the inner '[', StackDepth reports 2.
// - After encoding the inner ']', StackDepth reports 1.
// - After encoding the outer '}', StackDepth reports 0.
func (e *Encoder) StackDepth() int {
// NOTE: Keep in sync with Decoder.StackDepth.
return e.s.Tokens.Depth() - 1
@@ -946,13 +962,13 @@ func (e *Encoder) StackDepth() int {
// It must be a number between 0 and [Encoder.StackDepth], inclusive.
// For each level, it reports the kind:
//
// - 0 for a level of zero,
// - '{' for a level representing a JSON object, and
// - '[' for a level representing a JSON array.
// - [KindInvalid] for a level of zero,
// - [KindBeginObject] for a level representing a JSON object, and
// - [KindBeginArray] for a level representing a JSON array.
//
// It also reports the length of that JSON object or array.
// It also reports the length of that JSON object or array encoded so far.
// Each name and value in a JSON object is counted separately,
// so the effective number of members would be half the length.
// so the effective number of members is half the length.
// A complete JSON object must have an even length.
func (e *Encoder) StackIndex(i int) (Kind, int64) {
// NOTE: Keep in sync with Decoder.StackIndex.
+34 -3
View File
@@ -11,6 +11,8 @@ import (
"io"
"strconv"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/internal/jsonwire"
)
@@ -28,7 +30,20 @@ func (e *ioError) Unwrap() error {
return e.err
}
// SyntacticError is a description of a syntactic error that occurred when
type numError struct {
accessor string // either "Int", "Uint", or "Float"
value string // e.g., "1e1000"
err error // either [strconv.ErrSyntax] or [strconv.ErrRange]
}
func (e *numError) Error() string {
return "jsontext.Token(" + e.value + ")." + e.accessor + " error: " + e.err.Error()
}
func (e *numError) Unwrap() error {
return e.err
}
// SyntacticError is a description of an error that occurred when
// encoding or decoding JSON according to the grammar.
//
// The contents of this error as produced by this package may change over time.
@@ -36,7 +51,7 @@ type SyntacticError struct {
requireKeyedLiterals
nonComparable
// ByteOffset indicates that an error occurred after this byte offset.
// ByteOffset indicates that an error occurred at or after this byte offset.
ByteOffset int64
// JSONPointer indicates that an error occurred within this JSON value
// as indicated using the JSON Pointer notation (see RFC 6901).
@@ -53,10 +68,11 @@ type SyntacticError struct {
// It takes a relative offset pos that can be resolved into
// an absolute offset using state.offsetAt.
//
// It takes a where that specify how the JSON pointer is derived.
// It takes a where that specifies how the JSON pointer is derived.
// If the underlying error is a [pointerSuffixError],
// then the suffix is appended to the derived pointer.
func wrapSyntacticError(state interface {
options() *jsonopts.Struct
offsetAt(pos int) int64
AppendStackPointer(b []byte, where int) []byte
}, err error, pos, where int) error {
@@ -83,6 +99,21 @@ func wrapSyntacticError(state interface {
}
err = jsonwire.NewInvalidCharacterError(d.buf[pos:], where)
}
if state.options().Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) && err != io.ErrUnexpectedEOF {
// The v1 error offset more consistently reported
// the number of bytes read that lead to the error.
// This implies that the offset is after the invalid text.
if werr, ok := err.(*jsonwire.InvalidTextError); ok && werr != nil {
offset += int64(len(werr.What))
} else {
// This implies a structural error (e.g., parsing a JSON number
// when the grammar is expecting a JSON string for the object name).
// In such a case, simply increment by one, since the first byte
// is usually sufficient to indicate that the incoming token
// cannot possibly be correct.
offset++
}
}
return &SyntacticError{ByteOffset: offset, JSONPointer: Pointer(ptr), Err: err}
}
+14
View File
@@ -12,6 +12,20 @@ import (
"github.com/go-json-experiment/json/internal"
)
// TODO(https://go.dev/issue/73435): Remove the Internal symbol.
//
// The Go language lacks a 3rd category of visibility where
// certain symbols can only be referenced from within the same module.
// One solution to this is to put the entirety of a package with
// both public and module-only symbols exposed as an internal package.
// A separate, public package can re-export all of the public symbols
// via type aliases and thus ensuring module-only symbols cannot be
// referenced by the end-user. While this works, it unfortunately
// leads to a poor user experience since the Go pkgsite is unable to
// forward the documentation for symbols like methods and fields.
// We need to improve the pkgsite experience before we can delete
// the Internal symbol.
// Internal is for internal use only.
// This is exempt from the Go compatibility agreement.
var Internal exporter
+8 -8
View File
@@ -17,7 +17,7 @@ import (
// Options configures [NewEncoder], [Encoder.Reset], [NewDecoder],
// and [Decoder.Reset] with specific features.
// Each function takes in a variadic list of options, where properties
// set in latter options override the value of previously set properties.
// set in later options override the value of previously set properties.
//
// There is a single Options type, which is used with both encoding and decoding.
// Some options affect both operations, while others only affect one operation:
@@ -147,7 +147,7 @@ func CanonicalizeRawInts(v bool) Options {
// It is safe to canonicalize a serialized single precision number and
// parse it back as a single precision number and expect the same value.
// If a number exceeds ±1.7976931348623157e+308, which is the maximum
// finite number, then it saturated at that value and formatted as such.
// finite number, then it is saturated at that value and formatted as such.
//
// This only affects encoding and is ignored when decoding.
func CanonicalizeRawFloats(v bool) Options {
@@ -205,7 +205,7 @@ func SpaceAfterComma(v bool) Options {
// If [SpaceAfterComma] is not specified, then the default is false.
// If [WithIndent] is not specified, then the default is "\t".
//
// If set to false, then the output is a single-line,
// If set to false, then the output is a single line,
// where the only whitespace emitted is determined by the current
// values of [SpaceAfterColon] and [SpaceAfterComma].
//
@@ -222,9 +222,9 @@ func Multiline(v bool) Options {
// where each element in a JSON object or array begins on a new, indented line
// beginning with the indent prefix (see [WithIndentPrefix])
// followed by one or more copies of indent according to the nesting depth.
// The indent must only be composed of space or tab characters.
// The indent must be composed of only space and tab characters.
//
// If the intent to emit indented output without a preference for
// If the intent is to emit indented output without a preference for
// the particular indent string, then use [Multiline] instead.
//
// This only affects encoding and is ignored when decoding.
@@ -249,7 +249,7 @@ func WithIndent(indent string) Options {
// Otherwise, allocate for this unique value.
if s := strings.Trim(indent, " \t"); len(s) > 0 {
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent")
panic("json: invalid character " + jsonwire.QuoteRune([]byte(s)) + " in indent")
}
return jsonopts.Indent(indent)
}
@@ -258,13 +258,13 @@ func WithIndent(indent string) Options {
// where each element in a JSON object or array begins on a new, indented line
// beginning with the indent prefix followed by one or more copies of indent
// (see [WithIndent]) according to the nesting depth.
// The prefix must only be composed of space or tab characters.
// The prefix must be composed of only space and tab characters.
//
// This only affects encoding and is ignored when decoding.
// Use of this option implies [Multiline] being set to true.
func WithIndentPrefix(prefix string) Options {
if s := strings.Trim(prefix, " \t"); len(s) > 0 {
panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent prefix")
panic("json: invalid character " + jsonwire.QuoteRune([]byte(s)) + " in indent prefix")
}
return jsonopts.IndentPrefix(prefix)
}
+12
View File
@@ -54,6 +54,10 @@ func getBufferedEncoder(opts ...Options) *Encoder {
return e
}
func putBufferedEncoder(e *Encoder) {
if cap(e.s.availBuffer) > 64<<10 {
e.s.availBuffer = nil // avoid pinning arbitrarily large amounts of memory
}
// Recycle large buffers only if sufficiently utilized.
// If a buffer is under-utilized enough times sequentially,
// then it is discarded, ensuring that a single large buffer
@@ -95,9 +99,14 @@ func getStreamingEncoder(w io.Writer, opts ...Options) *Encoder {
}
}
func putStreamingEncoder(e *Encoder) {
if cap(e.s.availBuffer) > 64<<10 {
e.s.availBuffer = nil // avoid pinning arbitrarily large amounts of memory
}
if _, ok := e.s.wr.(*bytes.Buffer); ok {
e.s.wr, e.s.Buf = nil, nil // avoid pinning the provided bytes.Buffer
bytesBufferEncoderPool.Put(e)
} else {
e.s.wr = nil // avoid pinning the provided io.Writer
if cap(e.s.Buf) > 64<<10 {
e.s.Buf = nil // avoid pinning arbitrarily large amounts of memory
}
@@ -126,6 +135,7 @@ func getBufferedDecoder(b []byte, opts ...Options) *Decoder {
return d
}
func putBufferedDecoder(d *Decoder) {
d.s.buf = nil // avoid pinning the provided buffer
bufferedDecoderPool.Put(d)
}
@@ -142,8 +152,10 @@ func getStreamingDecoder(r io.Reader, opts ...Options) *Decoder {
}
func putStreamingDecoder(d *Decoder) {
if _, ok := d.s.rd.(*bytes.Buffer); ok {
d.s.rd, d.s.buf = nil, nil // avoid pinning the provided bytes.Buffer
bytesBufferDecoderPool.Put(d)
} else {
d.s.rd = nil // avoid pinning the provided io.Reader
if cap(d.s.buf) > 64<<10 {
d.s.buf = nil // avoid pinning arbitrarily large amounts of memory
}
+2 -2
View File
@@ -18,7 +18,7 @@ import (
// and an error is returned at the end indicating the presence of invalid UTF-8.
// The dst must not overlap with the src.
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
dst, err := jsonwire.AppendQuote(dst, src, &jsonflags.Flags{})
dst, err := jsonwire.AppendQuote(dst, []byte(src), &jsonflags.Flags{})
if err != nil {
err = &SyntacticError{Err: err}
}
@@ -33,7 +33,7 @@ func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)
// Any trailing bytes after the JSON string literal results in an error.
// The dst must not overlap with the src.
func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) {
dst, err := jsonwire.AppendUnquote(dst, src)
dst, err := jsonwire.AppendUnquote(dst, []byte(src))
if err != nil {
err = &SyntacticError{Err: err}
}
+16 -16
View File
@@ -24,8 +24,8 @@ import (
// The name of a duplicate JSON object member can be extracted as:
//
// err := ...
// var serr jsontext.SyntacticError
// if errors.As(err, &serr) && serr.Err == jsontext.ErrDuplicateName {
// serr, ok := errors.AsType[*jsontext.SyntacticError](err)
// if ok && serr.Err == jsontext.ErrDuplicateName {
// ptr := serr.JSONPointer // JSON pointer to duplicate name
// name := ptr.LastToken() // duplicate name itself
// ...
@@ -86,12 +86,12 @@ func (s *state) reset() {
// either a JSON object name or an index to a JSON array element
// encoded as a base-10 integer value.
// It is impossible to distinguish between an array index and an object name
// (that happens to be an base-10 encoded integer) without also knowing
// (that happens to be a base-10 encoded integer) without also knowing
// the structure of the top-level JSON value that the pointer refers to.
//
// There is exactly one representation of a pointer to a particular value,
// so comparability of Pointer values is equivalent to checking whether
// they both point to the exact same value.
// they both point to the same value.
type Pointer string
// IsValid reports whether p is a valid JSON Pointer according to RFC 6901.
@@ -117,13 +117,13 @@ func (p Pointer) Contains(pc Pointer) bool {
}
// Parent strips off the last token and returns the remaining pointer.
// The parent of an empty p is an empty string.
// The parent of an empty Pointer is the empty string.
func (p Pointer) Parent() Pointer {
return p[:max(strings.LastIndexByte(string(p), '/'), 0)]
}
// LastToken returns the last token in the pointer.
// The last token of an empty p is an empty string.
// The last token of an empty Pointer is the empty string.
func (p Pointer) LastToken() string {
last := p[max(strings.LastIndexByte(string(p), '/'), 0):]
return unescapePointerToken(strings.TrimPrefix(string(last), "/"))
@@ -131,14 +131,14 @@ func (p Pointer) LastToken() string {
// AppendToken appends a token to the end of p and returns the full pointer.
func (p Pointer) AppendToken(tok string) Pointer {
return Pointer(appendEscapePointerName([]byte(p+"/"), tok))
return Pointer(appendEscapePointerName([]byte(p+"/"), []byte(tok)))
}
// TODO: Add Pointer.AppendTokens,
// but should this take in a ...string or an iter.Seq[string]?
// Tokens returns an iterator over the reference tokens in the JSON pointer,
// starting from the first token until the last token (unless stopped early).
// from first to last.
func (p Pointer) Tokens() iter.Seq[string] {
return func(yield func(string) bool) {
for len(p) > 0 {
@@ -202,7 +202,7 @@ func (s state) appendStackPointer(b []byte, where int) []byte {
return b
}
func appendEscapePointerName[Bytes ~[]byte | ~string](b []byte, name Bytes) []byte {
func appendEscapePointerName(b, name []byte) []byte {
for _, r := range string(name) {
// Per RFC 6901, section 3, escape '~' and '/' characters.
switch r {
@@ -439,7 +439,7 @@ const (
stateTypeObject stateEntry = 0x8000_0000_0000_0000
stateTypeArray stateEntry = 0x0000_0000_0000_0000
// The name check mask (2 bit) records whether to update
// The name check mask (2 bits) records whether to update
// the namespaces for the current JSON object and
// whether the namespace is valid.
stateNamespaceMask stateEntry = 0x6000_0000_0000_0000
@@ -475,7 +475,7 @@ func (e stateEntry) NeedObjectName() bool {
return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountEven
}
// needImplicitColon reports whether an colon should occur next,
// needImplicitColon reports whether a colon should occur next,
// which always occurs after JSON object names.
func (e stateEntry) needImplicitColon() bool {
return e.needObjectValue()
@@ -487,7 +487,7 @@ func (e stateEntry) needObjectValue() bool {
return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountOdd
}
// needImplicitComma reports whether an comma should occur next,
// needImplicitComma reports whether a comma should occur next,
// which always occurs after a value in a JSON object or array
// before the next value (or name).
func (e stateEntry) needImplicitComma(next Kind) bool {
@@ -502,7 +502,7 @@ func (e *stateEntry) Increment() {
}
// decrement decrements the number of elements for the current object or array.
// It is the callers responsibility to ensure that e.length > 0.
// It is the caller's responsibility to ensure that e.length > 0.
func (e *stateEntry) decrement() {
(*e)--
}
@@ -575,7 +575,7 @@ func (ns *objectNameStack) getUnquoted(i int) []byte {
if i == 0 {
return ns.unquotedNames[:ns.offsets[0]]
} else {
return ns.unquotedNames[ns.offsets[i-1]:ns.offsets[i-0]]
return ns.unquotedNames[ns.offsets[i-1]:ns.offsets[i]]
}
}
@@ -706,7 +706,7 @@ func (nss *objectNamespaceStack) pop() {
}
// objectNamespace is the namespace for a JSON object.
// In contrast to objectNameStack, this needs to remember a all names
// In contrast to objectNameStack, this needs to remember all names
// per JSON object.
//
// The zero value is an empty namespace ready for use.
@@ -747,7 +747,7 @@ func (ns *objectNamespace) getUnquoted(i int) []byte {
if i == 0 {
return ns.allUnquotedNames[:ns.endOffsets[0]]
} else {
return ns.allUnquotedNames[ns.endOffsets[i-1]:ns.endOffsets[i-0]]
return ns.allUnquotedNames[ns.endOffsets[i-1]:ns.endOffsets[i]]
}
}
+288 -118
View File
@@ -39,6 +39,20 @@ var errInvalidToken = errors.New("invalid jsontext.Token")
// A Token cannot represent entire array or object values, while a [Value] can.
// There is no Token to represent commas and colons since
// these structural tokens can be inferred from the surrounding context.
//
// A Token stores data in one of two forms:
//
// - As raw JSON text: backed by the internal buffer of the [Decoder]
// and only ever produced by [Decoder.ReadToken].
// Such a token is only valid until the next call to any method on that
// [Decoder] (e.g., [Decoder.PeekKind], [Decoder.ReadToken],
// [Decoder.ReadValue], or [Decoder.SkipValue]).
// Call [Token.Clone] to copy the raw text into an independent allocation
// that persists beyond subsequent [Decoder] calls.
//
// - As a typed Go value: a self-contained representation produced by
// the constructor functions (e.g., [String], [Int], [Uint], [Float]).
// Such tokens are valid indefinitely and do not need to be cloned.
type Token struct {
nonComparable
@@ -48,21 +62,22 @@ type Token struct {
// The Encoder accepts Tokens in either the "raw" or "exact" form.
//
// The following chart shows the possible values for each Token type:
// ╔═════════════════╦════════════╤════════════╤════════════╗
// ║ Token type ║ raw field │ str field │ num field ║
// ╠═════════════════╬════════════╪════════════╪════════════╣
// ║ null (raw) ║ "null" │ "" │ 0 ║
// ║ false (raw) ║ "false" │ "" │ 0 ║
// ║ true (raw) ║ "true" │ "" │ 0 ║
// ║ string (raw) ║ non-empty │ "" │ offset ║
// ║ string (string) ║ nil │ non-empty │ 0 ║
// ║ number (raw) ║ non-empty │ "" │ offset ║
// ║ number (float) ║ nil │ "f" │ non-zero ║
// ║ number (int64) ║ nil │ "i" │ non-zero ║
// ║ number (uint64) ║ nil │ "u" │ non-zero ║
// ║ object (delim) ║ "{" or "}" │ "" │ 0 ║
// ║ array (delim) ║ "[" or "]" │ "" │ 0 ║
// ╚═════════════════╩════════════╧════════════╧════════════╝
// ╔══════════════════╦════════════╤════════════╤════════════╗
// ║ Token type ║ raw field │ str field │ num field ║
// ╠══════════════════╬════════════╪════════════╪════════════╣
// ║ null (raw) ║ "null" │ "" │ 0 ║
// ║ false (raw) ║ "false" │ "" │ 0 ║
// ║ true (raw) ║ "true" │ "" │ 0 ║
// ║ string (raw) ║ non-empty │ "" │ offset ║
// ║ string (string) ║ nil │ non-empty │ 0 ║
// ║ number (raw) ║ non-empty │ "" │ offset ║
// ║ number (float32) ║ nil │ "F" │ non-zero ║
// ║ number (float64) ║ nil │ "f" │ non-zero ║
// ║ number (int64) ║ nil │ "i" │ non-zero ║
// ║ number (uint64) ║ nil │ "u" │ non-zero ║
// ║ object (delim) ║ "{" or "}" │ "" │ 0 ║
// ║ array (delim) ║ "[" or "]" │ "" │ 0 ║
// ╚══════════════════╩════════════╧════════════╧════════════╝
//
// Notes:
// - For tokens stored in "raw" form, the num field contains the
@@ -80,11 +95,12 @@ type Token struct {
raw *decodeBuffer
// str is the unescaped JSON string if num is zero.
// Otherwise, it is "f", "i", or "u" if num should be interpreted
// as a float64, int64, or uint64, respectively.
// Otherwise, it is "F", "f", "i", or "u" if num should be interpreted
// as a float32, float64, int64, or uint64, respectively.
str string
// num is a float64, int64, or uint64 stored as a uint64 value.
// num is a float32, float64, int64, or uint64 stored as a uint64 value.
// For floating-point values, it stores the raw IEEE-754 bit-pattern.
// It is non-zero for any JSON number in the "exact" form.
num uint64
}
@@ -131,7 +147,29 @@ func String(s string) Token {
return Token{str: s}
}
// Float constructs a Token representing a JSON number.
// Float32 constructs a Token representing a JSON number as
// a 32-bit floating-point number formatted according to
// ECMA-262, 10th edition, section 7.1.12.1,
// with the exception that -0 is still formatted as -0.
// The values NaN, +Inf, and -Inf will be represented
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
//
// Note that most JSON libraries and standards assume that JSON numbers
// are 64-bit floating-point numbers. Use of 32-bit precision should
// only be used if the corresponding decoder knows that
// this JSON number token is expected to only have 32-bit precision.
// For all other situations, prefer using the [Float] constructor instead.
func Float32(n float32) Token {
if n != 0 && !math.IsNaN(float64(n)) && !math.IsInf(float64(n), 0) {
return Token{str: "F", num: uint64(math.Float32bits(n))}
}
return Float(float64(n)) // handles ±0, NaN, and ±Inf
}
// Float constructs a Token representing a JSON number as
// a 64-bit floating-point number formatted according to
// ECMA-262, 10th edition, section 7.1.12.1 and RFC 8785, section 3.2.2.3.
// with the exception that -0 is still formatted as -0.
// The values NaN, +Inf, and -Inf will be represented
// as a JSON string with the values "NaN", "Infinity", and "-Infinity".
func Float(n float64) Token {
@@ -164,8 +202,10 @@ func Uint(n uint64) Token {
return Token{str: "u", num: uint64(n)}
}
// Clone makes a copy of the Token such that its value remains valid
// even after a subsequent [Decoder.Read] call.
// Clone returns a copy of the token with a value that is not backed by the
// [Decoder] buffer and therefore remains valid past subsequent [Decoder] calls.
// It has no effect on tokens produced by constructor functions,
// since those are already self-contained.
func (t Token) Clone() Token {
// TODO: Allow caller to avoid any allocations?
if raw := t.raw; raw != nil {
@@ -189,6 +229,7 @@ func (t Token) Clone() Token {
}
}
// This only ever needs to clone strings and numbers.
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
@@ -226,7 +267,7 @@ func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error)
}
} else if len(t.str) != 0 && t.num == 0 {
// Handle exact string value.
return jsonwire.AppendQuote(dst, t.str, flags)
return jsonwire.AppendQuote(dst, []byte(t.str), flags)
}
panic("invalid JSON token kind: " + t.Kind().String())
@@ -265,8 +306,10 @@ func (t Token) string() (string, []byte) {
// Handle tokens that are not JSON strings for fmt.Stringer.
if t.num > 0 {
switch t.str[0] {
case 'F':
return string(jsonwire.AppendFloat(nil, float64(math.Float32frombits(uint32(t.num))), 32)), nil
case 'f':
return string(jsonwire.AppendFloat(nil, math.Float64frombits(t.num), 64)), nil
return string(jsonwire.AppendFloat(nil, float64(math.Float64frombits(uint64(t.num))), 64)), nil
case 'i':
return strconv.FormatInt(int64(t.num), 10), nil
case 'u':
@@ -289,8 +332,10 @@ func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error)
} else if t.num != 0 {
// Handle exact number value.
switch t.str[0] {
case 'F':
return jsonwire.AppendFloat(dst, float64(math.Float32frombits(uint32(t.num))), 32), nil
case 'f':
return jsonwire.AppendFloat(dst, math.Float64frombits(t.num), 64), nil
return jsonwire.AppendFloat(dst, float64(math.Float64frombits(uint64(t.num))), 64), nil
case 'i':
return strconv.AppendInt(dst, int64(t.num), 10), nil
case 'u':
@@ -301,30 +346,77 @@ func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error)
panic("invalid JSON token kind: " + t.Kind().String())
}
// Float returns the floating-point value for a JSON number.
// Float32 returns the floating-point value for a JSON number
// parsed according to 32 bits of precision.
//
// If the JSON number is outside the representable range of a float32,
// it returns +Inf or -Inf along with an error
// that matches [strconv.ErrRange] according to [errors.Is].
//
// It returns a NaN, +Inf, or -Inf value for any JSON string
// with the values "NaN", "Infinity", or "-Infinity".
// It panics for all other cases.
func (t Token) Float() float64 {
//
// It panics if the token kind is not a JSON number
// or a JSON string with the aforementioned values.
//
// Note that most JSON libraries and standards assume that JSON numbers
// are 64-bit floating-point numbers.
// This method should only be used if the caller knows
// from other context that this token is a JSON number
// formatted only to 32 bits of precision (such as being encoded
// using the [Float32] constructor). For all other situations,
// prefer using the [Token.Float] accessor instead.
func (t Token) Float32() (float32, error) {
f, err := t.float(32)
return float32(f), err
}
// Float returns the floating-point value for a JSON number
// parsed according to 64 bits of precision.
//
// If the JSON number is outside the representable range of a float64,
// it returns +Inf or -Inf along with an error
// that matches [strconv.ErrRange] according to [errors.Is].
//
// It returns a NaN, +Inf, or -Inf value for any JSON string
// with the values "NaN", "Infinity", or "-Infinity".
//
// It panics if the token kind is not a JSON number
// or a JSON string with the aforementioned values.
func (t Token) Float() (float64, error) {
f, err := t.float(64)
return float64(f), err
}
func (t Token) float(bits int) (float64, error) {
if raw := t.raw; raw != nil {
// Handle raw number value.
// Handle raw JSON number value.
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
buf := raw.previousBuffer()
if Kind(buf[0]).normalize() == '0' {
fv, _ := jsonwire.ParseFloat(buf, 64)
return fv
fv, err := strconv.ParseFloat(string(buf), bits)
if err != nil {
err = &numError{accessor: "Float", value: t.String(), err: errors.Unwrap(err)} // only ever ErrRange
}
return fv, err
}
} else if t.num != 0 {
// Handle exact number value.
// Handle typed Go number value.
switch t.str[0] {
case 'F':
return float64(math.Float32frombits(uint32(t.num))), nil
case 'f':
return math.Float64frombits(t.num)
f64 := float64(math.Float64frombits(uint64(t.num)))
if bits == 32 && !math.IsInf(f64, 0) && math.IsInf(float64(float32(f64)), 0) {
return f64, &numError{accessor: "Float", value: t.String(), err: strconv.ErrRange}
}
return f64, nil
case 'i':
return float64(int64(t.num))
return float64(int64(t.num)), nil // NOTE: This may lead to loss of precision.
case 'u':
return float64(uint64(t.num))
return float64(uint64(t.num)), nil // NOTE: This may lead to loss of precision.
}
}
@@ -332,128 +424,181 @@ func (t Token) Float() float64 {
if t.Kind() == '"' {
switch t.String() {
case "NaN":
return math.NaN()
return math.NaN(), nil
case "Infinity":
return math.Inf(+1)
return math.Inf(+1), nil
case "-Infinity":
return math.Inf(-1)
return math.Inf(-1), nil
}
// TODO: Should this be an error instead of a panic?
// We can safely switch from a panic to an error in the future.
}
panic("invalid JSON token kind: " + t.Kind().String())
}
// Int returns the signed integer value for a JSON number.
//
// It reports an error that matches [strconv.ErrSyntax] according to [errors.Is]
// if the JSON number does not match the restricted grammar of just a signed integer.
// It reports an error that matches [strconv.ErrRange] according to [errors.Is]
// if the JSON number is a signed integer, but outside the range of an int64.
// Even if an error is reported, a reasonable value is still returned.
// The fractional component of any number is ignored (truncation toward zero).
// Any number beyond the representation of an int64 will be saturated
// to the closest representable value.
//
// It panics if the token kind is not a JSON number.
func (t Token) Int() int64 {
func (t Token) Int() (int64, error) {
if raw := t.raw; raw != nil {
// Handle raw integer value.
// Handle raw JSON number value.
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
neg := false
buf := raw.previousBuffer()
if len(buf) > 0 && buf[0] == '-' {
neg, buf = true, buf[1:]
}
if numAbs, ok := jsonwire.ParseUint(buf); ok {
if neg {
if numAbs > -minInt64 {
return minInt64
}
return -1 * int64(numAbs)
} else {
if numAbs > +maxInt64 {
return maxInt64
}
return +1 * int64(numAbs)
// Prospectively parse a negative integer.
switch abs, ok := jsonwire.ParseUint(buf[len("-"):]); {
case abs > -minInt64:
return minInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
case ok:
return -1 * int64(abs), nil
}
} else {
// Prospectively parse a non-negative integer.
switch abs, ok := jsonwire.ParseUint(buf); {
case abs > +maxInt64:
return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
case ok:
return +1 * int64(abs), nil
}
}
// This is not a signed integer, which implies ErrSyntax.
if Kind(buf[0]).normalize() == '0' {
f64, _ := strconv.ParseFloat(string(buf), 64)
return f64toi64(f64), &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
}
} else if t.num != 0 {
// Handle exact integer value.
// Handle typed Go number value.
switch t.str[0] {
case 'i':
return int64(t.num)
return int64(t.num), nil
case 'u':
if t.num > maxInt64 {
return maxInt64
return maxInt64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
}
return int64(t.num), nil
case 'f', 'F':
f64 := float64(math.Float64frombits(uint64(t.num)))
if t.str[0] == 'F' {
f64 = float64(math.Float32frombits(uint32(t.num)))
}
switch i64 := f64toi64(f64); {
case math.IsNaN(f64), math.Trunc(f64) != f64:
return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrSyntax}
case (i64 == minInt64 && f64 < minInt64) || (i64 == maxInt64 && f64 > maxInt64):
return i64, &numError{accessor: "Int", value: t.String(), err: strconv.ErrRange}
default:
return i64, nil
}
return int64(t.num)
}
}
// Handle JSON number that is a floating-point value.
if t.Kind() == '0' {
switch fv := t.Float(); {
case fv >= maxInt64:
return maxInt64
case fv <= minInt64:
return minInt64
default:
return int64(fv) // truncation toward zero
}
}
panic("invalid JSON token kind: " + t.Kind().String())
}
func f64toi64(f64 float64) int64 {
switch {
case math.IsNaN(f64):
return 0
case f64 >= maxInt64+1:
return maxInt64
case f64 < minInt64:
return minInt64
default:
return int64(f64) // NOTE: This may lead to loss of precision.
}
}
// Uint returns the unsigned integer value for a JSON number.
//
// It reports an error that matches [strconv.ErrSyntax] if the JSON number
// does not match the restricted grammar of just an unsigned integer.
// It reports an error that matches [strconv.ErrRange] if the JSON number
// is an unsigned integer, but outside the representable range of a uint64.
// Even if an error is reported, a reasonable value is still returned.
// The fractional component of any number is ignored (truncation toward zero).
// Any number beyond the representation of an uint64 will be saturated
// Any number beyond the representation of a uint64 will be saturated
// to the closest representable value.
//
// It panics if the token kind is not a JSON number.
func (t Token) Uint() uint64 {
func (t Token) Uint() (uint64, error) {
// NOTE: This accessor returns 0 for any negative JSON number,
// which might be surprising, but is at least consistent with the behavior
// of saturating out-of-bounds numbers to the closest representable number.
// We report ErrSyntax instead of ErrRange since the grammar for
// an unsigned integer does not permit a negative sign.
if raw := t.raw; raw != nil {
// Handle raw integer value.
// Handle raw JSON number value.
if uint64(raw.previousOffsetStart()) != t.num {
panic(invalidTokenPanic)
}
neg := false
buf := raw.previousBuffer()
if len(buf) > 0 && buf[0] == '-' {
neg, buf = true, buf[1:]
// Prospectively parse an unsigned integer.
switch abs, ok := jsonwire.ParseUint(buf); {
case ok:
return abs, nil
case abs == maxUint64: // implies overflows
return maxUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
}
if num, ok := jsonwire.ParseUint(buf); ok {
if neg {
return minUint64
}
return num
// This is not an unsigned integer, which implies ErrSyntax.
if Kind(buf[0]).normalize() == '0' {
f64, _ := strconv.ParseFloat(string(buf), 64)
return f64tou64(f64), &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
}
} else if t.num != 0 {
// Handle exact integer value.
// Handle typed Go number value.
switch t.str[0] {
case 'u':
return t.num
return t.num, nil
case 'i':
if int64(t.num) < minUint64 {
return minUint64
return minUint64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
}
return uint64(int64(t.num)), nil
case 'f', 'F':
f64 := float64(math.Float64frombits(uint64(t.num)))
if t.str[0] == 'F' {
f64 = float64(math.Float32frombits(uint32(t.num)))
}
switch u64 := f64tou64(f64); {
case math.IsNaN(f64), math.Trunc(f64) != f64, math.Signbit(f64):
return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrSyntax}
case (u64 == minUint64 && f64 < minUint64) || (u64 == maxUint64 && f64 > maxUint64):
return u64, &numError{accessor: "Uint", value: t.String(), err: strconv.ErrRange}
default:
return u64, nil
}
return uint64(int64(t.num))
}
}
// Handle JSON number that is a floating-point value.
if t.Kind() == '0' {
switch fv := t.Float(); {
case fv >= maxUint64:
return maxUint64
case fv <= minUint64:
return minUint64
default:
return uint64(fv) // truncation toward zero
}
}
panic("invalid JSON token kind: " + t.Kind().String())
}
func f64tou64(f64 float64) uint64 {
switch {
case math.IsNaN(f64):
return 0
case f64 >= maxUint64+1:
return maxUint64
case f64 < minUint64:
return minUint64
default:
return uint64(f64) // NOTE: This may lead to loss of precision.
}
}
// Kind returns the token kind.
func (t Token) Kind() Kind {
switch {
@@ -472,29 +617,33 @@ func (t Token) Kind() Kind {
}
}
// Kind represents each possible JSON token kind with a single byte,
// which is conveniently the first byte of that kind's grammar
// with the restriction that numbers always be represented with '0':
// A Kind represents the kind of a JSON token.
//
// - 'n': null
// - 'f': false
// - 't': true
// - '"': string
// - '0': number
// - '{': object begin
// - '}': object end
// - '[': array begin
// - ']': array end
//
// An invalid kind is usually represented using 0,
// but may be non-zero due to invalid JSON data.
// A Kind is a single byte, which is conveniently the first byte of that
// kind's symbol in the grammar (except for numbers, which are always represented
// with '0').
type Kind byte
const (
KindInvalid Kind = 0 // invalid kind
KindNull Kind = 'n' // null
KindFalse Kind = 'f' // false
KindTrue Kind = 't' // true
KindString Kind = '"' // string
KindNumber Kind = '0' // number
KindBeginObject Kind = '{' // begin object
KindEndObject Kind = '}' // end object
KindBeginArray Kind = '[' // begin array
KindEndArray Kind = ']' // end array
)
const invalidKind Kind = 0
// String prints the kind in a humanly readable fashion.
// String returns a string representation of k.
func (k Kind) String() string {
switch k {
case 0:
return "invalid"
case 'n':
return "null"
case 'f':
@@ -514,14 +663,35 @@ func (k Kind) String() string {
case ']':
return "]"
default:
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune(string(k)) + ">"
return "<invalid jsontext.Kind: " + jsonwire.QuoteRune([]byte{byte(k)}) + ">"
}
}
// normalize coalesces all possible starting characters of a number as just '0'.
var normKind = [256]Kind{
'n': 'n',
'f': 'f',
't': 't',
'"': '"',
'{': '{',
'}': '}',
'[': '[',
']': ']',
'-': '0',
'0': '0',
'1': '0',
'2': '0',
'3': '0',
'4': '0',
'5': '0',
'6': '0',
'7': '0',
'8': '0',
'9': '0',
}
// normalize coalesces all possible starting characters of a number as just '0',
// and converts all invalid kinds to 0.
func (k Kind) normalize() Kind {
if k == '-' || ('0' <= k && k <= '9') {
return '0'
}
return k
// A lookup table keeps the inlining cost as low as possible.
return normKind[k]
}
+32 -17
View File
@@ -17,7 +17,24 @@ import (
"github.com/go-json-experiment/json/internal/jsonwire"
)
// NOTE: Value is analogous to v1 json.RawMessage.
// AppendFloat appends src to dst as a JSON number per RFC 8259, section 6.
//
// Except for -0, which is formatted as -0 instead of 0,
// the output is identical to ECMA-262, 10th edition, section 7.1.12.1
// and (for 64-bit precision) identical to RFC 8785, section 3.2.2.3.
// The values NaN, +Inf, and -Inf will be represented as a JSON string
// with the values "NaN", "Infinity", and "-Infinity".
//
// Note that most JSON libraries and standards assume that JSON numbers
// are 64-bit floating-point numbers. As such, prefer using 64 bits
// of precision unless the recipient can know from other context
// that the encoded number uses 32 bits of precision.
func AppendFloat(dst []byte, src float64, bits int) []byte {
if bits != 32 && bits != 64 {
panic("illegal AppendFloat bit size")
}
return jsonwire.AppendFloat(dst, src, bits)
}
// AppendFormat formats the JSON value in src and appends it to dst
// according to the specified options.
@@ -25,16 +42,18 @@ import (
//
// The dst and src may overlap.
// If an error is reported, then the entirety of src is appended to dst.
func AppendFormat(dst, src []byte, opts ...Options) ([]byte, error) {
func AppendFormat[Bytes ~[]byte | ~string](dst []byte, src Bytes, opts ...Options) ([]byte, error) {
e := getBufferedEncoder(opts...)
defer putBufferedEncoder(e)
e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1)
if err := e.s.WriteValue(src); err != nil {
if err := e.s.WriteValue(Value(src)); err != nil {
return append(dst, src...), err
}
return append(dst, e.s.Buf...), nil
}
// NOTE: Value is analogous to v1 json.RawMessage.
// Value represents a single raw JSON value, which may be one of the following:
// - a JSON literal (i.e., null, true, or false)
// - a JSON string (e.g., "hello, world!")
@@ -113,7 +132,7 @@ func (v *Value) Format(opts ...Options) error {
return v.format(opts, nil)
}
// format accepts two []Options to avoid the allocation appending them together.
// format accepts two []Options to avoid the allocation of appending them together.
// It is equivalent to v.Format(append(opts1, opts2...)...).
func (v *Value) format(opts1, opts2 []Options) error {
e := getBufferedEncoder(opts1...)
@@ -133,7 +152,7 @@ func (v *Value) format(opts1, opts2 []Options) error {
//
// It does not reformat JSON strings or numbers to use any other representation.
// To maximize the set of JSON values that can be formatted,
// this permits values with duplicate names and invalid UTF-8.
// it permits values with duplicate names and invalid UTF-8.
//
// Compact is equivalent to calling [Value.Format] with the following options:
// - [AllowDuplicateNames](true)
@@ -151,11 +170,11 @@ func (v *Value) Compact(opts ...Options) error {
}
// Indent reformats the whitespace in the raw JSON value so that each element
// in a JSON object or array begins on a indented line according to the nesting.
// in a JSON object or array begins on an indented line according to the nesting.
//
// It does not reformat JSON strings or numbers to use any other representation.
// To maximize the set of JSON values that can be formatted,
// this permits values with duplicate names and invalid UTF-8.
// it permits values with duplicate names and invalid UTF-8.
//
// Indent is equivalent to calling [Value.Format] with the following options:
// - [AllowDuplicateNames](true)
@@ -175,8 +194,10 @@ func (v *Value) Indent(opts ...Options) error {
}
// Canonicalize canonicalizes the raw JSON value according to the
// JSON Canonicalization Scheme (JCS) as defined by RFC 8785
// where it produces a stable representation of a JSON value.
// JSON Canonicalization Scheme (JCS) as defined by RFC 8785.
// Canonicalization produces a JSON value with the same meaning as the original,
// but is stable in the sense that calling Canonicalize on a canonicalized
// value does nothing.
//
// JSON strings are formatted to use their minimal representation,
// JSON numbers are formatted as double precision numbers according
@@ -184,12 +205,6 @@ func (v *Value) Indent(opts ...Options) error {
// JSON object members are sorted in ascending order by name.
// All whitespace is removed.
//
// The output stability is dependent on the stability of the application data
// (see RFC 8785, Appendix E). It cannot produce stable output from
// fundamentally unstable input. For example, if the JSON value
// contains ephemeral data (e.g., a frequently changing timestamp),
// then the value is still unstable regardless of whether this is called.
//
// Canonicalize is equivalent to calling [Value.Format] with the following options:
// - [CanonicalizeRawInts](true)
// - [CanonicalizeRawFloats](true)
@@ -214,7 +229,7 @@ func (v *Value) Canonicalize(opts ...Options) error {
}
// MarshalJSON returns v as the JSON encoding of v.
// It returns the stored value as the raw JSON output without any validation.
// It performs no validation.
// If v is nil, then this returns a JSON null.
func (v Value) MarshalJSON() ([]byte, error) {
// NOTE: This matches the behavior of v1 json.RawMessage.MarshalJSON.
@@ -236,7 +251,7 @@ func (v *Value) UnmarshalJSON(b []byte) error {
}
// Kind returns the starting token kind.
// For a valid value, this will never include '}' or ']'.
// For a valid value, this will never include [KindEndObject] or [KindEndArray].
func (v Value) Kind() Kind {
if v := v[jsonwire.ConsumeWhitespace(v):]; len(v) > 0 {
return Kind(v[0]).normalize()
+16 -4
View File
@@ -4,6 +4,8 @@ GOROOT=${1:-../go}
JSONROOT="."
cp $JSONROOT/alias_gen.go $JSONROOT/alias_gen.go.bak
cp $JSONROOT/options_format_global.go $JSONROOT/options_format_global.go.bak
cp $JSONROOT/options_format_test.go $JSONROOT/options_format_test.go.bak
rm -r $JSONROOT/*.go $JSONROOT/internal $JSONROOT/jsontext $JSONROOT/v1
mv $JSONROOT/alias_gen.go.bak $JSONROOT/alias_gen.go
cp -r $GOROOT/src/encoding/json/v2/*.go $JSONROOT/
@@ -18,6 +20,8 @@ for X in $(git ls-files --cached --others --exclude-standard | grep ".*[.]go$");
if [ ! -e "$X" ]; then
continue
fi
sed -i 's/go:build goexperiment.jsonv2 && !goexperiment.jsonformat$/go:build (!goexperiment.jsonv2 || !go1.25) \&\& !goexperiment.jsonformat/' $X
sed -i 's/go:build goexperiment.jsonv2 && goexperiment.jsonformat$/go:build (!goexperiment.jsonv2 || !go1.25) \&\& goexperiment.jsonformat/' $X
sed -i 's/go:build goexperiment.jsonv2$/go:build !goexperiment.jsonv2 || !go1.25/' $X
sed -i 's|"encoding/json/v2"|"github.com/go-json-experiment/json"|' $X
sed -i 's|"encoding/json/internal"|"github.com/go-json-experiment/json/internal"|' $X
@@ -32,10 +36,7 @@ for X in $(git ls-files --cached --others --exclude-standard | grep ".*[.]go$");
done
sed -i 's/v2[.]struct/json.struct/' $JSONROOT/errors_test.go
sed -i 's|jsonv1 "github.com/go-json-experiment/json/v1"|jsonv1 "encoding/json"|' $JSONROOT/bench_test.go
# TODO(go1.25): Remove test that relies on "synctest" that is not available yet.
sed -i '/Issue #73733/,+17d' $JSONROOT/v1/encode_test.go
goimports -w $JSONROOT/v1/encode_test.go
sed -i '/testenv/d' $JSONROOT/jsontext/token_test.go
# Remove documentation that only makes sense within the stdlib.
sed -i '/This package .* is experimental/,+4d' $JSONROOT/doc.go
@@ -46,3 +47,14 @@ git checkout internal/zstd # we still need local copy of zstd for testing
go run alias_gen.go "encoding/json" $JSONROOT/v1
go run alias_gen.go "encoding/json/v2" $JSONROOT
go run alias_gen.go "encoding/json/jsontext" $JSONROOT/jsontext
mv $JSONROOT/options_format_global.go.bak $JSONROOT/options_format_global.go
mv $JSONROOT/options_format_test.go.bak $JSONROOT/options_format_test.go
cp $GOROOT/src/encoding/json/internal/jsonopts/options_format.go $JSONROOT/options_format.go
sed -i '/go:build goexperiment.jsonv2/d' $JSONROOT/options_format.go
sed -i 's/package jsonopts/package json/' $JSONROOT/options_format.go
goimports -w $JSONROOT/options_format.go
sed -i -E '/^func (Marshal|MarshalWrite|MarshalEncode|Unmarshal|UnmarshalRead|UnmarshalDecode)\(/a opts = mayAppendSupportFormatTag(opts)' $JSONROOT/alias.go
sed -i -E '/^func (Marshal|MarshalWrite|MarshalEncode|Unmarshal|UnmarshalRead|UnmarshalDecode)\(/a opts = mayAppendSupportFormatTag(opts)' $JSONROOT/arshal.go
goimports -w $JSONROOT/alias.go
goimports -w $JSONROOT/arshal.go
+59 -44
View File
@@ -23,11 +23,11 @@ import (
// [encoding/json/jsontext.Options]. Options from the other packages can
// be used interchangeably with functionality in this package.
//
// Options represent either a singular option or a set of options.
// It can be functionally thought of as a Go map of option properties
// An Options value represents either a single option or a set of options.
// It can be thought of as a Go map of option properties
// (even though the underlying implementation avoids Go maps for performance).
//
// The constructors (e.g., [Deterministic]) return a singular option value:
// The constructors (e.g., [Deterministic]) return a value for a single option:
//
// opt := Deterministic(true)
//
@@ -35,7 +35,7 @@ import (
//
// opt := Options{"Deterministic": true}
//
// [JoinOptions] composes multiple options values to together:
// [JoinOptions] composes multiple options values together:
//
// out := JoinOptions(opts...)
//
@@ -48,7 +48,7 @@ import (
// }
// }
//
// [GetOption] looks up the value of options parameter:
// [GetOption] looks up the value of an options parameter:
//
// v, ok := GetOption(opts, Deterministic)
//
@@ -65,7 +65,6 @@ import (
// - [FormatNilMapAsNull] affects marshaling only
// - [OmitZeroStructFields] affects marshaling only
// - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling
// - [DiscardUnknownMembers] affects marshaling only
// - [RejectUnknownMembers] affects unmarshaling only
// - [WithMarshalers] affects marshaling only
// - [WithUnmarshalers] affects unmarshaling only
@@ -83,6 +82,7 @@ func JoinOptions(srcs ...Options) Options {
// GetOption returns the value stored in opts with the provided setter,
// reporting whether the value is present.
// If not present, the returned value is the zero value for type T.
//
// Example usage:
//
@@ -97,18 +97,33 @@ func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
}
// DefaultOptionsV2 is the full set of all options that define v2 semantics.
// 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.
// It is equivalent to the set of options in [encoding/json.DefaultOptionsV1]
// all being set to false. All other options are not present.
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 are parsed from a JSON string
// StringifyNumbers specifies that types that would normally be
// encoded as a JSON number instead be encoded as a JSON string
// containing the equivalent JSON number value.
// When unmarshaling, the value is parsed from a JSON string
// containing the JSON number without any surrounding whitespace.
//
// Specifying the `string` tag option on a Go struct field applies this option
// to the top-level JSON value for that field. When applied via the `string`
// tag option, StringifyNumbers option does not recursively apply to nested
// JSON numbers within a JSON object or array.
//
// Like all options, explicitly specifying this option in a call to [Marshal],
// [Unmarshal], etc, will apply recursively.
//
// A Go type with custom marshal/unmarshal that represents a JSON number
// should respect the StringifyNumbers option and if specified
// serialize as a JSON number within a JSON string.
// Custom marshal/unmarshal should handle nested JSON objects using
// [MarshalEncode]/[UnmarshalDecode], which will automatically apply the
// non-recursive `string` tag option behavior.
//
// 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.
// This may cause decoders to lose precision for int64 and uint64 types.
@@ -123,11 +138,18 @@ func StringifyNumbers(v bool) Options {
}
}
// Deterministic specifies that the same input value will be serialized
// as the exact same output bytes. Different processes of
// the same program will serialize equal values to the same bytes,
// but different versions of the same program are not guaranteed
// to produce the exact same sequence of bytes.
// Deterministic specifies that marshaling the same input value will always
// serialize as the same output bytes.
//
// For example, Go maps are marshaled sorted by key.
//
// For native Go types, Determinism is guaranteed across different instances of
// identical binaries, but not across different builds of a program (such as
// different source or toolchain version, different GOOS/GOARCH, different
// build flags).
//
// A Go type with a custom marshaler should also respect the Deterministic
// option and serialize deterministically if it is true.
//
// This only affects marshaling and is ignored when unmarshaling.
func Deterministic(v bool) Options {
@@ -141,8 +163,6 @@ func Deterministic(v bool) Options {
// FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
// JSON null instead of the default representation as an empty JSON array
// (or an empty JSON string in the case of ~[]byte).
// Slice fields explicitly marked with `format:emitempty` still marshal
// as an empty JSON array.
//
// This only affects marshaling and is ignored when unmarshaling.
func FormatNilSliceAsNull(v bool) Options {
@@ -155,8 +175,6 @@ func FormatNilSliceAsNull(v bool) Options {
// FormatNilMapAsNull specifies that a nil Go map should marshal as a
// JSON null instead of the default representation as an empty JSON object.
// Map fields explicitly marked with `format:emitempty` still marshal
// as an empty JSON object.
//
// This only affects marshaling and is ignored when unmarshaling.
func FormatNilMapAsNull(v bool) Options {
@@ -167,11 +185,11 @@ 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
// OmitZeroStructFields specifies that zero-valued fields of Go struct should be
// omitted from the marshaled output.
// A value is considered zero if its type has an "IsZero() bool" method that returns true,
// or if it lacks such a method and the value is a Go zero value.
// This option is equivalent to specifying the `omitzero` tag option
// on every field in a Go struct.
//
// This only affects marshaling and is ignored when unmarshaling.
@@ -185,14 +203,25 @@ func OmitZeroStructFields(v bool) Options {
// MatchCaseInsensitiveNames specifies that JSON object members are matched
// against Go struct fields using a case-insensitive match of the name.
// If a name matches multiple fields, the field whose name matches exactly is chosen.
// If there is none, an error is reported.
// 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.
//
// This affects either marshaling or unmarshaling.
// For marshaling, this option may alter the detection of duplicate names
// (assuming [jsontext.AllowDuplicateNames] is false) from inlined fields
// if it matches one of the declared fields in the Go struct.
//
// Matching names case-insensitively also affects duplicate name detection
// (assuming [jsontext.AllowDuplicateNames] is false) since
// variations of the same name may match the same Go struct field.
// For example, when unmarshaling, the names "foo" and "Foo" may both
// match the same Go struct field and therefore be considered a duplicate name.
// When marshaling, normally it is impossible for any two Go struct fields to
// serialize in a way where they unmarshal into the same Go struct field
// since they all have unique exact names.
// However, it is possible for an
// embedded fallback to contain a name that also matches the name for
// a Go struct field, resulting in a duplicate name error.
func MatchCaseInsensitiveNames(v bool) Options {
if v {
return jsonflags.MatchCaseInsensitiveNames | 1
@@ -201,22 +230,8 @@ func MatchCaseInsensitiveNames(v bool) Options {
}
}
// DiscardUnknownMembers specifies that marshaling should ignore any
// JSON object members stored in Go struct fields dedicated to storing
// unknown JSON object members.
//
// This only affects marshaling and is ignored when unmarshaling.
func DiscardUnknownMembers(v bool) Options {
if v {
return jsonflags.DiscardUnknownMembers | 1
} else {
return jsonflags.DiscardUnknownMembers | 0
}
}
// RejectUnknownMembers specifies that unknown members should be rejected
// when unmarshaling a JSON object, regardless of whether there is a field
// to store unknown members.
// when unmarshaling a JSON object.
//
// This only affects unmarshaling and is ignored when marshaling.
func RejectUnknownMembers(v bool) Options {
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
// NOTE: While [ExperimentalSupportFormatTag] is exported,
// it is in an internal package and thus inaccessible for public use.
//
// The [github.com/go-json-experiment/json] module is kept in sync
// with the Go standard standard library and will expose this option
// in a way that public code can now directly reference.
type supportFormatTag struct {
Options
v bool
}
var (
enableFormatTag = supportFormatTag{v: true}
disableFormatTag = supportFormatTag{v: false}
)
// ExperimentalSupportFormatTag is a marker method that is specially
// recognized by the "encoding/json/v2" runtime.
func (o *supportFormatTag) ExperimentalSupportFormatTag() bool {
return o.v
}
// ExperimentalSupportFormatTag enables support for the `format` tag.
//
// WARNING: This is an experimental feature and will be removed in the future
// as either a failed experiment or be formally included in "encoding/json/v2"
// in some semantically similar form, in which case, users of this option
// must migrate to the officially supported feature.
//
// The `format` tag was originally part of the "encoding/json/v2" experiment
// but support for it was removed (see https://go.dev/issue/79071) for the
// initial release of "encoding/json/v2" in light of the anticipation that
// the Go language might support typed struct tags (https://go.dev/issue/74472).
//
// Typed struct tags are a more expressive and type-safe way to express format
// attributes than the bespoke `format` tag option that implements a miniature
// domain-specific language (DSL) within the "json" package itself.
//
// While "encoding/json/v2" was in the experimental phase,
// some users were already depending on the `format` tag option.
// This experimental option exists to provide a temporary workaround
// until the (hopeful) inclusion of typed struct tags and support for
// formatting directives in "encoding/json/v2" using that language mechanism.
//
// This option enables support for the `format` tag option, which specifies
// a format flag used to specialize the formatting of the field value.
// The option is a key-value pair specified as "format:value" where
// the value must be either a literal consisting of letters and numbers
// (e.g., `format:RFC3339`) or a single-quoted string literal
// (e.g., `format:'2006-01-02'`). The interpretation of the format flag
// is determined by the struct field type.
//
// Go types with alternative representations are as follows:
//
// - A Go []byte or [N]byte is usually represented as a JSON string
// containing the binary value encoded using RFC 4648.
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
// If the format is "base64url", then this uses RFC 4648, section 5.
// If the format is "base32", then this uses RFC 4648, section 6.
// If the format is "base32hex", then this uses RFC 4648, section 7.
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
// If the format is "array", then the bytes value is represented as a JSON array
// where each element recursively uses the JSON representation of each byte.
//
// - A Go float is usually represented as a JSON number.
// If the format is "nonfinite", then NaN, +Inf, and -Inf are represented as
// the JSON strings "NaN", "Infinity", and "-Infinity", respectively.
// Without the use of this format, such string values result in a [SemanticError].
//
// - A nil Go map is usually encoded using an empty JSON object.
// 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.
//
// - A nil Go slice is usually encoded using an empty JSON array.
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
// regardless of whether [FormatNilSliceAsNull] is specified.
//
// - A Go pointer usually uses the JSON representation of the underlying value.
// The format is forwarded to the marshaling and unmarshaling of the underlying type.
//
// - A Go [time.Time] is usually represented as a JSON string containing
// the timestamp formatted in RFC 3339 with nanosecond precision.
// If the format matches one of the format constants declared
// in the time package (e.g., RFC1123), then that format is used.
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
// then the timestamp is represented as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
// To avoid a fractional component when encoding,
// round the timestamp to the relevant unit.
// Otherwise if non-empty, the format is used as-is and
// encoded using [time.Time.Format] and
// decoded using [time.Time.Parse].
//
// - A Go [time.Duration] usually has no default representation.
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is represented as a possibly fractional JSON number
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
// To avoid a fractional component when encoding,
// round the duration to the relevant unit.
// If the format is "units", it is represented as a JSON string
// encoded using [time.Duration.String] and decoded using [time.ParseDuration]
// (e.g., "1h30m" for 1 hour 30 minutes).
// If the format is "iso8601", it is represented as a JSON string using the
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
// using only accurate units of hours, minutes, and seconds.
func ExperimentalSupportFormatTag(v bool) Options {
if v {
return &enableFormatTag
} else {
return &disableFormatTag
}
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package json
import "sync/atomic"
var globalEnableFormatTag atomic.Bool
// ExperimentalGlobalSupportFormatTag globally enables
// [ExperimentalSupportFormatTag] for all calls to
// [Marshal], [MarshalWrite], [MarshalEncode],
// [Unmarshal], [UnmarshalRead], or [UnmarshalDecode].
//
// WARNING: This is an experimental feature and will be removed in the future
// as either a failed experiment or be formally included in "github.com/go-json-experiment/json"
// in some semantically similar form, in which case, users of this option
// must migrate to the officially supported feature.
func ExperimentalGlobalSupportFormatTag(v bool) {
globalEnableFormatTag.Store(v)
}
func mayAppendSupportFormatTag(opts []Options) []Options {
if globalEnableFormatTag.Load() {
var optsArr [4]Options
opts = append(append(optsArr[:0], opts...), &enableFormatTag)
}
return opts
}
+875
View File
@@ -0,0 +1,875 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by alias_gen.go; DO NOT EDIT.
//go:build goexperiment.jsonv2 && go1.25
// Package json implements encoding and decoding of JSON as defined in
// RFC 7159. The mapping between JSON and Go values is described
// in the documentation for the Marshal and Unmarshal functions.
//
// See [JSON and Go] for an introduction to this package.
//
// # Security Considerations
//
// See the "Security Considerations" section in [encoding/json/v2].
//
// For historical reasons, the default behavior of v1 [encoding/json]
// unfortunately operates with less secure defaults.
// New usages of JSON in Go are encouraged to use [encoding/json/v2] instead.
//
// [JSON and Go]: https://go.dev/blog/json
// Migrating to v2
//
// This package (i.e., [encoding/json]) is now formally known as the v1 package
// since a v2 package now exists at [encoding/json/v2].
// All the behavior of the v1 package is implemented in terms of
// the v2 package with the appropriate set of options specified that
// preserve the historical behavior of v1.
//
// The [jsonv2.Marshal] function is the newer equivalent of v1 [Marshal].
// The [jsonv2.Unmarshal] function is the newer equivalent of v1 [Unmarshal].
// The v2 functions have the same calling signature as the v1 equivalent
// except that they take in variadic [Options] arguments that can be specified
// to alter the behavior of marshal or unmarshal. Both v1 and v2 generally
// behave in similar ways, but there are some notable differences.
//
// The following is a list of differences between v1 and v2:
//
// - In v1, JSON object members are unmarshaled into a Go struct using a
// case-insensitive name match with the JSON name of the fields.
// In contrast, v2 matches fields using an exact, case-sensitive match.
// The [jsonv2.MatchCaseInsensitiveNames] and [MatchCaseSensitiveDelimiter]
// options control this behavior difference. To explicitly specify a Go struct
// field to use a particular name matching scheme, either the `case:ignore`
// or the `case:strict` field option can be specified.
// Field-specified options take precedence over caller-specified options.
//
// - In v1, when marshaling a Go struct, a 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. In contrast, v2 redefines
// `omitempty` to omit a field if it encodes as an "empty" JSON value,
// which is defined as a JSON null, or an empty JSON string, object, or array.
// The [OmitEmptyWithLegacySemantics] option controls this behavior difference.
// Note that `omitempty` behaves identically in both v1 and v2 for a
// Go array, slice, map, or string (assuming no user-defined MarshalJSON method
// overrides the default representation). Existing usages of `omitempty` on a
// Go bool, number, pointer, or interface value should migrate to specifying
// `omitzero` instead (which is identically supported in both v1 and v2).
//
// - In v1, a Go struct field marked as `string` can be used to quote a
// Go string, bool, number, or pointer to such as a JSON string.
// In contrast, v2 restricts the `string` option to only quote a value
// that would normally be represented as a JSON number,
// but also expands support for it to operate with any Go type
// that would normally be represented as a JSON number.
// The [StringifyWithLegacySemantics] option controls this behavior difference.
//
// - In v1, a nil Go slice or Go map is marshaled as a JSON null.
// In contrast, v2 marshals a nil Go slice or Go map as
// an empty JSON array or JSON object, respectively.
// The [jsonv2.FormatNilSliceAsNull] and [jsonv2.FormatNilMapAsNull] options
// control this behavior difference.
//
// - In v1, a Go array may be unmarshaled from a JSON array of any length.
// In contrast, in v2 a Go array must be unmarshaled from a JSON array
// of the same length, otherwise it results in an error.
// The [UnmarshalArrayFromAnyLength] option controls this behavior difference.
//
// - In v1, a Go byte array is represented as a JSON array of JSON numbers.
// In contrast, in v2 a Go byte array is represented as a Base64-encoded JSON string.
// The [FormatByteArrayAsArray] option controls this behavior difference.
//
// - In v1, MarshalJSON methods declared on a pointer receiver are only called
// if the Go value is addressable. In contrast, in v2 a MarshalJSON method
// is always callable regardless of addressability.
// The [CallMethodsWithLegacySemantics] option controls this behavior difference.
//
// - In v1, MarshalJSON and UnmarshalJSON methods are never called for Go map keys.
// In contrast, in v2 a MarshalJSON or UnmarshalJSON method is eligible for
// being called for Go map keys.
// The [CallMethodsWithLegacySemantics] option controls this behavior difference.
//
// - In v1, a Go map is marshaled in a deterministic order.
// In contrast, in v2 a Go map is marshaled in a non-deterministic order.
// The [jsonv2.Deterministic] option controls this behavior difference.
//
// - In v1, JSON strings are encoded with HTML-specific or JavaScript-specific
// characters being escaped. In contrast, in v2 JSON strings use the minimal
// encoding and only escape if required by the JSON grammar.
// The [jsontext.EscapeForHTML] and [jsontext.EscapeForJS] options
// control this behavior difference.
//
// - In v1, bytes of invalid UTF-8 within a string are silently replaced with
// the Unicode replacement character. In contrast, in v2 the presence of
// invalid UTF-8 results in an error. The [jsontext.AllowInvalidUTF8] option
// controls this behavior difference.
//
// - In v1, a JSON object with duplicate names is permitted.
// In contrast, in v2 a JSON object with duplicate names results in an error.
// The [jsontext.AllowDuplicateNames] option controls this behavior difference.
//
// - In v1, when unmarshaling a JSON null into a non-empty Go value it will
// inconsistently either zero out the value or do nothing.
// In contrast, in v2 unmarshaling a JSON null will consistently and always
// zero out the underlying Go value. The [MergeWithLegacySemantics] option
// controls this behavior difference.
//
// - In v1, when unmarshaling a JSON value into a non-zero Go value,
// it merges into the original Go value for array elements, slice elements,
// struct fields (but not map values),
// pointer values, and interface values (only if a non-nil pointer).
// In contrast, in v2 unmarshal merges into the Go value
// for struct fields, map values, pointer values, and interface values.
// In general, the v2 semantic merges when unmarshaling a JSON object,
// otherwise it replaces the value. The [MergeWithLegacySemantics] option
// controls this behavior difference.
//
// - In v1, a [time.Duration] is represented as a JSON number containing
// the decimal number of nanoseconds. In contrast, in v2 a [time.Duration]
// has no default representation and results in a runtime error.
// The [FormatDurationAsNano] option controls this behavior difference.
//
// - In v1, errors are never reported at runtime for Go struct types
// that have some form of structural error (e.g., a malformed tag option).
// In contrast, v2 reports a runtime error for Go types that are invalid
// as they relate to JSON serialization. For example, a Go struct
// with only unexported fields cannot be serialized.
// The [ReportErrorsWithLegacySemantics] option controls this behavior difference.
//
// As mentioned, the entirety of v1 is implemented in terms of v2,
// where options are implicitly specified to opt into legacy behavior.
// For example, [Marshal] directly calls [jsonv2.Marshal] with [DefaultOptionsV1].
// Similarly, [Unmarshal] directly calls [jsonv2.Unmarshal] with [DefaultOptionsV1].
// The [DefaultOptionsV1] option represents the set of all options that specify
// default v1 behavior.
//
// For many of the behavior differences, there are Go struct field options
// that the author of a Go type can specify to control the behavior such that
// the type is represented identically in JSON under either v1 or v2 semantics.
//
// The availability of [DefaultOptionsV1] and [jsonv2.DefaultOptionsV2],
// where later options take precedence over former options allows for
// a gradual migration from v1 to v2. For example:
//
// - jsonv1.Marshal(v)
// uses default v1 semantics.
//
// - jsonv2.Marshal(v, jsonv1.DefaultOptionsV1())
// is semantically equivalent to jsonv1.Marshal
// and thus uses default v1 semantics.
//
// - jsonv2.Marshal(v, jsonv1.DefaultOptionsV1(), jsontext.AllowDuplicateNames(false))
// uses mostly v1 semantics, but opts into one particular v2-specific behavior.
//
// - jsonv2.Marshal(v, jsonv1.CallMethodsWithLegacySemantics(true))
// uses mostly v2 semantics, but opts into one particular v1-specific behavior.
//
// - jsonv2.Marshal(v, ..., jsonv2.DefaultOptionsV2())
// is semantically equivalent to jsonv2.Marshal since
// jsonv2.DefaultOptionsV2 overrides any options specified earlier
// and thus uses default v2 semantics.
//
// - jsonv2.Marshal(v)
// uses default v2 semantics.
//
// All new usages of "json" in Go should use the v2 package,
// but the v1 package will forever remain supported.
//
// See the [encoding/json/v2 Migration Guide] for additional detail on migration approaches.
//
// [encoding/json/v2 Migration Guide]: https://go.dev/doc/jsonv2-migration
package json
import (
"bytes"
"encoding/json"
"io"
)
// Unmarshal parses the JSON-encoded data and stores the result
// in the value pointed to by v. If v is nil or not a pointer,
// Unmarshal returns an [InvalidUnmarshalError].
//
// Unmarshal uses the inverse of the encodings that
// [Marshal] uses, allocating maps, slices, and pointers as necessary,
// with the following additional rules:
//
// To unmarshal JSON into a pointer, Unmarshal first handles the case of
// the JSON being the JSON literal null. In that case, Unmarshal sets
// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
// the value pointed at by the pointer. If the pointer is nil, Unmarshal
// allocates a new value for it to point to.
//
// The JSON input is decoded according the following rules:
//
// - If the value type implements [jsonv2.UnmarshalerFrom],
// then the UnmarshalJSONFrom method is called to decode the JSON value.
// If the method returns [errors.ErrUnsupported],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [Unmarshaler],
// then the UnmarshalJSON method is called to decode the JSON value,
// including when the input is a JSON null.
//
// - If the value implements [encoding.TextUnmarshaler] and
// the input is a JSON string, then the UnmarshalText method
// is called with the unquoted form of the string.
//
// Otherwise, Unmarshal uses the following type-dependent default decodings:
//
// To unmarshal JSON into a struct, Unmarshal matches incoming object
// keys to the keys used by [Marshal] (either the struct field name or its tag),
// preferring an exact match but also accepting a case-insensitive match. By
// default, object keys which don't have a corresponding struct field are
// ignored (see [Decoder.DisallowUnknownFields] for an alternative).
//
// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
//
// - bool, for JSON booleans
// - float64, for JSON numbers
// - string, for JSON strings
// - []any, for JSON arrays
// - map[string]any, for JSON objects
// - nil for JSON null
//
// To unmarshal a JSON array into a slice, Unmarshal decodes each JSON array
// element into the corresponding slice element, reusing existing slice
// elements in-place. The slice grows to accommodate additional elements,
// or is truncated if the JSON array is shorter.
// As a special case, to unmarshal an empty JSON array into a slice,
// Unmarshal replaces the slice with a new empty slice.
//
// To unmarshal a JSON array into a Go array, Unmarshal decodes
// JSON array elements into corresponding Go array elements.
// If the Go array is smaller than the JSON array,
// the additional JSON array elements are discarded.
// If the JSON array is smaller than the Go array,
// the additional Go array elements are set to zero values.
//
// To unmarshal a JSON object into a map, Unmarshal first establishes a map to
// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal
// reuses the existing map, keeping existing entries. Unmarshal then stores
// key-value pairs from the JSON object into the map. The map's key type must
// either be any string type, an integer, or implement [encoding.TextUnmarshaler].
//
// If the JSON-encoded data contain a syntax error, Unmarshal returns a [SyntaxError].
//
// If a JSON value is not appropriate for a given target type,
// or if a JSON number overflows the target type, Unmarshal
// skips that field and completes the unmarshaling as best it can.
// If no more serious errors are encountered, Unmarshal returns
// an [UnmarshalTypeError] describing the earliest such error. In any
// case, it's not guaranteed that all the remaining fields following
// the problematic one will be unmarshaled into the target object.
//
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// “not present,” unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
//
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
func Unmarshal(data []byte, v any) error {
return json.Unmarshal(data, v)
}
// Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of
// a JSON value. UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
type Unmarshaler = json.Unmarshaler
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError = json.UnmarshalTypeError
// An UnmarshalFieldError describes a JSON object key that
// led to an unexported (and therefore unwritable) struct field.
//
// Deprecated: No longer used; kept for compatibility.
type UnmarshalFieldError = json.UnmarshalFieldError
// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal].
// (The argument to [Unmarshal] must be a non-nil pointer.)
type InvalidUnmarshalError = json.InvalidUnmarshalError
// A Number represents a JSON number literal.
type Number = json.Number
// Marshal returns the JSON encoding of v.
//
// Marshal traverses the value v recursively.
//
// The input value is encoded as JSON according the following rules:
//
// - If the value type implements [jsonv2.MarshalerTo],
// then the MarshalJSONTo method is called to encode the value.
// If the method returns [errors.ErrUnsupported],
// then the input is encoded according to subsequent rules.
//
// - 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.
//
// Otherwise, Marshal uses the following type-dependent default encodings:
//
// Boolean values encode as JSON booleans.
//
// Floating point, integer, and [Number] values encode as JSON numbers.
// NaN and +/-Inf values will return an [UnsupportedValueError].
//
// String values encode as JSON strings coerced to valid UTF-8,
// replacing invalid bytes with the Unicode replacement rune.
// So that the JSON will be safe to embed inside HTML <script> tags,
// the string is encoded using [HTMLEscape],
// which replaces "<", ">", "&", U+2028, and U+2029 are escaped
// to "\u003c","\u003e", "\u0026", "\u2028", and "\u2029".
// This replacement can be disabled when using an [Encoder],
// by calling [Encoder.SetEscapeHTML](false).
//
// Array and slice values encode as JSON arrays, except that
// []byte encodes as a base64-encoded string, and a nil slice
// encodes as the null JSON value.
//
// Struct values encode as JSON objects.
// Each exported struct field becomes a member of the object, using the
// field name as the object key, unless the field is omitted for one of the
// reasons given below.
//
// The encoding of each struct field can be customized by the format string
// stored under the "json" key in the struct field's tag.
// The format string gives the name of the field, possibly followed by a
// comma-separated list of options. The name may be empty in order to
// specify options without overriding the default field name.
//
// The "omitempty" option specifies that the field should be omitted
// from the encoding if the field has an empty value, defined as
// false, 0, a nil pointer, a nil interface value, and any array,
// slice, map, or string of length zero.
//
// As a special case, if the field tag is "-", the field is always omitted.
// JSON names containing commas or quotes, or names identical to "" or "-",
// can be specified using a single-quoted string literal, where the syntax
// is identical to the Go grammar for a double-quoted string literal,
// but instead uses single quotes as the delimiters.
//
// Examples of struct field tags and their meanings:
//
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
//
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
//
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
//
// // Field is ignored by this package.
// Field int `json:"-"`
//
// // Field appears in JSON as key "-".
// Field int `json:"'-'"`
//
// The "omitzero" option specifies that the field should be omitted
// from the encoding if the field has a zero value, according to rules:
//
// 1) If the field type has an "IsZero() bool" method, that will be used to
// determine whether the value is zero.
//
// 2) Otherwise, the value is zero if it is the zero value for its type.
//
// If both "omitempty" and "omitzero" are specified, the field will be omitted
// if the value is either empty or zero (or both).
//
// The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs:
//
// Int64String int64 `json:",string"`
//
// The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, and ASCII punctuation except quotation
// marks, backslash, and comma.
//
// Embedded struct fields are usually marshaled as if their inner exported fields
// were fields in the outer struct, subject to the usual Go visibility rules amended
// as described in the next paragraph.
// An anonymous struct field with a name given in its JSON tag is treated as
// having that name, rather than being anonymous.
// An anonymous struct field of interface type is treated the same as having
// that type as its name, rather than being anonymous.
//
// The Go visibility rules for struct fields are amended for JSON when
// deciding which field to marshal or unmarshal. If there are
// multiple fields at the same level, and that level is the least
// nested (and would therefore be the nesting level selected by the
// usual Go rules), the following extra rules apply:
//
// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered,
// even if there are multiple untagged fields that would otherwise conflict.
//
// 2) If there is exactly one field (tagged or not according to the first rule), that is selected.
//
// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
//
// Handling of anonymous struct fields is new in Go 1.1.
// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of
// an anonymous struct field in both current and earlier versions, give the field
// a JSON tag of "-".
//
// Map values encode as JSON objects. The map's key type must either be a
// string, an integer type, or implement [encoding.TextMarshaler]. The map keys
// are sorted and used as JSON object keys by applying the following rules,
// subject to the UTF-8 coercion described for string values above:
// - keys of any string type are used directly
// - keys that implement [encoding.TextMarshaler] are marshaled
// - integer keys are converted to strings
//
// Pointer values encode as the value pointed to.
// A nil pointer encodes as the null JSON value.
//
// Interface values encode as the value contained in the interface.
// A nil interface value encodes as the null JSON value.
//
// Channel, complex, and function values cannot be encoded in JSON.
// Attempting to encode such a value causes Marshal to return
// an [UnsupportedTypeError].
//
// JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in
// an error.
func Marshal(v any) ([]byte, error) {
return json.Marshal(v)
}
// MarshalIndent is like [Marshal] but applies [Indent] to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
return json.MarshalIndent(v, prefix, indent)
}
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler = json.Marshaler
// An UnsupportedTypeError is returned by [Marshal] when attempting
// to encode an unsupported value type.
type UnsupportedTypeError = json.UnsupportedTypeError
// An UnsupportedValueError is returned by [Marshal] when attempting
// to encode an unsupported value.
type UnsupportedValueError = json.UnsupportedValueError
// Before Go 1.2, an InvalidUTF8Error was returned by [Marshal] when
// attempting to encode a string value with invalid UTF-8 sequences.
// As of Go 1.2, [Marshal] instead coerces the string to valid UTF-8 by
// replacing invalid bytes with the Unicode replacement rune U+FFFD.
//
// Deprecated: No longer used; kept for compatibility.
type InvalidUTF8Error = json.InvalidUTF8Error
// A MarshalerError represents an error from calling a
// [Marshaler.MarshalJSON] or [encoding.TextMarshaler.MarshalText] method.
type MarshalerError = json.MarshalerError
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must be used.
func HTMLEscape(dst *bytes.Buffer, src []byte) {
json.HTMLEscape(dst, src)
}
// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
return json.Compact(dst, src)
}
// Indent appends to dst an indented form of the JSON-encoded src.
// 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 indentation nesting.
// The data appended to dst does not begin with the prefix nor
// any indentation, to make it easier to embed inside other formatted JSON data.
// Although leading space characters (space, tab, carriage return, newline)
// at the beginning of src are dropped, trailing space characters
// at the end of src are preserved and copied to dst.
// For example, if src has no trailing spaces, neither will dst;
// if src ends in a trailing newline, so will dst.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
return json.Indent(dst, src, prefix, indent)
}
// Options are a set of options to configure the v2 "json" package
// to operate with v1 semantics for particular features.
// Values of this type can be passed to v2 functions like
// [jsonv2.Marshal] or [jsonv2.Unmarshal].
// Instead of referencing this type, use [jsonv2.Options].
//
// See the "Migrating to v2" section for guidance on how to migrate usage
// of "json" from using v1 to using v2 instead.
type Options = json.Options
// DefaultOptionsV1 is the full set of all options that define v1 semantics.
// It is equivalent to the following boolean options being set to true:
//
// - [CallMethodsWithLegacySemantics]
// - [FormatByteArrayAsArray]
// - [FormatBytesWithLegacySemantics]
// - [FormatDurationAsNano]
// - [MatchCaseSensitiveDelimiter]
// - [MergeWithLegacySemantics]
// - [OmitEmptyWithLegacySemantics]
// - [ParseBytesWithLooseRFC4648]
// - [ParseTimeWithLooseRFC3339]
// - [ReportErrorsWithLegacySemantics]
// - [StringifyWithLegacySemantics]
// - [UnmarshalArrayFromAnyLength]
// - [jsonv2.Deterministic]
// - [jsonv2.FormatNilMapAsNull]
// - [jsonv2.FormatNilSliceAsNull]
// - [jsonv2.MatchCaseInsensitiveNames]
// - [jsontext.AllowDuplicateNames]
// - [jsontext.AllowInvalidUTF8]
// - [jsontext.EscapeForHTML]
// - [jsontext.EscapeForJS]
// - [jsontext.PreserveRawStrings]
//
// All other options are not present.
//
// The [Marshal] and [Unmarshal] functions in this package are
// semantically identical to calling the v2 equivalents with this option:
//
// jsonv2.Marshal(v, jsonv1.DefaultOptionsV1())
// jsonv2.Unmarshal(b, v, jsonv1.DefaultOptionsV1())
func DefaultOptionsV1() Options {
return json.DefaultOptionsV1()
}
// CallMethodsWithLegacySemantics specifies that calling of type-provided
// marshal and unmarshal methods follow legacy semantics:
//
// - When marshaling, a marshal method declared on a pointer receiver
// is only called if the Go value is addressable.
// Values obtained from an interface or map element are not addressable.
// Values obtained from a pointer or slice element are addressable.
// Values obtained from an array element or struct field inherit
// the addressability of the parent. In contrast, the v2 semantic
// is to always call marshal methods regardless of addressability.
//
// - When marshaling or unmarshaling, the [Marshaler] or [Unmarshaler]
// methods are ignored for map keys. However, [encoding.TextMarshaler]
// or [encoding.TextUnmarshaler] are still callable.
// In contrast, the v2 semantic is to serialize map keys
// like any other value (with regard to calling methods),
// which may include calling [Marshaler] or [Unmarshaler] methods,
// where it is the implementation's responsibility to represent the
// Go value as a JSON string (as required for JSON object names).
//
// - When marshaling, if a map key value implements a marshal method
// and is a nil pointer, then it is serialized as an empty JSON string.
// In contrast, the v2 semantic is to report an error.
//
// - When marshaling, if an interface type implements a marshal method
// and the interface value is a nil pointer to a concrete type,
// then the marshal method is always called.
// In contrast, the v2 semantic is to never directly call methods
// on interface values and to instead defer evaluation based upon
// the underlying concrete value. Similar to non-interface values,
// marshal methods are not called on nil pointers and
// are instead serialized as a JSON null.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func CallMethodsWithLegacySemantics(v bool) Options {
return json.CallMethodsWithLegacySemantics(v)
}
// FormatByteArrayAsArray specifies that a Go [N]byte is
// formatted as a normal Go array in contrast to the v2 default of
// formatting [N]byte as using binary data encoding (RFC 4648).
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func FormatByteArrayAsArray(v bool) Options {
return json.FormatByteArrayAsArray(v)
}
// FormatBytesWithLegacySemantics specifies that handling of
// []~byte and [N]~byte types follow legacy semantics:
//
// - A Go []~byte is to be treated as using some form of
// binary data encoding (RFC 4648) in contrast to the v2 default
// of only treating []byte as such. In particular, v2 does not
// treat slices of named byte types as representing binary data.
//
// - When marshaling, if a named byte implements a marshal method,
// then the slice is serialized as a JSON array of elements,
// each of which call the marshal method.
//
// - When unmarshaling, if the input is a JSON array,
// then unmarshal into the []~byte as if it were a normal Go slice.
// In contrast, the v2 default is to report an error unmarshaling
// a JSON array when expecting some form of binary data encoding.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func FormatBytesWithLegacySemantics(v bool) Options {
return json.FormatBytesWithLegacySemantics(v)
}
// FormatDurationAsNano specifies that a [time.Duration] is
// formatted as a JSON number representing the number of nanoseconds
// in contrast to the v2 default of reporting an error.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func FormatDurationAsNano(v bool) Options {
return json.FormatDurationAsNano(v)
}
// MatchCaseSensitiveDelimiter specifies that underscores and dashes are
// not to be ignored when performing case-insensitive name matching which
// occurs under [jsonv2.MatchCaseInsensitiveNames] or the `case:ignore` tag option.
// Thus, case-insensitive name matching is identical to [strings.EqualFold].
// Use of this option diminishes the ability of case-insensitive matching
// to be able to match common case variants (e.g., "foo_bar" with "fooBar").
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func MatchCaseSensitiveDelimiter(v bool) Options {
return json.MatchCaseSensitiveDelimiter(v)
}
// MergeWithLegacySemantics specifies that unmarshaling into a non-zero
// Go value follows legacy semantics:
//
// - When unmarshaling a JSON null, this preserves the original Go value
// if the kind is a bool, int, uint, float, string, array, or struct.
// Otherwise, it zeros the Go value.
// In contrast, the default v2 behavior is to consistently and always
// zero the Go value when unmarshaling a JSON null into it.
//
// - When unmarshaling a JSON value other than null, this merges into
// the original Go value for array elements, slice elements,
// struct fields (but not map values),
// pointer values, and interface values (only if a non-nil pointer).
// For slices, it will merge into the pre-existing value of slice elements
// even for those past the slice length. If the original slice length
// was longer than the JSON array, then it is truncated to match.
// In contrast, the default v2 behavior is to merge into the Go value
// for struct fields, map values, pointer values, and interface values.
// In general, the v2 semantic merges when unmarshaling a JSON object,
// otherwise it replaces the original value.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func MergeWithLegacySemantics(v bool) Options {
return json.MergeWithLegacySemantics(v)
}
// OmitEmptyWithLegacySemantics specifies that the `omitempty` tag option
// follows a definition of empty where a field is omitted if the Go value is
// false, 0, a nil pointer, a nil interface value,
// or any empty array, slice, map, or string.
// This overrides the v2 semantic where a field is empty if the value
// marshals as a JSON null or an empty JSON string, object, or array.
//
// The v1 and v2 definitions of `omitempty` are practically the same for
// Go strings, slices, arrays, and maps. Usages of `omitempty` on
// Go bools, ints, uints, floats, pointers, and interfaces should migrate to use
// the `omitzero` tag option, which omits a field if it is the zero Go value.
//
// This only affects marshaling and is ignored when unmarshaling.
// The v1 default is true.
func OmitEmptyWithLegacySemantics(v bool) Options {
return json.OmitEmptyWithLegacySemantics(v)
}
// ParseBytesWithLooseRFC4648 specifies that when parsing
// binary data encoded as "base32" or "base64",
// to ignore the presence of '\r' and '\n' characters.
// In contrast, the v2 default is to report an error in order to be
// strictly compliant with RFC 4648, section 3.3,
// which specifies that non-alphabet characters must be rejected.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func ParseBytesWithLooseRFC4648(v bool) Options {
return json.ParseBytesWithLooseRFC4648(v)
}
// ParseTimeWithLooseRFC3339 specifies that a [time.Time]
// parses according to loose adherence to RFC 3339.
// In particular, it permits historically incorrect representations,
// allowing for deviations in hour format, sub-second separator,
// and timezone representation. In contrast, the default v2 behavior
// is to strictly comply with the grammar specified in RFC 3339.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func ParseTimeWithLooseRFC3339(v bool) Options {
return json.ParseTimeWithLooseRFC3339(v)
}
// ReportErrorsWithLegacySemantics specifies that Marshal and Unmarshal
// should report errors with legacy semantics:
//
// - When marshaling or unmarshaling, the returned error values are
// usually of types such as [SyntaxError], [MarshalerError],
// [UnsupportedTypeError], [UnsupportedValueError],
// [InvalidUnmarshalError], or [UnmarshalTypeError].
// In contrast, the v2 semantic is to always return errors as either
// [jsonv2.SemanticError] or [jsontext.SyntacticError].
//
// - When marshaling, if a user-defined marshal method reports an error,
// it is always wrapped in a [MarshalerError], even if the error itself
// is already a [MarshalerError], which may lead to multiple redundant
// layers of wrapping. In contrast, the v2 semantic is to
// always wrap an error within [jsonv2.SemanticError]
// unless it is already a semantic error.
//
// - When unmarshaling, if a user-defined unmarshal method reports an error,
// it is never wrapped and reported verbatim. In contrast, the v2 semantic
// is to always wrap an error within [jsonv2.SemanticError]
// unless it is already a semantic error.
//
// - When marshaling or unmarshaling, if a Go struct contains type errors
// (e.g., conflicting names or malformed field tags), then such errors
// are ignored and the Go struct uses a best-effort representation.
// In contrast, the v2 semantic is to report a runtime error.
//
// - When unmarshaling with [jsonv2.MatchCaseInsensitiveNames], if a JSON
// object name has a non-exact match with multiple Go struct fields, then
// an error is not reported and instead the first declared field is used.
// In contrast, the v2 semantic is to report a runtime error.
//
// - When unmarshaling, the syntactic structure of the JSON input
// is fully validated before performing the semantic unmarshaling
// of the JSON data into the Go value. Practically speaking,
// this means that JSON input with syntactic errors do not result
// in any mutations of the target Go value. In contrast, the v2 semantic
// is to perform a streaming decode and gradually unmarshal the JSON input
// into the target Go value, which means that the Go value may be
// partially mutated when a syntactic error is encountered.
//
// - When unmarshaling, a semantic error does not immediately terminate the
// unmarshal procedure, but rather evaluation continues.
// When unmarshal returns, only the first semantic error is reported.
// In contrast, the v2 semantic is to terminate unmarshal the moment
// an error is encountered.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func ReportErrorsWithLegacySemantics(v bool) Options {
return json.ReportErrorsWithLegacySemantics(v)
}
// StringifyWithLegacySemantics specifies that the `string` tag option
// may stringify bools and string values. It only takes effect on fields
// where the top-level type is a bool, string, numeric kind, or a pointer to
// such a kind. In contrast, the v2 default only allows the `string` tag option
// on Go types that would have otherwise serialized as a JSON number,
// and stringifies the JSON number within a JSON string. In particular,
// the v2 default does not stringify Go bools and strings.
// If [ReportErrorsWithLegacySemantics] is false,
// then incorrect usages of `string` result in a runtime error.
//
// When marshaling, such Go values are serialized as their usual JSON
// representation, but quoted within a JSON string.
// When unmarshaling, such Go values must be deserialized from a JSON string
// containing their usual JSON representation or Go number representation for
// that numeric kind.
// Note that the Go number grammar is a superset of the JSON number grammar.
// A JSON null quoted in a JSON string is a valid substitute for JSON null
// while unmarshaling into a Go value that `string` takes effect on.
// In contrast, the v2 default rejects stringified numbers outside of the
// grammar for a JSON number and also rejects a JSON null quoted in a JSON string.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func StringifyWithLegacySemantics(v bool) Options {
return json.StringifyWithLegacySemantics(v)
}
// UnmarshalArrayFromAnyLength specifies that Go arrays can be unmarshaled
// from input JSON arrays of any length. If the JSON array is too short,
// then the remaining Go array elements are zeroed. If the JSON array
// is too long, then the excess JSON array elements are skipped over.
// In contrast, the v2 default expects that Go arrays be unmarshaled
// from input JSON arrays of the exact same length.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func UnmarshalArrayFromAnyLength(v bool) Options {
return json.UnmarshalArrayFromAnyLength(v)
}
// Valid reports whether data is a valid JSON encoding.
func Valid(data []byte) bool {
return json.Valid(data)
}
// A SyntaxError is a description of a JSON syntax error.
// [Unmarshal] will return a SyntaxError if the JSON can't be parsed.
type SyntaxError = json.SyntaxError
// A Decoder reads and decodes JSON values from an input stream.
type Decoder = json.Decoder
// NewDecoder returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may
// read data from r beyond the JSON values requested.
func NewDecoder(r io.Reader) *Decoder {
return json.NewDecoder(r)
}
// An Encoder writes JSON values to an output stream.
type Encoder = json.Encoder
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return json.NewEncoder(w)
}
// RawMessage is a raw encoded JSON value.
// It implements [Marshaler] and [Unmarshaler] and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawMessage = json.RawMessage
// A Token holds a value of one of these types:
//
// - [Delim], for the four JSON delimiters [ ] { }
// - bool, for JSON booleans
// - float64, for JSON numbers
// - [Number], for JSON numbers
// - string, for JSON string literals
// - nil, for JSON null
type Token = json.Token
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
type Delim = json.Delim
+271
View File
@@ -0,0 +1,271 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
// Represents JSON data structure using native Go types: booleans, floats,
// strings, arrays, and maps.
package json
import (
"cmp"
"fmt"
"reflect"
"strconv"
"strings"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/internal/jsonwire"
"github.com/go-json-experiment/json/jsontext"
)
// Unmarshal parses the JSON-encoded data and stores the result
// in the value pointed to by v. If v is nil or not a pointer,
// Unmarshal returns an [InvalidUnmarshalError].
//
// Unmarshal uses the inverse of the encodings that
// [Marshal] uses, allocating maps, slices, and pointers as necessary,
// with the following additional rules:
//
// To unmarshal JSON into a pointer, Unmarshal first handles the case of
// the JSON being the JSON literal null. In that case, Unmarshal sets
// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into
// the value pointed at by the pointer. If the pointer is nil, Unmarshal
// allocates a new value for it to point to.
//
// The JSON input is decoded according the following rules:
//
// - If the value type implements [jsonv2.UnmarshalerFrom],
// then the UnmarshalJSONFrom method is called to decode the JSON value.
// If the method returns [errors.ErrUnsupported],
// then the input is decoded according to subsequent rules.
//
// - If the value type implements [Unmarshaler],
// then the UnmarshalJSON method is called to decode the JSON value,
// including when the input is a JSON null.
//
// - If the value implements [encoding.TextUnmarshaler] and
// the input is a JSON string, then the UnmarshalText method
// is called with the unquoted form of the string.
//
// Otherwise, Unmarshal uses the following type-dependent default decodings:
//
// To unmarshal JSON into a struct, Unmarshal matches incoming object
// keys to the keys used by [Marshal] (either the struct field name or its tag),
// preferring an exact match but also accepting a case-insensitive match. By
// default, object keys which don't have a corresponding struct field are
// ignored (see [Decoder.DisallowUnknownFields] for an alternative).
//
// To unmarshal JSON into an interface value,
// Unmarshal stores one of these in the interface value:
//
// - bool, for JSON booleans
// - float64, for JSON numbers
// - string, for JSON strings
// - []any, for JSON arrays
// - map[string]any, for JSON objects
// - nil for JSON null
//
// To unmarshal a JSON array into a slice, Unmarshal decodes each JSON array
// element into the corresponding slice element, reusing existing slice
// elements in-place. The slice grows to accommodate additional elements,
// or is truncated if the JSON array is shorter.
// As a special case, to unmarshal an empty JSON array into a slice,
// Unmarshal replaces the slice with a new empty slice.
//
// To unmarshal a JSON array into a Go array, Unmarshal decodes
// JSON array elements into corresponding Go array elements.
// If the Go array is smaller than the JSON array,
// the additional JSON array elements are discarded.
// If the JSON array is smaller than the Go array,
// the additional Go array elements are set to zero values.
//
// To unmarshal a JSON object into a map, Unmarshal first establishes a map to
// use. If the map is nil, Unmarshal allocates a new map. Otherwise Unmarshal
// reuses the existing map, keeping existing entries. Unmarshal then stores
// key-value pairs from the JSON object into the map. The map's key type must
// either be any string type, an integer, or implement [encoding.TextUnmarshaler].
//
// If the JSON-encoded data contain a syntax error, Unmarshal returns a [SyntaxError].
//
// If a JSON value is not appropriate for a given target type,
// or if a JSON number overflows the target type, Unmarshal
// skips that field and completes the unmarshaling as best it can.
// If no more serious errors are encountered, Unmarshal returns
// an [UnmarshalTypeError] describing the earliest such error. In any
// case, it's not guaranteed that all the remaining fields following
// the problematic one will be unmarshaled into the target object.
//
// The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean
// “not present,” unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error.
//
// When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement
// character U+FFFD.
func Unmarshal(data []byte, v any) error {
return jsonv2.Unmarshal(data, v, DefaultOptionsV1())
}
// Unmarshaler is the interface implemented by types
// that can unmarshal a JSON description of themselves.
// The input can be assumed to be a valid encoding of
// a JSON value. UnmarshalJSON must copy the JSON data
// if it wishes to retain the data after returning.
type Unmarshaler = jsonv2.Unmarshaler
// An UnmarshalTypeError describes a JSON value that was
// not appropriate for a value of a specific Go type.
type UnmarshalTypeError struct {
Value string // description of JSON value - "bool", "array", "number -5"
Type reflect.Type // type of Go value it could not be assigned to
Offset int64 // error occurred after reading Offset bytes
Struct string // name of the root type containing the field
Field string // the full path from root node to the value
Err error // may be nil
}
func (e *UnmarshalTypeError) Error() string {
var s string
if e.Struct != "" || e.Field != "" {
// The design of UnmarshalTypeError overly assumes a struct-based
// Go representation for the JSON value.
// The logic in jsontext represents paths using a JSON Pointer,
// which is agnostic to the Go type system.
// Trying to convert a JSON Pointer into an UnmarshalTypeError.Field
// is difficult. As a heuristic, if the last path token looks like
// an index into a JSON array (e.g., ".foo.bar.0"),
// avoid the phrase "Go struct field ".
intoWhat := "Go struct field "
i := strings.LastIndexByte(e.Field, '.') + len(".")
if len(e.Field[i:]) > 0 && strings.TrimRight(e.Field[i:], "0123456789") == "" {
intoWhat = "" // likely a Go slice or array
}
s = "json: cannot unmarshal " + e.Value + " into " + intoWhat + e.Struct + "." + e.Field + " of type " + e.Type.String()
} else {
s = "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String()
}
if e.Err != nil {
s += ": " + e.Err.Error()
}
return s
}
func (e *UnmarshalTypeError) Unwrap() error {
return e.Err
}
// An UnmarshalFieldError describes a JSON object key that
// led to an unexported (and therefore unwritable) struct field.
//
// Deprecated: No longer used; kept for compatibility.
type UnmarshalFieldError struct {
Key string
Type reflect.Type
Field reflect.StructField
}
func (e *UnmarshalFieldError) Error() string {
return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String()
}
// An InvalidUnmarshalError describes an invalid argument passed to [Unmarshal].
// (The argument to [Unmarshal] must be a non-nil pointer.)
type InvalidUnmarshalError struct {
Type reflect.Type
}
func (e *InvalidUnmarshalError) Error() string {
if e.Type == nil {
return "json: Unmarshal(nil)"
}
if e.Type.Kind() != reflect.Pointer {
return "json: Unmarshal(non-pointer " + e.Type.String() + ")"
}
return "json: Unmarshal(nil " + e.Type.String() + ")"
}
// A Number represents a JSON number literal.
type Number string
// String returns the literal text of the number.
func (n Number) String() string { return string(n) }
// Float64 returns the number as a float64.
func (n Number) Float64() (float64, error) {
return strconv.ParseFloat(string(n), 64)
}
// Int64 returns the number as an int64.
func (n Number) Int64() (int64, error) {
return strconv.ParseInt(string(n), 10, 64)
}
var numberType = reflect.TypeFor[Number]()
// MarshalJSONTo implements [jsonv2.MarshalerTo].
func (n Number) MarshalJSONTo(enc *jsontext.Encoder) error {
opts := enc.Options()
stringify, _ := jsonv2.GetOption(opts, jsonv2.StringifyNumbers)
if k, n := enc.StackIndex(enc.StackDepth()); k == '{' && n%2 == 0 {
stringify = true // expecting a JSON object name
}
n = cmp.Or(n, "0")
var num []byte
val := enc.AvailableBuffer()
if stringify {
val = append(val, '"')
val = append(val, n...)
val = append(val, '"')
num = val[len(`"`) : len(val)-len(`"`)]
} else {
val = append(val, n...)
num = val
}
if n, err := jsonwire.ConsumeNumber(num); n != len(num) || err != nil {
return fmt.Errorf("cannot parse %q as JSON number: %w", val, strconv.ErrSyntax)
}
return enc.WriteValue(val)
}
// UnmarshalJSONFrom implements [jsonv2.UnmarshalerFrom].
func (n *Number) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
opts := dec.Options()
stringify, _ := jsonv2.GetOption(opts, jsonv2.StringifyNumbers)
if k, n := dec.StackIndex(dec.StackDepth()); k == '{' && n%2 == 0 {
stringify = true // expecting a JSON object name
}
val, err := dec.ReadValue()
if err != nil {
return err
}
val0 := val
k := val.Kind()
switch k {
case 'n':
if legacy, _ := jsonv2.GetOption(opts, MergeWithLegacySemantics); !legacy {
*n = ""
}
return nil
case '"':
verbatim := jsonwire.ConsumeSimpleString(val) == len(val)
val = jsonwire.UnquoteMayCopy(val, verbatim)
if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil {
return &jsonv2.SemanticError{JSONKind: val0.Kind(), JSONValue: val0.Clone(), GoType: numberType, Err: strconv.ErrSyntax}
}
*n = Number(val)
return nil
case '0':
if stringify {
break
}
*n = Number(val)
return nil
}
return &jsonv2.SemanticError{JSONKind: k, GoType: numberType}
}
+262
View File
@@ -0,0 +1,262 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
// Package json implements encoding and decoding of JSON as defined in
// RFC 7159. The mapping between JSON and Go values is described
// in the documentation for the Marshal and Unmarshal functions.
//
// See [JSON and Go] for an introduction to this package.
//
// # Security Considerations
//
// See the "Security Considerations" section in [encoding/json/v2].
//
// For historical reasons, the default behavior of v1 [encoding/json]
// unfortunately operates with less secure defaults.
// New usages of JSON in Go are encouraged to use [encoding/json/v2] instead.
//
// [JSON and Go]: https://go.dev/blog/json
package json
import (
"reflect"
"strconv"
jsonv2 "github.com/go-json-experiment/json"
)
// Marshal returns the JSON encoding of v.
//
// Marshal traverses the value v recursively.
//
// The input value is encoded as JSON according the following rules:
//
// - If the value type implements [jsonv2.MarshalerTo],
// then the MarshalJSONTo method is called to encode the value.
// If the method returns [errors.ErrUnsupported],
// then the input is encoded according to subsequent rules.
//
// - 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.
//
// Otherwise, Marshal uses the following type-dependent default encodings:
//
// Boolean values encode as JSON booleans.
//
// Floating point, integer, and [Number] values encode as JSON numbers.
// NaN and +/-Inf values will return an [UnsupportedValueError].
//
// String values encode as JSON strings coerced to valid UTF-8,
// replacing invalid bytes with the Unicode replacement rune.
// So that the JSON will be safe to embed inside HTML <script> tags,
// the string is encoded using [HTMLEscape],
// which replaces "<", ">", "&", U+2028, and U+2029 are escaped
// to "\u003c","\u003e", "\u0026", "\u2028", and "\u2029".
// This replacement can be disabled when using an [Encoder],
// by calling [Encoder.SetEscapeHTML](false).
//
// Array and slice values encode as JSON arrays, except that
// []byte encodes as a base64-encoded string, and a nil slice
// encodes as the null JSON value.
//
// Struct values encode as JSON objects.
// Each exported struct field becomes a member of the object, using the
// field name as the object key, unless the field is omitted for one of the
// reasons given below.
//
// The encoding of each struct field can be customized by the format string
// stored under the "json" key in the struct field's tag.
// The format string gives the name of the field, possibly followed by a
// comma-separated list of options. The name may be empty in order to
// specify options without overriding the default field name.
//
// The "omitempty" option specifies that the field should be omitted
// from the encoding if the field has an empty value, defined as
// false, 0, a nil pointer, a nil interface value, and any array,
// slice, map, or string of length zero.
//
// As a special case, if the field tag is "-", the field is always omitted.
// JSON names containing commas or quotes, or names identical to "" or "-",
// can be specified using a single-quoted string literal, where the syntax
// is identical to the Go grammar for a double-quoted string literal,
// but instead uses single quotes as the delimiters.
//
// Examples of struct field tags and their meanings:
//
// // Field appears in JSON as key "myName".
// Field int `json:"myName"`
//
// // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty,
// // as defined above.
// Field int `json:"myName,omitempty"`
//
// // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty.
// // Note the leading comma.
// Field int `json:",omitempty"`
//
// // Field is ignored by this package.
// Field int `json:"-"`
//
// // Field appears in JSON as key "-".
// Field int `json:"'-'"`
//
// The "omitzero" option specifies that the field should be omitted
// from the encoding if the field has a zero value, according to rules:
//
// 1) If the field type has an "IsZero() bool" method, that will be used to
// determine whether the value is zero.
//
// 2) Otherwise, the value is zero if it is the zero value for its type.
//
// If both "omitempty" and "omitzero" are specified, the field will be omitted
// if the value is either empty or zero (or both).
//
// The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs:
//
// Int64String int64 `json:",string"`
//
// The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, and ASCII punctuation except quotation
// marks, backslash, and comma.
//
// Embedded struct fields are usually marshaled as if their inner exported fields
// were fields in the outer struct, subject to the usual Go visibility rules amended
// as described in the next paragraph.
// An anonymous struct field with a name given in its JSON tag is treated as
// having that name, rather than being anonymous.
// An anonymous struct field of interface type is treated the same as having
// that type as its name, rather than being anonymous.
//
// The Go visibility rules for struct fields are amended for JSON when
// deciding which field to marshal or unmarshal. If there are
// multiple fields at the same level, and that level is the least
// nested (and would therefore be the nesting level selected by the
// usual Go rules), the following extra rules apply:
//
// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered,
// even if there are multiple untagged fields that would otherwise conflict.
//
// 2) If there is exactly one field (tagged or not according to the first rule), that is selected.
//
// 3) Otherwise there are multiple fields, and all are ignored; no error occurs.
//
// Handling of anonymous struct fields is new in Go 1.1.
// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of
// an anonymous struct field in both current and earlier versions, give the field
// a JSON tag of "-".
//
// Map values encode as JSON objects. The map's key type must either be a
// string, an integer type, or implement [encoding.TextMarshaler]. The map keys
// are sorted and used as JSON object keys by applying the following rules,
// subject to the UTF-8 coercion described for string values above:
// - keys of any string type are used directly
// - keys that implement [encoding.TextMarshaler] are marshaled
// - integer keys are converted to strings
//
// Pointer values encode as the value pointed to.
// A nil pointer encodes as the null JSON value.
//
// Interface values encode as the value contained in the interface.
// A nil interface value encodes as the null JSON value.
//
// Channel, complex, and function values cannot be encoded in JSON.
// Attempting to encode such a value causes Marshal to return
// an [UnsupportedTypeError].
//
// JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in
// an error.
func Marshal(v any) ([]byte, error) {
return jsonv2.Marshal(v, DefaultOptionsV1())
}
// MarshalIndent is like [Marshal] but applies [Indent] to format the output.
// Each JSON element in the output will begin on a new line beginning with prefix
// followed by one or more copies of indent according to the indentation nesting.
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
b, err := Marshal(v)
if err != nil {
return nil, err
}
b, err = appendIndent(nil, b, prefix, indent)
if err != nil {
return nil, err
}
return b, nil
}
// Marshaler is the interface implemented by types that
// can marshal themselves into valid JSON.
type Marshaler = jsonv2.Marshaler
// An UnsupportedTypeError is returned by [Marshal] when attempting
// to encode an unsupported value type.
type UnsupportedTypeError struct {
Type reflect.Type
}
func (e *UnsupportedTypeError) Error() string {
return "json: unsupported type: " + e.Type.String()
}
// An UnsupportedValueError is returned by [Marshal] when attempting
// to encode an unsupported value.
type UnsupportedValueError struct {
Value reflect.Value
Str string
}
func (e *UnsupportedValueError) Error() string {
return "json: unsupported value: " + e.Str
}
// Before Go 1.2, an InvalidUTF8Error was returned by [Marshal] when
// attempting to encode a string value with invalid UTF-8 sequences.
// As of Go 1.2, [Marshal] instead coerces the string to valid UTF-8 by
// replacing invalid bytes with the Unicode replacement rune U+FFFD.
//
// Deprecated: No longer used; kept for compatibility.
type InvalidUTF8Error struct {
S string // the whole string value that caused the error
}
func (e *InvalidUTF8Error) Error() string {
return "json: invalid UTF-8 in string: " + strconv.Quote(e.S)
}
// A MarshalerError represents an error from calling a
// [Marshaler.MarshalJSON] or [encoding.TextMarshaler.MarshalText] method.
type MarshalerError struct {
Type reflect.Type
Err error
sourceFunc string
}
func (e *MarshalerError) Error() string {
srcFunc := e.sourceFunc
if srcFunc == "" {
srcFunc = "MarshalJSON"
}
return "json: error calling " + srcFunc +
" for type " + e.Type.String() +
": " + e.Err.Error()
}
// Unwrap returns the underlying error.
func (e *MarshalerError) Unwrap() error { return e.Err }
+131
View File
@@ -0,0 +1,131 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
package json
import (
"bytes"
"strings"
"github.com/go-json-experiment/json/jsontext"
)
// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029
// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029
// so that the JSON will be safe to embed inside HTML <script> tags.
// For historical reasons, web browsers don't honor standard HTML
// escaping within <script> tags, so an alternative JSON encoding must be used.
func HTMLEscape(dst *bytes.Buffer, src []byte) {
dst.Grow(len(src))
dst.Write(appendHTMLEscape(dst.AvailableBuffer(), src))
}
func appendHTMLEscape(dst, src []byte) []byte {
const hex = "0123456789abcdef"
// The characters can only appear in string literals,
// so just scan the string one byte at a time.
start := 0
for i, c := range src {
if c == '<' || c == '>' || c == '&' {
dst = append(dst, src[start:i]...)
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
start = i + 1
}
// Convert U+2028 and U+2029 (E2 80 A8 and E2 80 A9).
if c == 0xE2 && i+2 < len(src) && src[i+1] == 0x80 && src[i+2]&^1 == 0xA8 {
dst = append(dst, src[start:i]...)
dst = append(dst, '\\', 'u', '2', '0', '2', hex[src[i+2]&0xF])
start = i + len("\u2029")
}
}
return append(dst, src[start:]...)
}
// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
dst.Grow(len(src))
b := dst.AvailableBuffer()
b, err := jsontext.AppendFormat(b, src,
ReportErrorsWithLegacySemantics(true),
jsontext.AllowDuplicateNames(true),
jsontext.AllowInvalidUTF8(true),
jsontext.PreserveRawStrings(true))
if err != nil {
return transformSyntacticError(err)
}
dst.Write(b)
return nil
}
// indentGrowthFactor specifies the growth factor of indenting JSON input.
// Empirically, the growth factor was measured to be between 1.4x to 1.8x
// for some set of compacted JSON with the indent being a single tab.
// Specify a growth factor slightly larger than what is observed
// to reduce probability of allocation in appendIndent.
// A factor no higher than 2 ensures that wasted space never exceeds 50%.
const indentGrowthFactor = 2
// Indent appends to dst an indented form of the JSON-encoded src.
// 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 indentation nesting.
// The data appended to dst does not begin with the prefix nor
// any indentation, to make it easier to embed inside other formatted JSON data.
// Although leading space characters (space, tab, carriage return, newline)
// at the beginning of src are dropped, trailing space characters
// at the end of src are preserved and copied to dst.
// For example, if src has no trailing spaces, neither will dst;
// if src ends in a trailing newline, so will dst.
func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error {
dst.Grow(indentGrowthFactor * len(src))
b := dst.AvailableBuffer()
b, err := appendIndent(b, src, prefix, indent)
dst.Write(b)
return err
}
func appendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
// In v2, only spaces and tabs are allowed, while v1 allowed any character.
dstLen := len(dst)
if len(strings.Trim(prefix, " \t"))+len(strings.Trim(indent, " \t")) > 0 {
// Use placeholder spaces of correct length, and replace afterwards.
invalidPrefix, invalidIndent := prefix, indent
prefix = strings.Repeat(" ", len(prefix))
indent = strings.Repeat(" ", len(indent))
defer func() {
b := dst[dstLen:]
for i := bytes.IndexByte(b, '\n'); i >= 0; i = bytes.IndexByte(b, '\n') {
b = b[i+len("\n"):]
n := len(b) - len(bytes.TrimLeft(b, " ")) // len(prefix)+n*len(indent)
spaces := b[:n]
spaces = spaces[copy(spaces, invalidPrefix):]
for len(spaces) > 0 {
spaces = spaces[copy(spaces, invalidIndent):]
}
b = b[n:]
}
}()
}
dst, err := jsontext.AppendFormat(dst, src,
ReportErrorsWithLegacySemantics(true),
jsontext.AllowDuplicateNames(true),
jsontext.AllowInvalidUTF8(true),
jsontext.PreserveRawStrings(true),
jsontext.Multiline(true),
jsontext.WithIndentPrefix(prefix),
jsontext.WithIndent(indent))
if err != nil {
return dst[:dstLen], transformSyntacticError(err)
}
// In v2, trailing whitespace is discarded, while v1 preserved it.
if n := len(src) - len(bytes.TrimRight(src, " \n\r\t")); n > 0 {
dst = append(dst, src[len(src)-n:]...)
}
return dst, nil
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
package json
import (
"fmt"
"reflect"
"strconv"
"strings"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/jsontext"
)
// Inject functionality into v2 to properly handle v1 types.
func init() {
internal.TransformMarshalError = transformMarshalError
internal.TransformUnmarshalError = transformUnmarshalError
internal.NewMarshalerError = func(val any, err error, funcName string) error {
return &MarshalerError{reflect.TypeOf(val), err, funcName}
}
internal.NewRawNumber = func() any { return new(Number) }
internal.RawNumberOf = func(b []byte) any { return Number(b) }
}
func transformMarshalError(root any, err error) error {
// Historically, errors returned from Marshal methods were wrapped
// in a [MarshalerError]. This is directly performed by the v2 package
// via the injected [internal.NewMarshalerError] constructor
// while operating under [ReportErrorsWithLegacySemantics].
// Note that errors from a Marshal method were always wrapped,
// even if wrapped for multiple layers.
if err, ok := err.(*jsonv2.SemanticError); err != nil {
if err.Err == nil {
// Historically, this was only reported for unserializable types
// like complex numbers, channels, functions, and unsafe.Pointers.
return &UnsupportedTypeError{Type: err.GoType}
} else {
// Historically, this was only reported for NaN or ±Inf values
// and cycles detected in the value.
// The Value field used to be populated with the reflect.Value,
// but this is no longer supported.
errStr := err.Err.Error()
if err.Err == internal.ErrCycle && err.GoType != nil {
errStr += " via " + err.GoType.String()
}
errStr = strings.TrimPrefix(errStr, "unsupported value: ")
return &UnsupportedValueError{Str: errStr}
}
} else if ok {
return (*UnsupportedValueError)(nil)
}
if err, _ := err.(*MarshalerError); err != nil {
err.Err = transformSyntacticError(err.Err)
return err
}
return transformSyntacticError(err)
}
func transformUnmarshalError(root any, err error) error {
// Historically, errors from Unmarshal methods were never wrapped and
// returned verbatim while operating under [ReportErrorsWithLegacySemantics].
if err, ok := err.(*jsonv2.SemanticError); err != nil {
if err.Err == internal.ErrNonNilReference {
return &InvalidUnmarshalError{err.GoType}
}
if err.Err == jsonv2.ErrUnknownName {
return fmt.Errorf("json: unknown field %q", err.JSONPointer.LastToken())
}
if err.Err == internal.ErrNilInterface {
err.Err = nil // non-descriptive for historical reasons
}
// Historically, UnmarshalTypeError has always been inconsistent
// about how it reported position information.
//
// The Struct field now points to the root type,
// rather than some intermediate struct in the path.
// This better matches the original intent of the field based
// on how the Error message was formatted.
//
// For a representation closer to the historical representation,
// we switch the '/'-delimited representation of a JSON pointer
// to use a '.'-delimited representation. This may be ambiguous,
// but the prior representation was always ambiguous as well.
// Users that care about precise positions should use v2 errors
// by disabling [ReportErrorsWithLegacySemantics].
//
// The introduction of an Err field is new to the v1-to-v2 migration
// and allows us to preserve stronger error information
// that may be surfaced by the v2 package.
//
// See https://go.dev/issue/43126
var value string
switch err.JSONKind {
case 'n', '"', '0':
value = err.JSONKind.String()
case 'f', 't':
value = "bool"
case '[', ']':
value = "array"
case '{', '}':
value = "object"
}
if len(err.JSONValue) > 0 {
isStrconvError := err.Err == strconv.ErrRange || err.Err == strconv.ErrSyntax
isNumericKind := func(t reflect.Type) bool {
if t == nil {
return false
}
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64:
return true
}
return false
}
if isStrconvError && isNumericKind(err.GoType) {
value = "number"
if err.JSONKind == '"' {
err.JSONValue, _ = jsontext.AppendUnquote(nil, err.JSONValue)
}
err.Err = nil
}
value += " " + string(err.JSONValue)
}
var rootName string
if t := reflect.TypeOf(root); t != nil && err.JSONPointer != "" {
if t.Kind() == reflect.Pointer {
t = t.Elem()
}
rootName = t.Name()
}
fieldPath := string(err.JSONPointer)
fieldPath = strings.TrimPrefix(fieldPath, "/")
fieldPath = strings.ReplaceAll(fieldPath, "/", ".")
return &UnmarshalTypeError{
Value: value,
Type: err.GoType,
Offset: err.ByteOffset,
Struct: rootName,
Field: fieldPath,
Err: transformSyntacticError(err.Err),
}
} else if ok {
return (*UnmarshalTypeError)(nil)
}
return transformSyntacticError(err)
}
+553
View File
@@ -0,0 +1,553 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
// Migrating to v2
//
// This package (i.e., [encoding/json]) is now formally known as the v1 package
// since a v2 package now exists at [encoding/json/v2].
// All the behavior of the v1 package is implemented in terms of
// the v2 package with the appropriate set of options specified that
// preserve the historical behavior of v1.
//
// The [jsonv2.Marshal] function is the newer equivalent of v1 [Marshal].
// The [jsonv2.Unmarshal] function is the newer equivalent of v1 [Unmarshal].
// The v2 functions have the same calling signature as the v1 equivalent
// except that they take in variadic [Options] arguments that can be specified
// to alter the behavior of marshal or unmarshal. Both v1 and v2 generally
// behave in similar ways, but there are some notable differences.
//
// The following is a list of differences between v1 and v2:
//
// - In v1, JSON object members are unmarshaled into a Go struct using a
// case-insensitive name match with the JSON name of the fields.
// In contrast, v2 matches fields using an exact, case-sensitive match.
// The [jsonv2.MatchCaseInsensitiveNames] and [MatchCaseSensitiveDelimiter]
// options control this behavior difference. To explicitly specify a Go struct
// field to use a particular name matching scheme, either the `case:ignore`
// or the `case:strict` field option can be specified.
// Field-specified options take precedence over caller-specified options.
//
// - In v1, when marshaling a Go struct, a 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. In contrast, v2 redefines
// `omitempty` to omit a field if it encodes as an "empty" JSON value,
// which is defined as a JSON null, or an empty JSON string, object, or array.
// The [OmitEmptyWithLegacySemantics] option controls this behavior difference.
// Note that `omitempty` behaves identically in both v1 and v2 for a
// Go array, slice, map, or string (assuming no user-defined MarshalJSON method
// overrides the default representation). Existing usages of `omitempty` on a
// Go bool, number, pointer, or interface value should migrate to specifying
// `omitzero` instead (which is identically supported in both v1 and v2).
//
// - In v1, a Go struct field marked as `string` can be used to quote a
// Go string, bool, number, or pointer to such as a JSON string.
// In contrast, v2 restricts the `string` option to only quote a value
// that would normally be represented as a JSON number,
// but also expands support for it to operate with any Go type
// that would normally be represented as a JSON number.
// The [StringifyWithLegacySemantics] option controls this behavior difference.
//
// - In v1, a nil Go slice or Go map is marshaled as a JSON null.
// In contrast, v2 marshals a nil Go slice or Go map as
// an empty JSON array or JSON object, respectively.
// The [jsonv2.FormatNilSliceAsNull] and [jsonv2.FormatNilMapAsNull] options
// control this behavior difference.
//
// - In v1, a Go array may be unmarshaled from a JSON array of any length.
// In contrast, in v2 a Go array must be unmarshaled from a JSON array
// of the same length, otherwise it results in an error.
// The [UnmarshalArrayFromAnyLength] option controls this behavior difference.
//
// - In v1, a Go byte array is represented as a JSON array of JSON numbers.
// In contrast, in v2 a Go byte array is represented as a Base64-encoded JSON string.
// The [FormatByteArrayAsArray] option controls this behavior difference.
//
// - In v1, MarshalJSON methods declared on a pointer receiver are only called
// if the Go value is addressable. In contrast, in v2 a MarshalJSON method
// is always callable regardless of addressability.
// The [CallMethodsWithLegacySemantics] option controls this behavior difference.
//
// - In v1, MarshalJSON and UnmarshalJSON methods are never called for Go map keys.
// In contrast, in v2 a MarshalJSON or UnmarshalJSON method is eligible for
// being called for Go map keys.
// The [CallMethodsWithLegacySemantics] option controls this behavior difference.
//
// - In v1, a Go map is marshaled in a deterministic order.
// In contrast, in v2 a Go map is marshaled in a non-deterministic order.
// The [jsonv2.Deterministic] option controls this behavior difference.
//
// - In v1, JSON strings are encoded with HTML-specific or JavaScript-specific
// characters being escaped. In contrast, in v2 JSON strings use the minimal
// encoding and only escape if required by the JSON grammar.
// The [jsontext.EscapeForHTML] and [jsontext.EscapeForJS] options
// control this behavior difference.
//
// - In v1, bytes of invalid UTF-8 within a string are silently replaced with
// the Unicode replacement character. In contrast, in v2 the presence of
// invalid UTF-8 results in an error. The [jsontext.AllowInvalidUTF8] option
// controls this behavior difference.
//
// - In v1, a JSON object with duplicate names is permitted.
// In contrast, in v2 a JSON object with duplicate names results in an error.
// The [jsontext.AllowDuplicateNames] option controls this behavior difference.
//
// - In v1, when unmarshaling a JSON null into a non-empty Go value it will
// inconsistently either zero out the value or do nothing.
// In contrast, in v2 unmarshaling a JSON null will consistently and always
// zero out the underlying Go value. The [MergeWithLegacySemantics] option
// controls this behavior difference.
//
// - In v1, when unmarshaling a JSON value into a non-zero Go value,
// it merges into the original Go value for array elements, slice elements,
// struct fields (but not map values),
// pointer values, and interface values (only if a non-nil pointer).
// In contrast, in v2 unmarshal merges into the Go value
// for struct fields, map values, pointer values, and interface values.
// In general, the v2 semantic merges when unmarshaling a JSON object,
// otherwise it replaces the value. The [MergeWithLegacySemantics] option
// controls this behavior difference.
//
// - In v1, a [time.Duration] is represented as a JSON number containing
// the decimal number of nanoseconds. In contrast, in v2 a [time.Duration]
// has no default representation and results in a runtime error.
// The [FormatDurationAsNano] option controls this behavior difference.
//
// - In v1, errors are never reported at runtime for Go struct types
// that have some form of structural error (e.g., a malformed tag option).
// In contrast, v2 reports a runtime error for Go types that are invalid
// as they relate to JSON serialization. For example, a Go struct
// with only unexported fields cannot be serialized.
// The [ReportErrorsWithLegacySemantics] option controls this behavior difference.
//
// As mentioned, the entirety of v1 is implemented in terms of v2,
// where options are implicitly specified to opt into legacy behavior.
// For example, [Marshal] directly calls [jsonv2.Marshal] with [DefaultOptionsV1].
// Similarly, [Unmarshal] directly calls [jsonv2.Unmarshal] with [DefaultOptionsV1].
// The [DefaultOptionsV1] option represents the set of all options that specify
// default v1 behavior.
//
// For many of the behavior differences, there are Go struct field options
// that the author of a Go type can specify to control the behavior such that
// the type is represented identically in JSON under either v1 or v2 semantics.
//
// The availability of [DefaultOptionsV1] and [jsonv2.DefaultOptionsV2],
// where later options take precedence over former options allows for
// a gradual migration from v1 to v2. For example:
//
// - jsonv1.Marshal(v)
// uses default v1 semantics.
//
// - jsonv2.Marshal(v, jsonv1.DefaultOptionsV1())
// is semantically equivalent to jsonv1.Marshal
// and thus uses default v1 semantics.
//
// - jsonv2.Marshal(v, jsonv1.DefaultOptionsV1(), jsontext.AllowDuplicateNames(false))
// uses mostly v1 semantics, but opts into one particular v2-specific behavior.
//
// - jsonv2.Marshal(v, jsonv1.CallMethodsWithLegacySemantics(true))
// uses mostly v2 semantics, but opts into one particular v1-specific behavior.
//
// - jsonv2.Marshal(v, ..., jsonv2.DefaultOptionsV2())
// is semantically equivalent to jsonv2.Marshal since
// jsonv2.DefaultOptionsV2 overrides any options specified earlier
// and thus uses default v2 semantics.
//
// - jsonv2.Marshal(v)
// uses default v2 semantics.
//
// All new usages of "json" in Go should use the v2 package,
// but the v1 package will forever remain supported.
//
// See the [encoding/json/v2 Migration Guide] for additional detail on migration approaches.
//
// [encoding/json/v2 Migration Guide]: https://go.dev/doc/jsonv2-migration
package json
// TODO(https://go.dev/issue/71631): Update the "Migrating to v2" documentation
// with default v2 behavior for [time.Duration].
import (
"encoding"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/internal/jsonopts"
"github.com/go-json-experiment/json/jsontext"
)
// Reference encoding, jsonv2, and jsontext packages to assist pkgsite
// in being able to hotlink references to those packages.
var (
_ encoding.TextMarshaler
_ encoding.TextUnmarshaler
_ jsonv2.Options
_ jsontext.Options
)
// Options are a set of options to configure the v2 "json" package
// to operate with v1 semantics for particular features.
// Values of this type can be passed to v2 functions like
// [jsonv2.Marshal] or [jsonv2.Unmarshal].
// Instead of referencing this type, use [jsonv2.Options].
//
// See the "Migrating to v2" section for guidance on how to migrate usage
// of "json" from using v1 to using v2 instead.
type Options = jsonopts.Options
// DefaultOptionsV1 is the full set of all options that define v1 semantics.
// It is equivalent to the following boolean options being set to true:
//
// - [CallMethodsWithLegacySemantics]
// - [FormatByteArrayAsArray]
// - [FormatBytesWithLegacySemantics]
// - [FormatDurationAsNano]
// - [MatchCaseSensitiveDelimiter]
// - [MergeWithLegacySemantics]
// - [OmitEmptyWithLegacySemantics]
// - [ParseBytesWithLooseRFC4648]
// - [ParseTimeWithLooseRFC3339]
// - [ReportErrorsWithLegacySemantics]
// - [StringifyWithLegacySemantics]
// - [UnmarshalArrayFromAnyLength]
// - [jsonv2.Deterministic]
// - [jsonv2.FormatNilMapAsNull]
// - [jsonv2.FormatNilSliceAsNull]
// - [jsonv2.MatchCaseInsensitiveNames]
// - [jsontext.AllowDuplicateNames]
// - [jsontext.AllowInvalidUTF8]
// - [jsontext.EscapeForHTML]
// - [jsontext.EscapeForJS]
// - [jsontext.PreserveRawStrings]
//
// All other options are not present.
//
// The [Marshal] and [Unmarshal] functions in this package are
// semantically identical to calling the v2 equivalents with this option:
//
// jsonv2.Marshal(v, jsonv1.DefaultOptionsV1())
// jsonv2.Unmarshal(b, v, jsonv1.DefaultOptionsV1())
func DefaultOptionsV1() Options {
return &jsonopts.DefaultOptionsV1
}
// CallMethodsWithLegacySemantics specifies that calling of type-provided
// marshal and unmarshal methods follow legacy semantics:
//
// - When marshaling, a marshal method declared on a pointer receiver
// is only called if the Go value is addressable.
// Values obtained from an interface or map element are not addressable.
// Values obtained from a pointer or slice element are addressable.
// Values obtained from an array element or struct field inherit
// the addressability of the parent. In contrast, the v2 semantic
// is to always call marshal methods regardless of addressability.
//
// - When marshaling or unmarshaling, the [Marshaler] or [Unmarshaler]
// methods are ignored for map keys. However, [encoding.TextMarshaler]
// or [encoding.TextUnmarshaler] are still callable.
// In contrast, the v2 semantic is to serialize map keys
// like any other value (with regard to calling methods),
// which may include calling [Marshaler] or [Unmarshaler] methods,
// where it is the implementation's responsibility to represent the
// Go value as a JSON string (as required for JSON object names).
//
// - When marshaling, if a map key value implements a marshal method
// and is a nil pointer, then it is serialized as an empty JSON string.
// In contrast, the v2 semantic is to report an error.
//
// - When marshaling, if an interface type implements a marshal method
// and the interface value is a nil pointer to a concrete type,
// then the marshal method is always called.
// In contrast, the v2 semantic is to never directly call methods
// on interface values and to instead defer evaluation based upon
// the underlying concrete value. Similar to non-interface values,
// marshal methods are not called on nil pointers and
// are instead serialized as a JSON null.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func CallMethodsWithLegacySemantics(v bool) Options {
if v {
return jsonflags.CallMethodsWithLegacySemantics | 1
} else {
return jsonflags.CallMethodsWithLegacySemantics | 0
}
}
// FormatByteArrayAsArray specifies that a Go [N]byte is
// formatted as a normal Go array in contrast to the v2 default of
// formatting [N]byte as using binary data encoding (RFC 4648).
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func FormatByteArrayAsArray(v bool) Options {
if v {
return jsonflags.FormatByteArrayAsArray | 1
} else {
return jsonflags.FormatByteArrayAsArray | 0
}
}
// FormatBytesWithLegacySemantics specifies that handling of
// []~byte and [N]~byte types follow legacy semantics:
//
// - A Go []~byte is to be treated as using some form of
// binary data encoding (RFC 4648) in contrast to the v2 default
// of only treating []byte as such. In particular, v2 does not
// treat slices of named byte types as representing binary data.
//
// - When marshaling, if a named byte implements a marshal method,
// then the slice is serialized as a JSON array of elements,
// each of which call the marshal method.
//
// - When unmarshaling, if the input is a JSON array,
// then unmarshal into the []~byte as if it were a normal Go slice.
// In contrast, the v2 default is to report an error unmarshaling
// a JSON array when expecting some form of binary data encoding.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func FormatBytesWithLegacySemantics(v bool) Options {
if v {
return jsonflags.FormatBytesWithLegacySemantics | 1
} else {
return jsonflags.FormatBytesWithLegacySemantics | 0
}
}
// FormatDurationAsNano specifies that a [time.Duration] is
// formatted as a JSON number representing the number of nanoseconds
// in contrast to the v2 default of reporting an error.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func FormatDurationAsNano(v bool) Options {
// TODO(https://go.dev/issue/71631): Update documentation with v2 behavior.
if v {
return jsonflags.FormatDurationAsNano | 1
} else {
return jsonflags.FormatDurationAsNano | 0
}
}
// MatchCaseSensitiveDelimiter specifies that underscores and dashes are
// not to be ignored when performing case-insensitive name matching which
// occurs under [jsonv2.MatchCaseInsensitiveNames] or the `case:ignore` tag option.
// Thus, case-insensitive name matching is identical to [strings.EqualFold].
// Use of this option diminishes the ability of case-insensitive matching
// to be able to match common case variants (e.g., "foo_bar" with "fooBar").
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func MatchCaseSensitiveDelimiter(v bool) Options {
if v {
return jsonflags.MatchCaseSensitiveDelimiter | 1
} else {
return jsonflags.MatchCaseSensitiveDelimiter | 0
}
}
// MergeWithLegacySemantics specifies that unmarshaling into a non-zero
// Go value follows legacy semantics:
//
// - When unmarshaling a JSON null, this preserves the original Go value
// if the kind is a bool, int, uint, float, string, array, or struct.
// Otherwise, it zeros the Go value.
// In contrast, the default v2 behavior is to consistently and always
// zero the Go value when unmarshaling a JSON null into it.
//
// - When unmarshaling a JSON value other than null, this merges into
// the original Go value for array elements, slice elements,
// struct fields (but not map values),
// pointer values, and interface values (only if a non-nil pointer).
// For slices, it will merge into the pre-existing value of slice elements
// even for those past the slice length. If the original slice length
// was longer than the JSON array, then it is truncated to match.
// In contrast, the default v2 behavior is to merge into the Go value
// for struct fields, map values, pointer values, and interface values.
// In general, the v2 semantic merges when unmarshaling a JSON object,
// otherwise it replaces the original value.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func MergeWithLegacySemantics(v bool) Options {
if v {
return jsonflags.MergeWithLegacySemantics | 1
} else {
return jsonflags.MergeWithLegacySemantics | 0
}
}
// OmitEmptyWithLegacySemantics specifies that the `omitempty` tag option
// follows a definition of empty where a field is omitted if the Go value is
// false, 0, a nil pointer, a nil interface value,
// or any empty array, slice, map, or string.
// This overrides the v2 semantic where a field is empty if the value
// marshals as a JSON null or an empty JSON string, object, or array.
//
// The v1 and v2 definitions of `omitempty` are practically the same for
// Go strings, slices, arrays, and maps. Usages of `omitempty` on
// Go bools, ints, uints, floats, pointers, and interfaces should migrate to use
// the `omitzero` tag option, which omits a field if it is the zero Go value.
//
// This only affects marshaling and is ignored when unmarshaling.
// The v1 default is true.
func OmitEmptyWithLegacySemantics(v bool) Options {
if v {
return jsonflags.OmitEmptyWithLegacySemantics | 1
} else {
return jsonflags.OmitEmptyWithLegacySemantics | 0
}
}
// ParseBytesWithLooseRFC4648 specifies that when parsing
// binary data encoded as "base32" or "base64",
// to ignore the presence of '\r' and '\n' characters.
// In contrast, the v2 default is to report an error in order to be
// strictly compliant with RFC 4648, section 3.3,
// which specifies that non-alphabet characters must be rejected.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func ParseBytesWithLooseRFC4648(v bool) Options {
if v {
return jsonflags.ParseBytesWithLooseRFC4648 | 1
} else {
return jsonflags.ParseBytesWithLooseRFC4648 | 0
}
}
// ParseTimeWithLooseRFC3339 specifies that a [time.Time]
// parses according to loose adherence to RFC 3339.
// In particular, it permits historically incorrect representations,
// allowing for deviations in hour format, sub-second separator,
// and timezone representation. In contrast, the default v2 behavior
// is to strictly comply with the grammar specified in RFC 3339.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func ParseTimeWithLooseRFC3339(v bool) Options {
if v {
return jsonflags.ParseTimeWithLooseRFC3339 | 1
} else {
return jsonflags.ParseTimeWithLooseRFC3339 | 0
}
}
// ReportErrorsWithLegacySemantics specifies that Marshal and Unmarshal
// should report errors with legacy semantics:
//
// - When marshaling or unmarshaling, the returned error values are
// usually of types such as [SyntaxError], [MarshalerError],
// [UnsupportedTypeError], [UnsupportedValueError],
// [InvalidUnmarshalError], or [UnmarshalTypeError].
// In contrast, the v2 semantic is to always return errors as either
// [jsonv2.SemanticError] or [jsontext.SyntacticError].
//
// - When marshaling, if a user-defined marshal method reports an error,
// it is always wrapped in a [MarshalerError], even if the error itself
// is already a [MarshalerError], which may lead to multiple redundant
// layers of wrapping. In contrast, the v2 semantic is to
// always wrap an error within [jsonv2.SemanticError]
// unless it is already a semantic error.
//
// - When unmarshaling, if a user-defined unmarshal method reports an error,
// it is never wrapped and reported verbatim. In contrast, the v2 semantic
// is to always wrap an error within [jsonv2.SemanticError]
// unless it is already a semantic error.
//
// - When marshaling or unmarshaling, if a Go struct contains type errors
// (e.g., conflicting names or malformed field tags), then such errors
// are ignored and the Go struct uses a best-effort representation.
// In contrast, the v2 semantic is to report a runtime error.
//
// - When unmarshaling with [jsonv2.MatchCaseInsensitiveNames], if a JSON
// object name has a non-exact match with multiple Go struct fields, then
// an error is not reported and instead the first declared field is used.
// In contrast, the v2 semantic is to report a runtime error.
//
// - When unmarshaling, the syntactic structure of the JSON input
// is fully validated before performing the semantic unmarshaling
// of the JSON data into the Go value. Practically speaking,
// this means that JSON input with syntactic errors do not result
// in any mutations of the target Go value. In contrast, the v2 semantic
// is to perform a streaming decode and gradually unmarshal the JSON input
// into the target Go value, which means that the Go value may be
// partially mutated when a syntactic error is encountered.
//
// - When unmarshaling, a semantic error does not immediately terminate the
// unmarshal procedure, but rather evaluation continues.
// When unmarshal returns, only the first semantic error is reported.
// In contrast, the v2 semantic is to terminate unmarshal the moment
// an error is encountered.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func ReportErrorsWithLegacySemantics(v bool) Options {
if v {
return jsonflags.ReportErrorsWithLegacySemantics | 1
} else {
return jsonflags.ReportErrorsWithLegacySemantics | 0
}
}
// StringifyWithLegacySemantics specifies that the `string` tag option
// may stringify bools and string values. It only takes effect on fields
// where the top-level type is a bool, string, numeric kind, or a pointer to
// such a kind. In contrast, the v2 default only allows the `string` tag option
// on Go types that would have otherwise serialized as a JSON number,
// and stringifies the JSON number within a JSON string. In particular,
// the v2 default does not stringify Go bools and strings.
// If [ReportErrorsWithLegacySemantics] is false,
// then incorrect usages of `string` result in a runtime error.
//
// When marshaling, such Go values are serialized as their usual JSON
// representation, but quoted within a JSON string.
// When unmarshaling, such Go values must be deserialized from a JSON string
// containing their usual JSON representation or Go number representation for
// that numeric kind.
// Note that the Go number grammar is a superset of the JSON number grammar.
// A JSON null quoted in a JSON string is a valid substitute for JSON null
// while unmarshaling into a Go value that `string` takes effect on.
// In contrast, the v2 default rejects stringified numbers outside of the
// grammar for a JSON number and also rejects a JSON null quoted in a JSON string.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func StringifyWithLegacySemantics(v bool) Options {
if v {
return jsonflags.StringifyWithLegacySemantics | 1
} else {
return jsonflags.StringifyWithLegacySemantics | 0
}
}
// UnmarshalArrayFromAnyLength specifies that Go arrays can be unmarshaled
// from input JSON arrays of any length. If the JSON array is too short,
// then the remaining Go array elements are zeroed. If the JSON array
// is too long, then the excess JSON array elements are skipped over.
// In contrast, the v2 default expects that Go arrays be unmarshaled
// from input JSON arrays of the exact same length.
//
// This only affects unmarshaling and is ignored when marshaling.
// The v1 default is true.
func UnmarshalArrayFromAnyLength(v bool) Options {
if v {
return jsonflags.UnmarshalArrayFromAnyLength | 1
} else {
return jsonflags.UnmarshalArrayFromAnyLength | 0
}
}
// unmarshalAnyWithRawNumber specifies that unmarshaling a JSON number into
// an empty Go interface should use the Number type instead of a float64.
func unmarshalAnyWithRawNumber(v bool) Options {
if v {
return jsonflags.UnmarshalAnyWithRawNumber | 1
} else {
return jsonflags.UnmarshalAnyWithRawNumber | 0
}
}
+91
View File
@@ -0,0 +1,91 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
package json
import (
"errors"
"io"
"strings"
"github.com/go-json-experiment/json/internal"
"github.com/go-json-experiment/json/internal/jsonflags"
"github.com/go-json-experiment/json/jsontext"
)
// export exposes internal functionality of the "jsontext" package.
var export = jsontext.Internal.Export(&internal.AllowInternalUse)
// Valid reports whether data is a valid JSON encoding.
func Valid(data []byte) bool {
return checkValid(data) == nil
}
func checkValid(data []byte) error {
d := export.GetBufferedDecoder(data)
defer export.PutBufferedDecoder(d)
xd := export.Decoder(d)
xd.Struct.Flags.Set(jsonflags.ReportErrorsWithLegacySemantics | jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1)
if _, err := d.ReadValue(); err != nil {
if err == io.EOF {
offset := d.InputOffset() + int64(len(d.UnreadBuffer()))
err = &jsontext.SyntacticError{ByteOffset: offset, Err: io.ErrUnexpectedEOF}
}
return transformSyntacticError(err)
}
if err := xd.CheckEOF(); err != nil {
return transformSyntacticError(err)
}
return nil
}
// A SyntaxError is a description of a JSON syntax error.
// [Unmarshal] will return a SyntaxError if the JSON can't be parsed.
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
var errUnexpectedEnd = errors.New("unexpected end of JSON input")
func transformSyntacticError(err error) error {
switch serr, ok := err.(*jsontext.SyntacticError); {
case serr != nil:
// If the SyntacticError wraps an IO error, unwrap it
// to match v1 behavior which returned IO errors directly.
if export.IsIOError(serr.Err) {
return errors.Unwrap(serr.Err)
}
if serr.Err == io.ErrUnexpectedEOF {
serr.Err = errUnexpectedEnd
}
msg := serr.Err.Error()
if i := strings.Index(msg, " (expecting"); i >= 0 && !strings.Contains(msg, " in literal") {
msg = msg[:i]
}
return &SyntaxError{Offset: serr.ByteOffset, msg: syntaxErrorReplacer.Replace(msg)}
case ok:
return (*SyntaxError)(nil)
case export.IsIOError(err):
return errors.Unwrap(err) // v1 historically did not wrap IO errors
default:
return err
}
}
// syntaxErrorReplacer replaces certain string literals in the v2 error
// to better match the historical string rendering of syntax errors.
// In particular, v2 uses the terminology "object name" to match RFC 8259,
// while v1 uses "object key", which is not a term found in JSON literature.
var syntaxErrorReplacer = strings.NewReplacer(
"object name", "object key",
"at start of value", "looking for beginning of value",
"at start of string", "looking for beginning of object key string",
"after object value", "after object key:value pair",
"in number", "in numeric literal",
)
+311
View File
@@ -0,0 +1,311 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !goexperiment.jsonv2 || !go1.25
package json
import (
"bytes"
"errors"
"io"
"reflect"
jsonv2 "github.com/go-json-experiment/json"
"github.com/go-json-experiment/json/jsontext"
)
// A Decoder reads and decodes JSON values from an input stream.
type Decoder struct {
dec *jsontext.Decoder
opts jsonv2.Options
err error
// hadPeeked reports whether [Decoder.More] was called.
// It is reset by [Decoder.Decode] and [Decoder.Token].
hadPeeked bool
// hadEOF reports whether [Decoder.Token] had hit [io.EOF].
// It is reset by [Decoder.Decode] and [Decoder.Token].
hadEOF bool
}
// NewDecoder returns a new decoder that reads from r.
//
// The decoder introduces its own buffering and may
// read data from r beyond the JSON values requested.
func NewDecoder(r io.Reader) *Decoder {
// Hide bytes.Buffer from jsontext since it implements optimizations that
// also limit certain ways it could be used. For example, one cannot write
// to the bytes.Buffer while it is in use by jsontext.Decoder.
if _, ok := r.(*bytes.Buffer); ok {
r = struct{ io.Reader }{r}
}
dec := new(Decoder)
dec.opts = DefaultOptionsV1()
dec.dec = jsontext.NewDecoder(r, dec.opts)
return dec
}
// UseNumber causes the Decoder to unmarshal a number into an
// interface value as a [Number] instead of as a float64.
func (dec *Decoder) UseNumber() {
if useNumber, _ := jsonv2.GetOption(dec.opts, unmarshalAnyWithRawNumber); !useNumber {
dec.opts = jsonv2.JoinOptions(dec.opts, unmarshalAnyWithRawNumber(true))
}
}
// DisallowUnknownFields causes the Decoder to return an error when the destination
// is a struct and the input contains object keys which do not match any
// non-ignored, exported fields in the destination.
func (dec *Decoder) DisallowUnknownFields() {
if reject, _ := jsonv2.GetOption(dec.opts, jsonv2.RejectUnknownMembers); !reject {
dec.opts = jsonv2.JoinOptions(dec.opts, jsonv2.RejectUnknownMembers(true))
}
}
// Decode reads the next JSON-encoded value from its
// input and stores it in the value pointed to by v.
//
// See the documentation for [Unmarshal] for details about
// the conversion of JSON into a Go value.
func (dec *Decoder) Decode(v any) error {
if dec.err != nil {
return dec.err
}
b, err := dec.dec.ReadValue()
if err != nil {
dec.err = transformSyntacticError(err)
if dec.err.Error() == errUnexpectedEnd.Error() {
// NOTE: Decode has always been inconsistent with Unmarshal
// with regard to the exact error value for truncated input.
dec.err = io.ErrUnexpectedEOF
}
return dec.err
}
dec.hadPeeked = false
dec.hadEOF = false
return jsonv2.Unmarshal(b, v, dec.opts)
}
// Buffered returns a reader of the data remaining in the unread buffer,
// which may contain zero or more bytes.
// This is the data already consumed from the input [io.Reader],
// but not yet read by a [Decoder.Decode] or [Decoder.Token] call.
// It may contain bytes that do not form valid JSON as it has not yet
// been validated according to the JSON grammar.
// The exact amount of buffered data is an implementation detail
// of the Decoder and may change over time.
//
// It is the caller's responsibility to concatenate this buffer with
// the remainder of the input Reader to obtain the full sequence
// of bytes after the last decoded JSON value.
//
// The reader is valid until the next call to [Decoder.Decode] or [Decoder.Token].
func (dec *Decoder) Buffered() io.Reader {
return bytes.NewReader(dec.dec.UnreadBuffer())
}
// An Encoder writes JSON values to an output stream.
type Encoder struct {
w io.Writer
opts jsonv2.Options
err error
indentBuf bytes.Buffer
indentPrefix string
indentValue string
}
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
enc := new(Encoder)
enc.w = w
enc.opts = DefaultOptionsV1()
return enc
}
// Encode writes the JSON encoding of v to the stream,
// followed by a newline character.
//
// See the documentation for [Marshal] for details about the
// conversion of Go values to JSON.
func (enc *Encoder) Encode(v any) error {
if enc.err != nil {
return enc.err
}
e := export.GetBufferedEncoder(enc.opts)
defer export.PutBufferedEncoder(e)
if err := jsonv2.MarshalEncode(e, v); err != nil {
return err
}
b := export.Encoder(e).Buf // b must not leak current scope
if len(enc.indentPrefix)+len(enc.indentValue) > 0 {
enc.indentBuf.Reset()
if err := Indent(&enc.indentBuf, b, enc.indentPrefix, enc.indentValue); err != nil {
return err
}
b = enc.indentBuf.Bytes()
}
b = append(b, '\n')
if _, err := enc.w.Write(b); err != nil {
enc.err = err
return err
}
return nil
}
// SetIndent instructs the encoder to format each subsequent encoded
// value as if indented by the package-level function Indent(dst, src, prefix, indent).
// Calling SetIndent("", "") disables indentation.
func (enc *Encoder) SetIndent(prefix, indent string) {
// NOTE: Do not rely on the newer [jsontext.WithIndent] option since
// the v1 [Indent] behavior has historical bugs that cannot be changed
// for backward compatibility reasons.
enc.indentPrefix = prefix
enc.indentValue = indent
}
// SetEscapeHTML specifies whether problematic HTML characters
// should be escaped inside JSON quoted strings.
// The default behavior is to escape &, <, and > to \u0026, \u003c, and \u003e
// to avoid certain safety problems that can arise when embedding JSON in HTML.
//
// In non-HTML settings where the escaping interferes with the readability
// of the output, SetEscapeHTML(false) disables this behavior.
func (enc *Encoder) SetEscapeHTML(on bool) {
if escape, _ := jsonv2.GetOption(enc.opts, jsontext.EscapeForHTML); escape != on {
enc.opts = jsonv2.JoinOptions(enc.opts, jsontext.EscapeForHTML(on))
}
}
// RawMessage is a raw encoded JSON value.
// It implements [Marshaler] and [Unmarshaler] and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawMessage = jsontext.Value
// A Token holds a value of one of these types:
//
// - [Delim], for the four JSON delimiters [ ] { }
// - bool, for JSON booleans
// - float64, for JSON numbers
// - [Number], for JSON numbers
// - string, for JSON string literals
// - nil, for JSON null
type Token any
// A Delim is a JSON array or object delimiter, one of [ ] { or }.
type Delim rune
func (d Delim) String() string {
return string(d)
}
// Token returns the next JSON token in the input stream.
// At the end of the input stream, Token returns nil, [io.EOF].
//
// Token guarantees that the delimiters [ ] { } it returns are
// properly nested and matched: if Token encounters an unexpected
// delimiter in the input, it will return an error.
//
// The input stream consists of basic JSON values—bool, string,
// number, and null—along with delimiters [ ] { } of type [Delim]
// to mark the start and end of arrays and objects.
// Commas and colons are elided.
func (dec *Decoder) Token() (Token, error) {
if dec.err != nil {
return nil, dec.err
}
tok, err := dec.dec.ReadToken()
if err != nil {
// Historically, v1 would report just [io.EOF] if
// the stream is a prefix of a valid JSON value.
// It reports an unwrapped [io.ErrUnexpectedEOF] if
// truncated within a JSON token such as a literal, number, or string.
if errors.Is(err, io.ErrUnexpectedEOF) {
if len(bytes.Trim(dec.dec.UnreadBuffer(), " \r\n\t,:")) == 0 {
dec.hadEOF = true
return nil, io.EOF
}
return nil, io.ErrUnexpectedEOF
}
return nil, transformSyntacticError(err)
}
dec.hadPeeked = false
dec.hadEOF = false
switch k := tok.Kind(); k {
case 'n':
return nil, nil
case 'f':
return false, nil
case 't':
return true, nil
case '"':
return tok.String(), nil
case '0':
if useNumber, _ := jsonv2.GetOption(dec.opts, unmarshalAnyWithRawNumber); useNumber {
return Number(tok.String()), nil
}
v, err := tok.Float()
if err != nil {
return nil, &UnmarshalTypeError{Value: "number " + tok.String(), Type: reflect.TypeFor[float64](), Offset: dec.InputOffset()}
}
return v, nil
case '{', '}', '[', ']':
return Delim(k), nil
default:
panic("unreachable")
}
}
// More reports whether there is another element in the
// current array or object being parsed.
func (dec *Decoder) More() bool {
dec.hadPeeked = true
k := dec.dec.PeekKind()
if k == 0 {
if dec.err == nil {
// PeekKind doesn't distinguish between EOF and error,
// so read the next token to see which we get.
_, err := dec.dec.ReadToken()
if err == nil {
// This is only possible if jsontext violates its documentation.
err = errors.New("json: successful read after failed peek")
}
dec.err = transformSyntacticError(err)
}
return dec.err != io.EOF
}
return k != ']' && k != '}'
}
// InputOffset returns the input stream byte offset of the current decoder position.
// The offset gives the location of the end of the most recently returned token
// and the beginning of the next token.
func (dec *Decoder) InputOffset() int64 {
offset := dec.dec.InputOffset()
if dec.hadPeeked || dec.hadEOF {
// Historically, InputOffset reported the location of
// the end of the most recently returned token
// unless [Decoder.More] is called, in which case, it reported
// the beginning of the next token.
unreadBuffer := dec.dec.UnreadBuffer()
trailingTokens := bytes.TrimLeft(unreadBuffer, " \n\r\t")
if len(trailingTokens) > 0 {
leadingWhitespace := len(unreadBuffer) - len(trailingTokens)
offset += int64(leadingWhitespace)
// If [Decoder.Token] had encountered [io.EOF],
// then it also includes the upcoming comma or colon.
if dec.hadEOF && (trailingTokens[0] == ',' || trailingTokens[0] == ':') {
offset++
}
}
}
return offset
}