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