From 0e17905793cb5e0acc323a0cdf3733199d93976a Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Fri, 11 Apr 2025 14:19:51 -0700 Subject: [PATCH] encoding/json: add json/v2 with GOEXPERIMENT=jsonv2 guard This imports the proposed new v2 JSON API implemented in github.com/go-json-experiment/json as of commit d3c622f1b874954c355e60c8e6b6baa5f60d2fed. When GOEXPERIMENT=jsonv2 is set, the encoding/json/v2 and encoding/jsontext packages are visible, the encoding/json package is implemented in terms of encoding/json/v2, and the encoding/json package include various additional APIs. (See #71497 for details.) When GOEXPERIMENT=jsonv2 is not set, the new API is not present and the encoding/json package is unchanged. The experimental API is not bound by the Go compatibility promise and is expected to evolve as updates are made to the json/v2 proposal. The contents of encoding/json/internal/jsontest/testdata are compressed with zstd v1.5.7 with the -19 option. Fixes #71845 For #71497 Change-Id: Ib8c94e5f0586b6aaa22833190b41cf6ef59f4f01 Reviewed-on: https://go-review.googlesource.com/c/go/+/665796 Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Michael Pratt Reviewed-by: Joseph Tsai Reviewed-by: Dmitri Shuralyov --- src/encoding/json/bench_test.go | 2 + src/encoding/json/decode.go | 2 + src/encoding/json/decode_test.go | 2 + src/encoding/json/encode.go | 2 + src/encoding/json/encode_test.go | 2 + src/encoding/json/example_marshaling_test.go | 2 + src/encoding/json/example_test.go | 2 + .../json/example_text_marshaling_test.go | 2 + src/encoding/json/fold.go | 2 + src/encoding/json/fold_test.go | 2 + src/encoding/json/fuzz_test.go | 2 + src/encoding/json/indent.go | 2 + src/encoding/json/internal/internal.go | 41 + src/encoding/json/internal/jsonflags/flags.go | 205 + .../json/internal/jsonflags/flags_test.go | 75 + .../json/internal/jsonopts/options.go | 202 + .../json/internal/jsonopts/options_test.go | 233 + .../json/internal/jsontest/testcase.go | 37 + .../json/internal/jsontest/testdata.go | 607 ++ .../testdata/canada_geometry.json.zst | Bin 0 -> 67918 bytes .../jsontest/testdata/citm_catalog.json.zst | Bin 0 -> 9811 bytes .../jsontest/testdata/golang_source.json.zst | Bin 0 -> 93459 bytes .../jsontest/testdata/string_escaped.json.zst | Bin 0 -> 9052 bytes .../jsontest/testdata/string_unicode.json.zst | Bin 0 -> 11000 bytes .../jsontest/testdata/synthea_fhir.json.zst | Bin 0 -> 53517 bytes .../jsontest/testdata/twitter_status.json.zst | Bin 0 -> 37733 bytes src/encoding/json/internal/jsonwire/decode.go | 629 ++ .../json/internal/jsonwire/decode_test.go | 443 + src/encoding/json/internal/jsonwire/encode.go | 294 + .../json/internal/jsonwire/encode_test.go | 332 + src/encoding/json/internal/jsonwire/wire.go | 217 + .../json/internal/jsonwire/wire_test.go | 98 + src/encoding/json/jsontext/coder_test.go | 856 ++ src/encoding/json/jsontext/decode.go | 1168 ++ src/encoding/json/jsontext/decode_test.go | 1267 +++ src/encoding/json/jsontext/doc.go | 107 + src/encoding/json/jsontext/encode.go | 972 ++ src/encoding/json/jsontext/encode_test.go | 737 ++ src/encoding/json/jsontext/errors.go | 182 + src/encoding/json/jsontext/example_test.go | 130 + src/encoding/json/jsontext/export.go | 77 + src/encoding/json/jsontext/fuzz_test.go | 236 + src/encoding/json/jsontext/options.go | 303 + src/encoding/json/jsontext/pools.go | 152 + src/encoding/json/jsontext/quote.go | 41 + src/encoding/json/jsontext/state.go | 828 ++ src/encoding/json/jsontext/state_test.go | 396 + src/encoding/json/jsontext/token.go | 527 + src/encoding/json/jsontext/token_test.go | 168 + src/encoding/json/jsontext/value.go | 395 + src/encoding/json/jsontext/value_test.go | 200 + src/encoding/json/number_test.go | 2 + src/encoding/json/scanner.go | 2 + src/encoding/json/scanner_test.go | 2 + src/encoding/json/stream.go | 2 + src/encoding/json/stream_test.go | 2 + src/encoding/json/tables.go | 2 + src/encoding/json/tagkey_test.go | 2 + src/encoding/json/tags.go | 2 + src/encoding/json/tags_test.go | 2 + src/encoding/json/v2/arshal.go | 570 + src/encoding/json/v2/arshal_any.go | 283 + src/encoding/json/v2/arshal_default.go | 1910 ++++ src/encoding/json/v2/arshal_funcs.go | 432 + src/encoding/json/v2/arshal_inlined.go | 230 + src/encoding/json/v2/arshal_methods.go | 337 + src/encoding/json/v2/arshal_test.go | 9488 +++++++++++++++++ src/encoding/json/v2/arshal_time.go | 600 ++ src/encoding/json/v2/arshal_time_test.go | 312 + src/encoding/json/v2/bench_test.go | 647 ++ src/encoding/json/v2/doc.go | 170 + src/encoding/json/v2/errors.go | 420 + src/encoding/json/v2/errors_test.go | 115 + .../json/v2/example_orderedobject_test.go | 113 + src/encoding/json/v2/example_test.go | 692 ++ src/encoding/json/v2/fields.go | 646 ++ src/encoding/json/v2/fields_test.go | 821 ++ src/encoding/json/v2/fold.go | 58 + src/encoding/json/v2/fold_test.go | 127 + src/encoding/json/v2/fuzz_test.go | 39 + src/encoding/json/v2/inline_test.go | 109 + src/encoding/json/v2/intern.go | 88 + src/encoding/json/v2/intern_test.go | 146 + src/encoding/json/v2/options.go | 288 + src/encoding/json/v2_bench_test.go | 483 + src/encoding/json/v2_decode.go | 253 + src/encoding/json/v2_decode_test.go | 2803 +++++ src/encoding/json/v2_diff_test.go | 1129 ++ src/encoding/json/v2_encode.go | 240 + src/encoding/json/v2_encode_test.go | 1410 +++ .../json/v2_example_marshaling_test.go | 76 + src/encoding/json/v2_example_test.go | 313 + .../json/v2_example_text_marshaling_test.go | 70 + src/encoding/json/v2_fuzz_test.go | 85 + src/encoding/json/v2_indent.go | 133 + src/encoding/json/v2_inject.go | 153 + src/encoding/json/v2_options.go | 528 + src/encoding/json/v2_scanner.go | 82 + src/encoding/json/v2_scanner_test.go | 306 + src/encoding/json/v2_stream.go | 231 + src/encoding/json/v2_stream_test.go | 504 + src/encoding/json/v2_tagkey_test.go | 121 + src/go/build/deps_test.go | 23 +- src/internal/goexperiment/exp_jsonv2_off.go | 8 + src/internal/goexperiment/exp_jsonv2_on.go | 8 + src/internal/goexperiment/flags.go | 3 + src/time/time_test.go | 17 +- 107 files changed, 39814 insertions(+), 3 deletions(-) create mode 100644 src/encoding/json/internal/internal.go create mode 100644 src/encoding/json/internal/jsonflags/flags.go create mode 100644 src/encoding/json/internal/jsonflags/flags_test.go create mode 100644 src/encoding/json/internal/jsonopts/options.go create mode 100644 src/encoding/json/internal/jsonopts/options_test.go create mode 100644 src/encoding/json/internal/jsontest/testcase.go create mode 100644 src/encoding/json/internal/jsontest/testdata.go create mode 100644 src/encoding/json/internal/jsontest/testdata/canada_geometry.json.zst create mode 100644 src/encoding/json/internal/jsontest/testdata/citm_catalog.json.zst create mode 100644 src/encoding/json/internal/jsontest/testdata/golang_source.json.zst create mode 100644 src/encoding/json/internal/jsontest/testdata/string_escaped.json.zst create mode 100644 src/encoding/json/internal/jsontest/testdata/string_unicode.json.zst create mode 100644 src/encoding/json/internal/jsontest/testdata/synthea_fhir.json.zst create mode 100644 src/encoding/json/internal/jsontest/testdata/twitter_status.json.zst create mode 100644 src/encoding/json/internal/jsonwire/decode.go create mode 100644 src/encoding/json/internal/jsonwire/decode_test.go create mode 100644 src/encoding/json/internal/jsonwire/encode.go create mode 100644 src/encoding/json/internal/jsonwire/encode_test.go create mode 100644 src/encoding/json/internal/jsonwire/wire.go create mode 100644 src/encoding/json/internal/jsonwire/wire_test.go create mode 100644 src/encoding/json/jsontext/coder_test.go create mode 100644 src/encoding/json/jsontext/decode.go create mode 100644 src/encoding/json/jsontext/decode_test.go create mode 100644 src/encoding/json/jsontext/doc.go create mode 100644 src/encoding/json/jsontext/encode.go create mode 100644 src/encoding/json/jsontext/encode_test.go create mode 100644 src/encoding/json/jsontext/errors.go create mode 100644 src/encoding/json/jsontext/example_test.go create mode 100644 src/encoding/json/jsontext/export.go create mode 100644 src/encoding/json/jsontext/fuzz_test.go create mode 100644 src/encoding/json/jsontext/options.go create mode 100644 src/encoding/json/jsontext/pools.go create mode 100644 src/encoding/json/jsontext/quote.go create mode 100644 src/encoding/json/jsontext/state.go create mode 100644 src/encoding/json/jsontext/state_test.go create mode 100644 src/encoding/json/jsontext/token.go create mode 100644 src/encoding/json/jsontext/token_test.go create mode 100644 src/encoding/json/jsontext/value.go create mode 100644 src/encoding/json/jsontext/value_test.go create mode 100644 src/encoding/json/v2/arshal.go create mode 100644 src/encoding/json/v2/arshal_any.go create mode 100644 src/encoding/json/v2/arshal_default.go create mode 100644 src/encoding/json/v2/arshal_funcs.go create mode 100644 src/encoding/json/v2/arshal_inlined.go create mode 100644 src/encoding/json/v2/arshal_methods.go create mode 100644 src/encoding/json/v2/arshal_test.go create mode 100644 src/encoding/json/v2/arshal_time.go create mode 100644 src/encoding/json/v2/arshal_time_test.go create mode 100644 src/encoding/json/v2/bench_test.go create mode 100644 src/encoding/json/v2/doc.go create mode 100644 src/encoding/json/v2/errors.go create mode 100644 src/encoding/json/v2/errors_test.go create mode 100644 src/encoding/json/v2/example_orderedobject_test.go create mode 100644 src/encoding/json/v2/example_test.go create mode 100644 src/encoding/json/v2/fields.go create mode 100644 src/encoding/json/v2/fields_test.go create mode 100644 src/encoding/json/v2/fold.go create mode 100644 src/encoding/json/v2/fold_test.go create mode 100644 src/encoding/json/v2/fuzz_test.go create mode 100644 src/encoding/json/v2/inline_test.go create mode 100644 src/encoding/json/v2/intern.go create mode 100644 src/encoding/json/v2/intern_test.go create mode 100644 src/encoding/json/v2/options.go create mode 100644 src/encoding/json/v2_bench_test.go create mode 100644 src/encoding/json/v2_decode.go create mode 100644 src/encoding/json/v2_decode_test.go create mode 100644 src/encoding/json/v2_diff_test.go create mode 100644 src/encoding/json/v2_encode.go create mode 100644 src/encoding/json/v2_encode_test.go create mode 100644 src/encoding/json/v2_example_marshaling_test.go create mode 100644 src/encoding/json/v2_example_test.go create mode 100644 src/encoding/json/v2_example_text_marshaling_test.go create mode 100644 src/encoding/json/v2_fuzz_test.go create mode 100644 src/encoding/json/v2_indent.go create mode 100644 src/encoding/json/v2_inject.go create mode 100644 src/encoding/json/v2_options.go create mode 100644 src/encoding/json/v2_scanner.go create mode 100644 src/encoding/json/v2_scanner_test.go create mode 100644 src/encoding/json/v2_stream.go create mode 100644 src/encoding/json/v2_stream_test.go create mode 100644 src/encoding/json/v2_tagkey_test.go create mode 100644 src/internal/goexperiment/exp_jsonv2_off.go create mode 100644 src/internal/goexperiment/exp_jsonv2_on.go diff --git a/src/encoding/json/bench_test.go b/src/encoding/json/bench_test.go index 032114cac1..cd55ceed90 100644 --- a/src/encoding/json/bench_test.go +++ b/src/encoding/json/bench_test.go @@ -8,6 +8,8 @@ // We benchmark converting between the JSON form // and in-memory data structures. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index 3b398c9fc3..4e195e0948 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -5,6 +5,8 @@ // Represents JSON data structure using native Go types: booleans, floats, // strings, arrays, and maps. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/decode_test.go b/src/encoding/json/decode_test.go index 8aad11b8bf..5bc3d3c856 100644 --- a/src/encoding/json/decode_test.go +++ b/src/encoding/json/decode_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/encode.go b/src/encoding/json/encode.go index 7b4bfff700..78d0865b89 100644 --- a/src/encoding/json/encode.go +++ b/src/encoding/json/encode.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + // 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. diff --git a/src/encoding/json/encode_test.go b/src/encoding/json/encode_test.go index 79c481754e..bc31f9d48a 100644 --- a/src/encoding/json/encode_test.go +++ b/src/encoding/json/encode_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/example_marshaling_test.go b/src/encoding/json/example_marshaling_test.go index 7f15c742b8..72f4cca8ad 100644 --- a/src/encoding/json/example_marshaling_test.go +++ b/src/encoding/json/example_marshaling_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json_test import ( diff --git a/src/encoding/json/example_test.go b/src/encoding/json/example_test.go index 2261c770c0..15c2538349 100644 --- a/src/encoding/json/example_test.go +++ b/src/encoding/json/example_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json_test import ( diff --git a/src/encoding/json/example_text_marshaling_test.go b/src/encoding/json/example_text_marshaling_test.go index 04c7813b26..178c7bafd2 100644 --- a/src/encoding/json/example_text_marshaling_test.go +++ b/src/encoding/json/example_text_marshaling_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json_test import ( diff --git a/src/encoding/json/fold.go b/src/encoding/json/fold.go index c4c671b527..f096ed6054 100644 --- a/src/encoding/json/fold.go +++ b/src/encoding/json/fold.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/fold_test.go b/src/encoding/json/fold_test.go index 9d6fd0559d..4d03e3d1c2 100644 --- a/src/encoding/json/fold_test.go +++ b/src/encoding/json/fold_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/fuzz_test.go b/src/encoding/json/fuzz_test.go index f01960398a..37dc436fcd 100644 --- a/src/encoding/json/fuzz_test.go +++ b/src/encoding/json/fuzz_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/indent.go b/src/encoding/json/indent.go index 01bfdf65e7..b6f31fb510 100644 --- a/src/encoding/json/indent.go +++ b/src/encoding/json/indent.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import "bytes" diff --git a/src/encoding/json/internal/internal.go b/src/encoding/json/internal/internal.go new file mode 100644 index 0000000000..f587c7b32c --- /dev/null +++ b/src/encoding/json/internal/internal.go @@ -0,0 +1,41 @@ +// 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 + +package internal + +import "errors" + +// NotForPublicUse is a marker type that an API is for internal use only. +// It does not perfectly prevent usage of that API, but helps to restrict usage. +// Anything with this marker is not covered by the Go compatibility agreement. +type NotForPublicUse struct{} + +// AllowInternalUse is passed from "json" to "jsontext" to authenticate +// that the caller can have access to internal functionality. +var AllowInternalUse NotForPublicUse + +// Sentinel error values internally shared between jsonv1 and jsonv2. +var ( + ErrCycle = errors.New("encountered a cycle") + ErrNonNilReference = errors.New("value must be passed as a non-nil pointer reference") +) + +var ( + // TransformMarshalError converts a v2 error into a v1 error. + // It is called only at the top-level of a Marshal function. + TransformMarshalError func(any, error) error + // NewMarshalerError constructs a jsonv1.MarshalerError. + // It is called after a user-defined Marshal method/function fails. + NewMarshalerError func(any, error, string) error + // TransformUnmarshalError converts a v2 error into a v1 error. + // It is called only at the top-level of a Unmarshal function. + TransformUnmarshalError func(any, error) error + + // NewRawNumber returns new(jsonv1.Number). + NewRawNumber func() any + // RawNumberOf returns jsonv1.Number(b). + RawNumberOf func(b []byte) any +) diff --git a/src/encoding/json/internal/jsonflags/flags.go b/src/encoding/json/internal/jsonflags/flags.go new file mode 100644 index 0000000000..4496359c89 --- /dev/null +++ b/src/encoding/json/internal/jsonflags/flags.go @@ -0,0 +1,205 @@ +// 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 + +// jsonflags implements all the optional boolean flags. +// These flags are shared across both "json", "jsontext", and "jsonopts". +package jsonflags + +import "encoding/json/internal" + +// Bools represents zero or more boolean flags, all set to true or false. +// The least-significant bit is the boolean value of all flags in the set. +// The remaining bits identify which particular flags. +// +// In common usage, this is OR'd with 0 or 1. For example: +// - (AllowInvalidUTF8 | 0) means "AllowInvalidUTF8 is false" +// - (Multiline | Indent | 1) means "Multiline and Indent are true" +type Bools uint64 + +func (Bools) JSONOptions(internal.NotForPublicUse) {} + +const ( + // AllFlags is the set of all flags. + AllFlags = AllCoderFlags | AllArshalV2Flags | AllArshalV1Flags + + // AllCoderFlags is the set of all encoder/decoder flags. + AllCoderFlags = (maxCoderFlag - 1) - initFlag + + // AllArshalV2Flags is the set of all v2 marshal/unmarshal flags. + AllArshalV2Flags = (maxArshalV2Flag - 1) - (maxCoderFlag - 1) + + // AllArshalV1Flags is the set of all v1 marshal/unmarshal flags. + AllArshalV1Flags = (maxArshalV1Flag - 1) - (maxArshalV2Flag - 1) + + // NonBooleanFlags is the set of non-boolean flags, + // where the value is some other concrete Go type. + // The value of the flag is stored within jsonopts.Struct. + NonBooleanFlags = 0 | + Indent | + IndentPrefix | + ByteLimit | + DepthLimit | + Marshalers | + Unmarshalers + + // DefaultV1Flags is the set of booleans flags that default to true under + // v1 semantics. None of the non-boolean flags differ between v1 and v2. + DefaultV1Flags = 0 | + AllowDuplicateNames | + AllowInvalidUTF8 | + EscapeForHTML | + EscapeForJS | + EscapeInvalidUTF8 | + PreserveRawStrings | + Deterministic | + FormatNilMapAsNull | + FormatNilSliceAsNull | + MatchCaseInsensitiveNames | + CallMethodsWithLegacySemantics | + FormatBytesWithLegacySemantics | + FormatTimeWithLegacySemantics | + MatchCaseSensitiveDelimiter | + MergeWithLegacySemantics | + OmitEmptyWithLegacyDefinition | + ReportErrorsWithLegacySemantics | + StringifyWithLegacySemantics | + UnmarshalArrayFromAnyLength + + // AnyWhitespace reports whether the encoded output might have any whitespace. + AnyWhitespace = Multiline | SpaceAfterColon | SpaceAfterComma + + // WhitespaceFlags is the set of flags related to whitespace formatting. + // In contrast to AnyWhitespace, this includes Indent and IndentPrefix + // as those settings take no effect if Multiline is false. + WhitespaceFlags = AnyWhitespace | Indent | IndentPrefix + + // AnyEscape is the set of flags related to escaping in a JSON string. + AnyEscape = EscapeForHTML | EscapeForJS | EscapeInvalidUTF8 + + // CanonicalizeNumbers is the set of flags related to raw number canonicalization. + CanonicalizeNumbers = CanonicalizeRawInts | CanonicalizeRawFloats +) + +// Encoder and decoder flags. +const ( + initFlag Bools = 1 << iota // reserved for the boolean value itself + + AllowDuplicateNames // encode or decode + AllowInvalidUTF8 // encode or decode + WithinArshalCall // encode or decode; for internal use by json.Marshal and json.Unmarshal + OmitTopLevelNewline // encode only; for internal use by json.Marshal and json.MarshalWrite + PreserveRawStrings // encode only + CanonicalizeRawInts // encode only + CanonicalizeRawFloats // encode only + ReorderRawObjects // encode only + EscapeForHTML // encode only + EscapeForJS // encode only + EscapeInvalidUTF8 // encode only; only exposed in v1 + Multiline // encode only + SpaceAfterColon // encode only + SpaceAfterComma // encode only + Indent // encode only; non-boolean flag + IndentPrefix // encode only; non-boolean flag + ByteLimit // encode or decode; non-boolean flag + DepthLimit // encode or decode; non-boolean flag + + maxCoderFlag +) + +// Marshal and Unmarshal flags (for v2). +const ( + _ Bools = (maxCoderFlag >> 1) << iota + + StringifyNumbers // marshal or unmarshal + Deterministic // marshal only + FormatNilMapAsNull // marshal only + 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 + + maxArshalV2Flag +) + +// Marshal and Unmarshal flags (for v1). +const ( + _ Bools = (maxArshalV2Flag >> 1) << iota + + CallMethodsWithLegacySemantics // marshal or unmarshal + FormatBytesWithLegacySemantics // marshal or unmarshal + FormatTimeWithLegacySemantics // marshal or unmarshal + MatchCaseSensitiveDelimiter // marshal or unmarshal + MergeWithLegacySemantics // unmarshal + OmitEmptyWithLegacyDefinition // marshal + ReportErrorsWithLegacySemantics // marshal or unmarshal + StringifyWithLegacySemantics // marshal or unmarshal + StringifyBoolsAndStrings // marshal or unmarshal; for internal use by jsonv2.makeStructArshaler + UnmarshalAnyWithRawNumber // unmarshal; for internal use by jsonv1.Decoder.UseNumber + UnmarshalArrayFromAnyLength // unmarshal + + maxArshalV1Flag +) + +// Flags is a set of boolean flags. +// If the presence bit is zero, then the value bit must also be zero. +// The least-significant bit of both fields is always zero. +// +// Unlike Bools, which can represent a set of bools that are all true or false, +// Flags represents a set of bools, each individually may be true or false. +type Flags struct{ Presence, Values uint64 } + +// Join joins two sets of flags such that the latter takes precedence. +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 +} + +// Set sets both the presence and value for the provided bool (or set of bools). +func (fs *Flags) Set(f Bools) { + // Select out the bits for the flag identifiers (everything except LSB), + // 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., 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.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 +} + +// Get reports whether the bool (or any of the bools) is true. +// This is generally only used with a singular bool. +// The value bit of f (i.e., the LSB) is ignored. +func (fs Flags) Get(f Bools) bool { + return fs.Values&uint64(f) > 0 +} + +// Has reports whether the bool (or any of the bools) is set. +// The value bit of f (i.e., the LSB) is ignored. +func (fs Flags) Has(f Bools) bool { + return fs.Presence&uint64(f) > 0 +} + +// Clear clears both the presence and value for the provided bool or bools. +// 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., 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 + fs.Values &= mask // e.g., 0b_0001_0010 & 0b_1110_0111 -> 0b_0000_0010 +} diff --git a/src/encoding/json/internal/jsonflags/flags_test.go b/src/encoding/json/internal/jsonflags/flags_test.go new file mode 100644 index 0000000000..e4d3358bff --- /dev/null +++ b/src/encoding/json/internal/jsonflags/flags_test.go @@ -0,0 +1,75 @@ +// 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 + +package jsonflags + +import "testing" + +func TestFlags(t *testing.T) { + type Check struct{ want Flags } + type Join struct{ in Flags } + type Set struct{ in Bools } + type Clear struct{ in Bools } + type Get struct { + in Bools + want bool + wantOk bool + } + + calls := []any{ + Get{in: AllowDuplicateNames, want: false, wantOk: false}, + Set{in: AllowDuplicateNames | 0}, + Get{in: AllowDuplicateNames, want: false, wantOk: true}, + Set{in: AllowDuplicateNames | 1}, + Get{in: AllowDuplicateNames, want: true, wantOk: true}, + Check{want: Flags{Presence: uint64(AllowDuplicateNames), Values: uint64(AllowDuplicateNames)}}, + Get{in: AllowInvalidUTF8, want: false, wantOk: false}, + Set{in: AllowInvalidUTF8 | 1}, + Get{in: AllowInvalidUTF8, want: true, wantOk: true}, + Set{in: AllowInvalidUTF8 | 0}, + Get{in: AllowInvalidUTF8, want: false, wantOk: true}, + Get{in: AllowDuplicateNames, want: true, wantOk: true}, + Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, + Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 0}, + Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(0)}}, + Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 0}, + Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(0)}}, + Set{in: AllowDuplicateNames | AllowInvalidUTF8 | 1}, + Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames | AllowInvalidUTF8)}}, + Join{in: Flags{Presence: 0, Values: 0}}, + Check{want: Flags{Presence: uint64(AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames | AllowInvalidUTF8)}}, + Join{in: Flags{Presence: uint64(Multiline | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, + Check{want: Flags{Presence: uint64(Multiline | AllowDuplicateNames | AllowInvalidUTF8), Values: uint64(AllowDuplicateNames)}}, + Clear{in: AllowDuplicateNames | AllowInvalidUTF8}, + Check{want: Flags{Presence: uint64(Multiline), Values: uint64(0)}}, + Set{in: AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | 1}, + Set{in: Multiline | StringifyNumbers | 0}, + Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics | Multiline | StringifyNumbers), Values: uint64(AllowInvalidUTF8 | Deterministic | ReportErrorsWithLegacySemantics)}}, + Clear{in: ^AllCoderFlags}, + Check{want: Flags{Presence: uint64(AllowInvalidUTF8 | Multiline), Values: uint64(AllowInvalidUTF8)}}, + } + var fs Flags + for i, call := range calls { + switch call := call.(type) { + case Join: + fs.Join(call.in) + case Set: + fs.Set(call.in) + case Clear: + fs.Clear(call.in) + case Get: + got := fs.Get(call.in) + gotOk := fs.Has(call.in) + if got != call.want || gotOk != call.wantOk { + t.Fatalf("%d: GetOk = (%v, %v), want (%v, %v)", i, got, gotOk, call.want, call.wantOk) + } + case Check: + if fs != call.want { + t.Fatalf("%d: got %x, want %x", i, fs, call.want) + } + } + } +} diff --git a/src/encoding/json/internal/jsonopts/options.go b/src/encoding/json/internal/jsonopts/options.go new file mode 100644 index 0000000000..2226830b6b --- /dev/null +++ b/src/encoding/json/internal/jsonopts/options.go @@ -0,0 +1,202 @@ +// 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 + +package jsonopts + +import ( + "encoding/json/internal" + "encoding/json/internal/jsonflags" +) + +// Options is the common options type shared across json packages. +type Options interface { + // JSONOptions is exported so related json packages can implement Options. + JSONOptions(internal.NotForPublicUse) +} + +// 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 { + Flags jsonflags.Flags + + CoderValues + ArshalValues +} + +type CoderValues struct { + Indent string // jsonflags.Indent + IndentPrefix string // jsonflags.IndentPrefix + ByteLimit int64 // jsonflags.ByteLimit + DepthLimit int // jsonflags.DepthLimit +} + +type ArshalValues struct { + // The Marshalers and Unmarshalers fields use the any type to avoid a + // concrete dependency on *json.Marshalers and *json.Unmarshalers, + // which would in turn create a dependency on the "reflect" package. + + Marshalers any // jsonflags.Marshalers + Unmarshalers any // jsonflags.Unmarshalers + + Format string + FormatDepth int +} + +// 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), + }, +} + +// 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), + }, +} + +func (*Struct) JSONOptions(internal.NotForPublicUse) {} + +// GetUnknownOption is injected by the "json" package to handle Options +// declared in that package so that "jsonopts" can handle them. +var GetUnknownOption = func(*Struct, Options) (any, bool) { panic("unknown option") } + +func GetOption[T any](opts Options, setter func(T) Options) (T, bool) { + // Collapse the options to *Struct to simplify lookup. + structOpts, ok := opts.(*Struct) + if !ok { + var structOpts2 Struct + structOpts2.Join(opts) + structOpts = &structOpts2 + } + + // Lookup the option based on the return value of the setter. + var zero T + switch opt := setter(zero).(type) { + case jsonflags.Bools: + v := structOpts.Flags.Get(opt) + ok := structOpts.Flags.Has(opt) + return any(v).(T), ok + case Indent: + if !structOpts.Flags.Has(jsonflags.Indent) { + return zero, false + } + return any(structOpts.Indent).(T), true + case IndentPrefix: + if !structOpts.Flags.Has(jsonflags.IndentPrefix) { + return zero, false + } + return any(structOpts.IndentPrefix).(T), true + case ByteLimit: + if !structOpts.Flags.Has(jsonflags.ByteLimit) { + return zero, false + } + return any(structOpts.ByteLimit).(T), true + case DepthLimit: + if !structOpts.Flags.Has(jsonflags.DepthLimit) { + return zero, false + } + return any(structOpts.DepthLimit).(T), true + default: + v, ok := GetUnknownOption(structOpts, opt) + return v.(T), ok + } +} + +// JoinUnknownOption is injected by the "json" package to handle Options +// declared in that package so that "jsonopts" can handle them. +var JoinUnknownOption = func(*Struct, Options) { panic("unknown option") } + +func (dst *Struct) Join(srcs ...Options) { + dst.join(false, srcs...) +} + +func (dst *Struct) JoinWithoutCoderOptions(srcs ...Options) { + dst.join(true, srcs...) +} + +func (dst *Struct) join(excludeCoderOptions bool, srcs ...Options) { + for _, src := range srcs { + switch src := src.(type) { + case nil: + continue + case jsonflags.Bools: + if excludeCoderOptions { + src &= ^jsonflags.AllCoderFlags + } + dst.Flags.Set(src) + case Indent: + if excludeCoderOptions { + continue + } + 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.Indent = src.Indent + } + if srcFlags.Has(jsonflags.IndentPrefix) { + dst.IndentPrefix = src.IndentPrefix + } + if srcFlags.Has(jsonflags.ByteLimit) { + dst.ByteLimit = src.ByteLimit + } + if srcFlags.Has(jsonflags.DepthLimit) { + dst.DepthLimit = src.DepthLimit + } + if srcFlags.Has(jsonflags.Marshalers) { + dst.Marshalers = src.Marshalers + } + if srcFlags.Has(jsonflags.Unmarshalers) { + dst.Unmarshalers = src.Unmarshalers + } + } + default: + JoinUnknownOption(dst, src) + } + } +} + +type ( + Indent string // jsontext.WithIndent + IndentPrefix string // jsontext.WithIndentPrefix + ByteLimit int64 // jsontext.WithByteLimit + DepthLimit int // jsontext.WithDepthLimit + // type for jsonflags.Marshalers declared in "json" package + // type for jsonflags.Unmarshalers declared in "json" package +) + +func (Indent) JSONOptions(internal.NotForPublicUse) {} +func (IndentPrefix) JSONOptions(internal.NotForPublicUse) {} +func (ByteLimit) JSONOptions(internal.NotForPublicUse) {} +func (DepthLimit) JSONOptions(internal.NotForPublicUse) {} diff --git a/src/encoding/json/internal/jsonopts/options_test.go b/src/encoding/json/internal/jsonopts/options_test.go new file mode 100644 index 0000000000..ebfaf05c83 --- /dev/null +++ b/src/encoding/json/internal/jsonopts/options_test.go @@ -0,0 +1,233 @@ +// 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 + +package jsonopts_test + +import ( + "reflect" + "testing" + + "encoding/json/internal/jsonflags" + . "encoding/json/internal/jsonopts" + "encoding/json/jsontext" + "encoding/json/v2" +) + +func makeFlags(f ...jsonflags.Bools) (fs jsonflags.Flags) { + for _, f := range f { + fs.Set(f) + } + return fs +} + +func TestJoin(t *testing.T) { + tests := []struct { + in Options + excludeCoders bool + want *Struct + }{{ + in: jsonflags.AllowInvalidUTF8 | 1, + want: &Struct{Flags: makeFlags(jsonflags.AllowInvalidUTF8 | 1)}, + }, { + in: jsonflags.Multiline | 0, + want: &Struct{ + Flags: makeFlags(jsonflags.AllowInvalidUTF8|1, jsonflags.Multiline|0)}, + }, { + in: Indent("\t"), // implicitly sets Multiline=true + want: &Struct{ + Flags: makeFlags(jsonflags.AllowInvalidUTF8 | jsonflags.Multiline | jsonflags.Indent | 1), + CoderValues: CoderValues{Indent: "\t"}, + }, + }, { + in: &Struct{ + Flags: makeFlags(jsonflags.Multiline|jsonflags.EscapeForJS|0, jsonflags.AllowInvalidUTF8|1), + }, + want: &Struct{ + Flags: makeFlags(jsonflags.AllowInvalidUTF8|jsonflags.Indent|1, jsonflags.Multiline|jsonflags.EscapeForJS|0), + CoderValues: CoderValues{Indent: "\t"}, + }, + }, { + in: &DefaultOptionsV1, + want: func() *Struct { + v1 := DefaultOptionsV1 + v1.Flags.Set(jsonflags.Indent | 1) + v1.Flags.Set(jsonflags.Multiline | 0) + v1.Indent = "\t" + return &v1 + }(), // v1 fully replaces before (except for whitespace related flags) + }, { + in: &DefaultOptionsV2, + want: func() *Struct { + v2 := DefaultOptionsV2 + v2.Flags.Set(jsonflags.Indent | 1) + v2.Flags.Set(jsonflags.Multiline | 0) + v2.Indent = "\t" + return &v2 + }(), // v2 fully replaces before (except for whitespace related flags) + }, { + in: jsonflags.Deterministic | jsonflags.AllowInvalidUTF8 | 1, excludeCoders: true, + want: func() *Struct { + v2 := DefaultOptionsV2 + v2.Flags.Set(jsonflags.Deterministic | 1) + v2.Flags.Set(jsonflags.Indent | 1) + v2.Flags.Set(jsonflags.Multiline | 0) + v2.Indent = "\t" + return &v2 + }(), + }, { + in: jsontext.WithIndentPrefix(" "), excludeCoders: true, + want: func() *Struct { + v2 := DefaultOptionsV2 + v2.Flags.Set(jsonflags.Deterministic | 1) + v2.Flags.Set(jsonflags.Indent | 1) + v2.Flags.Set(jsonflags.Multiline | 0) + v2.Indent = "\t" + return &v2 + }(), + }, { + in: jsontext.WithIndentPrefix(" "), excludeCoders: false, + want: func() *Struct { + v2 := DefaultOptionsV2 + v2.Flags.Set(jsonflags.Deterministic | 1) + v2.Flags.Set(jsonflags.Indent | 1) + v2.Flags.Set(jsonflags.IndentPrefix | 1) + v2.Flags.Set(jsonflags.Multiline | 1) + v2.Indent = "\t" + v2.IndentPrefix = " " + return &v2 + }(), + }, { + in: &Struct{ + Flags: jsonflags.Flags{ + Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix), + Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix), + }, + CoderValues: CoderValues{Indent: " ", IndentPrefix: " "}, + }, + excludeCoders: true, + want: func() *Struct { + v2 := DefaultOptionsV2 + v2.Flags.Set(jsonflags.Indent | 1) + v2.Flags.Set(jsonflags.IndentPrefix | 1) + v2.Flags.Set(jsonflags.Multiline | 1) + v2.Indent = "\t" + v2.IndentPrefix = " " + return &v2 + }(), + }, { + in: &Struct{ + Flags: jsonflags.Flags{ + Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix), + Values: uint64(jsonflags.Indent | jsonflags.IndentPrefix), + }, + CoderValues: CoderValues{Indent: " ", IndentPrefix: " "}, + }, + excludeCoders: false, + want: func() *Struct { + v2 := DefaultOptionsV2 + v2.Flags.Set(jsonflags.Indent | 1) + v2.Flags.Set(jsonflags.IndentPrefix | 1) + v2.Flags.Set(jsonflags.Multiline | 1) + v2.Indent = " " + v2.IndentPrefix = " " + return &v2 + }(), + }} + got := new(Struct) + for i, tt := range tests { + if tt.excludeCoders { + got.JoinWithoutCoderOptions(tt.in) + } else { + got.Join(tt.in) + } + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("%d: Join:\n\tgot: %+v\n\twant: %+v", i, got, tt.want) + } + } +} + +func TestGet(t *testing.T) { + opts := &Struct{ + Flags: makeFlags(jsonflags.Indent|jsonflags.Deterministic|jsonflags.Marshalers|1, jsonflags.Multiline|0), + CoderValues: CoderValues{Indent: "\t"}, + ArshalValues: ArshalValues{Marshalers: new(json.Marshalers)}, + } + if v, ok := json.GetOption(nil, jsontext.AllowDuplicateNames); v || ok { + t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) + } + if v, ok := json.GetOption(jsonflags.AllowInvalidUTF8|0, jsontext.AllowDuplicateNames); v || ok { + t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) + } + if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|0, jsontext.AllowDuplicateNames); v || !ok { + t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, true)", v, ok) + } + if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.AllowDuplicateNames); !v || !ok { + t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (true, true)", v, ok) + } + if v, ok := json.GetOption(Indent(""), jsontext.AllowDuplicateNames); v || ok { + t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) + } + if v, ok := json.GetOption(Indent(" "), jsontext.WithIndent); v != " " || !ok { + t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want (" ", true)`, v, ok) + } + if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.WithIndent); v != "" || ok { + t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("", false)`, v, ok) + } + if v, ok := json.GetOption(opts, jsontext.AllowDuplicateNames); v || ok { + t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok) + } + if v, ok := json.GetOption(opts, json.Deterministic); !v || !ok { + t.Errorf("GetOption(..., Deterministic) = (%v, %v), want (true, true)", v, ok) + } + if v, ok := json.GetOption(opts, jsontext.Multiline); v || !ok { + t.Errorf("GetOption(..., Multiline) = (%v, %v), want (false, true)", v, ok) + } + if v, ok := json.GetOption(opts, jsontext.AllowInvalidUTF8); v || ok { + t.Errorf("GetOption(..., AllowInvalidUTF8) = (%v, %v), want (false, false)", v, ok) + } + if v, ok := json.GetOption(opts, jsontext.WithIndent); v != "\t" || !ok { + t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("\t", true)`, v, ok) + } + if v, ok := json.GetOption(opts, jsontext.WithIndentPrefix); v != "" || ok { + t.Errorf(`GetOption(..., WithIndentPrefix) = (%q, %v), want ("", false)`, v, ok) + } + if v, ok := json.GetOption(opts, json.WithMarshalers); v == nil || !ok { + t.Errorf(`GetOption(..., WithMarshalers) = (%v, %v), want (non-nil, true)`, v, ok) + } + if v, ok := json.GetOption(opts, json.WithUnmarshalers); v != nil || ok { + t.Errorf(`GetOption(..., WithUnmarshalers) = (%v, %v), want (nil, false)`, v, ok) + } +} + +var sink struct { + Bool bool + String string + Marshalers *json.Marshalers +} + +func BenchmarkGetBool(b *testing.B) { + b.ReportAllocs() + opts := json.DefaultOptionsV2() + for range b.N { + sink.Bool, sink.Bool = json.GetOption(opts, jsontext.AllowDuplicateNames) + } +} + +func BenchmarkGetIndent(b *testing.B) { + b.ReportAllocs() + opts := json.DefaultOptionsV2() + for range b.N { + sink.String, sink.Bool = json.GetOption(opts, jsontext.WithIndent) + } +} + +func BenchmarkGetMarshalers(b *testing.B) { + b.ReportAllocs() + opts := json.JoinOptions(json.DefaultOptionsV2(), json.WithMarshalers(nil)) + for range b.N { + sink.Marshalers, sink.Bool = json.GetOption(opts, json.WithMarshalers) + } +} diff --git a/src/encoding/json/internal/jsontest/testcase.go b/src/encoding/json/internal/jsontest/testcase.go new file mode 100644 index 0000000000..73a64c8cfa --- /dev/null +++ b/src/encoding/json/internal/jsontest/testcase.go @@ -0,0 +1,37 @@ +// 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 + +package jsontest + +import ( + "fmt" + "path" + "runtime" +) + +// TODO(https://go.dev/issue/52751): Replace with native testing support. + +// CaseName is a case name annotated with a file and line. +type CaseName struct { + Name string + Where CasePos +} + +// Name annotates a case name with the file and line of the caller. +func Name(s string) (c CaseName) { + c.Name = s + runtime.Callers(2, c.Where.pc[:]) + return c +} + +// CasePos represents a file and line number. +type CasePos struct{ pc [1]uintptr } + +func (pos CasePos) String() string { + frames := runtime.CallersFrames(pos.pc[:]) + frame, _ := frames.Next() + return fmt.Sprintf("%s:%d", path.Base(frame.File), frame.Line) +} diff --git a/src/encoding/json/internal/jsontest/testdata.go b/src/encoding/json/internal/jsontest/testdata.go new file mode 100644 index 0000000000..74de366136 --- /dev/null +++ b/src/encoding/json/internal/jsontest/testdata.go @@ -0,0 +1,607 @@ +// Copyright 2020 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 + +// Package jsontest contains functionality to assist in testing JSON. +package jsontest + +import ( + "bytes" + "embed" + "errors" + "internal/zstd" + "io" + "io/fs" + "path" + "slices" + "strings" + "sync" + "time" +) + +// Embed the testdata directory as a fs.FS because this package is imported +// by other packages such that the location of testdata may change relative +// to the working directory of the test itself. +// +//go:embed testdata/*.json.zst +var testdataFS embed.FS + +type Entry struct { + Name string + Data func() []byte + New func() any // nil if there is no concrete type for this +} + +func mustGet[T any](v T, err error) T { + if err != nil { + panic(err) + } + return v +} + +// Data is a list of JSON testdata. +var Data = func() (entries []Entry) { + fis := mustGet(fs.ReadDir(testdataFS, "testdata")) + slices.SortFunc(fis, func(x, y fs.DirEntry) int { return strings.Compare(x.Name(), y.Name()) }) + for _, fi := range fis { + var entry Entry + + // Convert snake_case file name to CamelCase. + words := strings.Split(strings.TrimSuffix(fi.Name(), ".json.zst"), "_") + for i := range words { + words[i] = strings.Title(words[i]) + } + entry.Name = strings.Join(words, "") + + // Lazily read and decompress the test data. + entry.Data = sync.OnceValue(func() []byte { + filePath := path.Join("testdata", fi.Name()) + b := mustGet(fs.ReadFile(testdataFS, filePath)) + zr := zstd.NewReader(bytes.NewReader(b)) + return mustGet(io.ReadAll(zr)) + }) + + // Check whether there is a concrete type for this data. + switch entry.Name { + case "CanadaGeometry": + entry.New = func() any { return new(canadaRoot) } + case "CitmCatalog": + entry.New = func() any { return new(citmRoot) } + case "GolangSource": + entry.New = func() any { return new(golangRoot) } + case "StringEscaped": + entry.New = func() any { return new(stringRoot) } + case "StringUnicode": + entry.New = func() any { return new(stringRoot) } + case "SyntheaFhir": + entry.New = func() any { return new(syntheaRoot) } + case "TwitterStatus": + entry.New = func() any { return new(twitterRoot) } + } + + entries = append(entries, entry) + } + return entries +}() + +type ( + canadaRoot struct { + Type string `json:"type"` + Features []struct { + Type string `json:"type"` + Properties struct { + Name string `json:"name"` + } `json:"properties"` + Geometry struct { + Type string `json:"type"` + Coordinates [][][2]float64 `json:"coordinates"` + } `json:"geometry"` + } `json:"features"` + } +) + +type ( + citmRoot struct { + AreaNames map[int64]string `json:"areaNames"` + AudienceSubCategoryNames map[int64]string `json:"audienceSubCategoryNames"` + BlockNames map[int64]string `json:"blockNames"` + Events map[int64]struct { + Description string `json:"description"` + ID int `json:"id"` + Logo string `json:"logo"` + Name string `json:"name"` + SubTopicIds []int `json:"subTopicIds"` + SubjectCode any `json:"subjectCode"` + Subtitle any `json:"subtitle"` + TopicIds []int `json:"topicIds"` + } `json:"events"` + Performances []struct { + EventID int `json:"eventId"` + ID int `json:"id"` + Logo any `json:"logo"` + Name any `json:"name"` + Prices []struct { + Amount int `json:"amount"` + AudienceSubCategoryID int64 `json:"audienceSubCategoryId"` + SeatCategoryID int64 `json:"seatCategoryId"` + } `json:"prices"` + SeatCategories []struct { + Areas []struct { + AreaID int `json:"areaId"` + BlockIds []any `json:"blockIds"` + } `json:"areas"` + SeatCategoryID int `json:"seatCategoryId"` + } `json:"seatCategories"` + SeatMapImage any `json:"seatMapImage"` + Start int64 `json:"start"` + VenueCode string `json:"venueCode"` + } `json:"performances"` + SeatCategoryNames map[uint64]string `json:"seatCategoryNames"` + SubTopicNames map[uint64]string `json:"subTopicNames"` + SubjectNames map[uint64]string `json:"subjectNames"` + TopicNames map[uint64]string `json:"topicNames"` + TopicSubTopics map[uint64][]uint64 `json:"topicSubTopics"` + VenueNames map[string]string `json:"venueNames"` + } +) + +type ( + golangRoot struct { + Tree *golangNode `json:"tree"` + Username string `json:"username"` + } + golangNode struct { + Name string `json:"name"` + Kids []golangNode `json:"kids"` + CLWeight float64 `json:"cl_weight"` + Touches int `json:"touches"` + MinT uint64 `json:"min_t"` + MaxT uint64 `json:"max_t"` + MeanT uint64 `json:"mean_t"` + } +) + +type ( + stringRoot struct { + Arabic string `json:"Arabic"` + ArabicPresentationFormsA string `json:"Arabic Presentation Forms-A"` + ArabicPresentationFormsB string `json:"Arabic Presentation Forms-B"` + Armenian string `json:"Armenian"` + Arrows string `json:"Arrows"` + Bengali string `json:"Bengali"` + Bopomofo string `json:"Bopomofo"` + BoxDrawing string `json:"Box Drawing"` + CJKCompatibility string `json:"CJK Compatibility"` + CJKCompatibilityForms string `json:"CJK Compatibility Forms"` + CJKCompatibilityIdeographs string `json:"CJK Compatibility Ideographs"` + CJKSymbolsAndPunctuation string `json:"CJK Symbols and Punctuation"` + CJKUnifiedIdeographs string `json:"CJK Unified Ideographs"` + CJKUnifiedIdeographsExtensionA string `json:"CJK Unified Ideographs Extension A"` + CJKUnifiedIdeographsExtensionB string `json:"CJK Unified Ideographs Extension B"` + Cherokee string `json:"Cherokee"` + CurrencySymbols string `json:"Currency Symbols"` + Cyrillic string `json:"Cyrillic"` + CyrillicSupplementary string `json:"Cyrillic Supplementary"` + Devanagari string `json:"Devanagari"` + EnclosedAlphanumerics string `json:"Enclosed Alphanumerics"` + EnclosedCJKLettersAndMonths string `json:"Enclosed CJK Letters and Months"` + Ethiopic string `json:"Ethiopic"` + GeometricShapes string `json:"Geometric Shapes"` + Georgian string `json:"Georgian"` + GreekAndCoptic string `json:"Greek and Coptic"` + Gujarati string `json:"Gujarati"` + Gurmukhi string `json:"Gurmukhi"` + HangulCompatibilityJamo string `json:"Hangul Compatibility Jamo"` + HangulJamo string `json:"Hangul Jamo"` + HangulSyllables string `json:"Hangul Syllables"` + Hebrew string `json:"Hebrew"` + Hiragana string `json:"Hiragana"` + IPAExtentions string `json:"IPA Extentions"` + KangxiRadicals string `json:"Kangxi Radicals"` + Katakana string `json:"Katakana"` + Khmer string `json:"Khmer"` + KhmerSymbols string `json:"Khmer Symbols"` + Latin string `json:"Latin"` + LatinExtendedAdditional string `json:"Latin Extended Additional"` + Latin1Supplement string `json:"Latin-1 Supplement"` + LatinExtendedA string `json:"Latin-Extended A"` + LatinExtendedB string `json:"Latin-Extended B"` + LetterlikeSymbols string `json:"Letterlike Symbols"` + Malayalam string `json:"Malayalam"` + MathematicalAlphanumericSymbols string `json:"Mathematical Alphanumeric Symbols"` + MathematicalOperators string `json:"Mathematical Operators"` + MiscellaneousSymbols string `json:"Miscellaneous Symbols"` + Mongolian string `json:"Mongolian"` + NumberForms string `json:"Number Forms"` + Oriya string `json:"Oriya"` + PhoneticExtensions string `json:"Phonetic Extensions"` + SupplementalArrowsB string `json:"Supplemental Arrows-B"` + Syriac string `json:"Syriac"` + Tamil string `json:"Tamil"` + Thaana string `json:"Thaana"` + Thai string `json:"Thai"` + UnifiedCanadianAboriginalSyllabics string `json:"Unified Canadian Aboriginal Syllabics"` + YiRadicals string `json:"Yi Radicals"` + YiSyllables string `json:"Yi Syllables"` + } +) + +type ( + syntheaRoot struct { + Entry []struct { + FullURL string `json:"fullUrl"` + Request *struct { + Method string `json:"method"` + URL string `json:"url"` + } `json:"request"` + Resource *struct { + AbatementDateTime time.Time `json:"abatementDateTime"` + AchievementStatus syntheaCode `json:"achievementStatus"` + Active bool `json:"active"` + Activity []struct { + Detail *struct { + Code syntheaCode `json:"code"` + Location syntheaReference `json:"location"` + Status string `json:"status"` + } `json:"detail"` + } `json:"activity"` + Address []syntheaAddress `json:"address"` + Addresses []syntheaReference `json:"addresses"` + AuthoredOn time.Time `json:"authoredOn"` + BillablePeriod syntheaRange `json:"billablePeriod"` + BirthDate string `json:"birthDate"` + CareTeam []struct { + Provider syntheaReference `json:"provider"` + Reference string `json:"reference"` + Role syntheaCode `json:"role"` + Sequence int64 `json:"sequence"` + } `json:"careTeam"` + Category []syntheaCode `json:"category"` + Claim syntheaReference `json:"claim"` + Class syntheaCoding `json:"class"` + ClinicalStatus syntheaCode `json:"clinicalStatus"` + Code syntheaCode `json:"code"` + Communication []struct { + Language syntheaCode `json:"language"` + } `json:"communication"` + Component []struct { + Code syntheaCode `json:"code"` + ValueQuantity syntheaCoding `json:"valueQuantity"` + } `json:"component"` + Contained []struct { + Beneficiary syntheaReference `json:"beneficiary"` + ID string `json:"id"` + Intent string `json:"intent"` + Payor []syntheaReference `json:"payor"` + Performer []syntheaReference `json:"performer"` + Requester syntheaReference `json:"requester"` + ResourceType string `json:"resourceType"` + Status string `json:"status"` + Subject syntheaReference `json:"subject"` + Type syntheaCode `json:"type"` + } `json:"contained"` + Created time.Time `json:"created"` + DeceasedDateTime time.Time `json:"deceasedDateTime"` + Description syntheaCode `json:"description"` + Diagnosis []struct { + DiagnosisReference syntheaReference `json:"diagnosisReference"` + Sequence int64 `json:"sequence"` + Type []syntheaCode `json:"type"` + } `json:"diagnosis"` + DosageInstruction []struct { + AsNeededBoolean bool `json:"asNeededBoolean"` + DoseAndRate []struct { + DoseQuantity *struct { + Value float64 `json:"value"` + } `json:"doseQuantity"` + Type syntheaCode `json:"type"` + } `json:"doseAndRate"` + Sequence int64 `json:"sequence"` + Timing *struct { + Repeat *struct { + Frequency int64 `json:"frequency"` + Period float64 `json:"period"` + PeriodUnit string `json:"periodUnit"` + } `json:"repeat"` + } `json:"timing"` + } `json:"dosageInstruction"` + EffectiveDateTime time.Time `json:"effectiveDateTime"` + Encounter syntheaReference `json:"encounter"` + Extension []syntheaExtension `json:"extension"` + Gender string `json:"gender"` + Goal []syntheaReference `json:"goal"` + ID string `json:"id"` + Identifier []struct { + System string `json:"system"` + Type syntheaCode `json:"type"` + Use string `json:"use"` + Value string `json:"value"` + } `json:"identifier"` + Insurance []struct { + Coverage syntheaReference `json:"coverage"` + Focal bool `json:"focal"` + Sequence int64 `json:"sequence"` + } `json:"insurance"` + Insurer syntheaReference `json:"insurer"` + Intent string `json:"intent"` + Issued time.Time `json:"issued"` + Item []struct { + Adjudication []struct { + Amount syntheaCurrency `json:"amount"` + Category syntheaCode `json:"category"` + } `json:"adjudication"` + Category syntheaCode `json:"category"` + DiagnosisSequence []int64 `json:"diagnosisSequence"` + Encounter []syntheaReference `json:"encounter"` + InformationSequence []int64 `json:"informationSequence"` + LocationCodeableConcept syntheaCode `json:"locationCodeableConcept"` + Net syntheaCurrency `json:"net"` + ProcedureSequence []int64 `json:"procedureSequence"` + ProductOrService syntheaCode `json:"productOrService"` + Sequence int64 `json:"sequence"` + ServicedPeriod syntheaRange `json:"servicedPeriod"` + } `json:"item"` + LifecycleStatus string `json:"lifecycleStatus"` + ManagingOrganization []syntheaReference `json:"managingOrganization"` + MaritalStatus syntheaCode `json:"maritalStatus"` + MedicationCodeableConcept syntheaCode `json:"medicationCodeableConcept"` + MultipleBirthBoolean bool `json:"multipleBirthBoolean"` + Name rawValue `json:"name"` + NumberOfInstances int64 `json:"numberOfInstances"` + NumberOfSeries int64 `json:"numberOfSeries"` + OccurrenceDateTime time.Time `json:"occurrenceDateTime"` + OnsetDateTime time.Time `json:"onsetDateTime"` + Outcome string `json:"outcome"` + Participant []struct { + Individual syntheaReference `json:"individual"` + Member syntheaReference `json:"member"` + Role []syntheaCode `json:"role"` + } `json:"participant"` + Patient syntheaReference `json:"patient"` + Payment *struct { + Amount syntheaCurrency `json:"amount"` + } `json:"payment"` + PerformedPeriod syntheaRange `json:"performedPeriod"` + Period syntheaRange `json:"period"` + Prescription syntheaReference `json:"prescription"` + PrimarySource bool `json:"primarySource"` + Priority syntheaCode `json:"priority"` + Procedure []struct { + ProcedureReference syntheaReference `json:"procedureReference"` + Sequence int64 `json:"sequence"` + } `json:"procedure"` + Provider syntheaReference `json:"provider"` + ReasonCode []syntheaCode `json:"reasonCode"` + ReasonReference []syntheaReference `json:"reasonReference"` + RecordedDate time.Time `json:"recordedDate"` + Referral syntheaReference `json:"referral"` + Requester syntheaReference `json:"requester"` + ResourceType string `json:"resourceType"` + Result []syntheaReference `json:"result"` + Series []struct { + BodySite syntheaCoding `json:"bodySite"` + Instance []struct { + Number int64 `json:"number"` + SopClass syntheaCoding `json:"sopClass"` + Title string `json:"title"` + UID string `json:"uid"` + } `json:"instance"` + Modality syntheaCoding `json:"modality"` + Number int64 `json:"number"` + NumberOfInstances int64 `json:"numberOfInstances"` + Started string `json:"started"` + UID string `json:"uid"` + } `json:"series"` + ServiceProvider syntheaReference `json:"serviceProvider"` + Started time.Time `json:"started"` + Status string `json:"status"` + Subject syntheaReference `json:"subject"` + SupportingInfo []struct { + Category syntheaCode `json:"category"` + Sequence int64 `json:"sequence"` + ValueReference syntheaReference `json:"valueReference"` + } `json:"supportingInfo"` + Telecom []map[string]string `json:"telecom"` + Text map[string]string `json:"text"` + Total rawValue `json:"total"` + Type rawValue `json:"type"` + Use string `json:"use"` + VaccineCode syntheaCode `json:"vaccineCode"` + ValueCodeableConcept syntheaCode `json:"valueCodeableConcept"` + ValueQuantity syntheaCoding `json:"valueQuantity"` + VerificationStatus syntheaCode `json:"verificationStatus"` + } `json:"resource"` + } `json:"entry"` + ResourceType string `json:"resourceType"` + Type string `json:"type"` + } + syntheaCode struct { + Coding []syntheaCoding `json:"coding"` + Text string `json:"text"` + } + syntheaCoding struct { + Code string `json:"code"` + Display string `json:"display"` + System string `json:"system"` + Unit string `json:"unit"` + Value float64 `json:"value"` + } + syntheaReference struct { + Display string `json:"display"` + Reference string `json:"reference"` + } + syntheaAddress struct { + City string `json:"city"` + Country string `json:"country"` + Extension []syntheaExtension `json:"extension"` + Line []string `json:"line"` + PostalCode string `json:"postalCode"` + State string `json:"state"` + } + syntheaExtension struct { + URL string `json:"url"` + ValueAddress syntheaAddress `json:"valueAddress"` + ValueCode string `json:"valueCode"` + ValueDecimal float64 `json:"valueDecimal"` + ValueString string `json:"valueString"` + Extension []syntheaExtension `json:"extension"` + } + syntheaRange struct { + End time.Time `json:"end"` + Start time.Time `json:"start"` + } + syntheaCurrency struct { + Currency string `json:"currency"` + Value float64 `json:"value"` + } +) + +type ( + twitterRoot struct { + Statuses []twitterStatus `json:"statuses"` + SearchMetadata struct { + CompletedIn float64 `json:"completed_in"` + MaxID int64 `json:"max_id"` + MaxIDStr int64 `json:"max_id_str,string"` + NextResults string `json:"next_results"` + Query string `json:"query"` + RefreshURL string `json:"refresh_url"` + Count int `json:"count"` + SinceID int `json:"since_id"` + SinceIDStr int `json:"since_id_str,string"` + } `json:"search_metadata"` + } + twitterStatus struct { + Metadata struct { + ResultType string `json:"result_type"` + IsoLanguageCode string `json:"iso_language_code"` + } `json:"metadata"` + CreatedAt string `json:"created_at"` + ID int64 `json:"id"` + IDStr int64 `json:"id_str,string"` + Text string `json:"text"` + Source string `json:"source"` + Truncated bool `json:"truncated"` + InReplyToStatusID int64 `json:"in_reply_to_status_id"` + InReplyToStatusIDStr int64 `json:"in_reply_to_status_id_str,string"` + InReplyToUserID int64 `json:"in_reply_to_user_id"` + InReplyToUserIDStr int64 `json:"in_reply_to_user_id_str,string"` + InReplyToScreenName string `json:"in_reply_to_screen_name"` + User twitterUser `json:"user,omitempty"` + Geo any `json:"geo"` + Coordinates any `json:"coordinates"` + Place any `json:"place"` + Contributors any `json:"contributors"` + RetweeetedStatus *twitterStatus `json:"retweeted_status"` + RetweetCount int `json:"retweet_count"` + FavoriteCount int `json:"favorite_count"` + Entities twitterEntities `json:"entities,omitempty"` + Favorited bool `json:"favorited"` + Retweeted bool `json:"retweeted"` + PossiblySensitive bool `json:"possibly_sensitive"` + Lang string `json:"lang"` + } + twitterUser struct { + ID int64 `json:"id"` + IDStr string `json:"id_str"` + Name string `json:"name"` + ScreenName string `json:"screen_name"` + Location string `json:"location"` + Description string `json:"description"` + URL any `json:"url"` + Entities twitterEntities `json:"entities"` + Protected bool `json:"protected"` + FollowersCount int `json:"followers_count"` + FriendsCount int `json:"friends_count"` + ListedCount int `json:"listed_count"` + CreatedAt string `json:"created_at"` + FavouritesCount int `json:"favourites_count"` + UtcOffset int `json:"utc_offset"` + TimeZone string `json:"time_zone"` + GeoEnabled bool `json:"geo_enabled"` + Verified bool `json:"verified"` + StatusesCount int `json:"statuses_count"` + Lang string `json:"lang"` + ContributorsEnabled bool `json:"contributors_enabled"` + IsTranslator bool `json:"is_translator"` + IsTranslationEnabled bool `json:"is_translation_enabled"` + ProfileBackgroundColor string `json:"profile_background_color"` + ProfileBackgroundImageURL string `json:"profile_background_image_url"` + ProfileBackgroundImageURLHTTPS string `json:"profile_background_image_url_https"` + ProfileBackgroundTile bool `json:"profile_background_tile"` + ProfileImageURL string `json:"profile_image_url"` + ProfileImageURLHTTPS string `json:"profile_image_url_https"` + ProfileBannerURL string `json:"profile_banner_url"` + ProfileLinkColor string `json:"profile_link_color"` + ProfileSidebarBorderColor string `json:"profile_sidebar_border_color"` + ProfileSidebarFillColor string `json:"profile_sidebar_fill_color"` + ProfileTextColor string `json:"profile_text_color"` + ProfileUseBackgroundImage bool `json:"profile_use_background_image"` + DefaultProfile bool `json:"default_profile"` + DefaultProfileImage bool `json:"default_profile_image"` + Following bool `json:"following"` + FollowRequestSent bool `json:"follow_request_sent"` + Notifications bool `json:"notifications"` + } + twitterEntities struct { + Hashtags []any `json:"hashtags"` + Symbols []any `json:"symbols"` + URL *twitterURL `json:"url"` + URLs []twitterURL `json:"urls"` + UserMentions []struct { + ScreenName string `json:"screen_name"` + Name string `json:"name"` + ID int64 `json:"id"` + IDStr int64 `json:"id_str,string"` + Indices []int `json:"indices"` + } `json:"user_mentions"` + Description struct { + URLs []twitterURL `json:"urls"` + } `json:"description"` + Media []struct { + ID int64 `json:"id"` + IDStr string `json:"id_str"` + Indices []int `json:"indices"` + MediaURL string `json:"media_url"` + MediaURLHTTPS string `json:"media_url_https"` + URL string `json:"url"` + DisplayURL string `json:"display_url"` + ExpandedURL string `json:"expanded_url"` + Type string `json:"type"` + Sizes map[string]struct { + W int `json:"w"` + H int `json:"h"` + Resize string `json:"resize"` + } `json:"sizes"` + SourceStatusID int64 `json:"source_status_id"` + SourceStatusIDStr int64 `json:"source_status_id_str,string"` + } `json:"media"` + } + twitterURL struct { + URL string `json:"url"` + URLs []twitterURL `json:"urls"` + ExpandedURL string `json:"expanded_url"` + DisplayURL string `json:"display_url"` + Indices []int `json:"indices"` + } +) + +// rawValue is the raw encoded JSON value. +type rawValue []byte + +func (v rawValue) MarshalJSON() ([]byte, error) { + if v == nil { + return []byte("null"), nil + } + return v, nil +} + +func (v *rawValue) UnmarshalJSON(b []byte) error { + if v == nil { + return errors.New("jsontest.rawValue: UnmarshalJSON on nil pointer") + } + *v = append((*v)[:0], b...) + return nil +} diff --git a/src/encoding/json/internal/jsontest/testdata/canada_geometry.json.zst b/src/encoding/json/internal/jsontest/testdata/canada_geometry.json.zst new file mode 100644 index 0000000000000000000000000000000000000000..e0f56291c4d79aafeeec015fe4db48747f6dd897 GIT binary patch literal 67918 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 literal 0 HcmV?d00001 diff --git a/src/encoding/json/internal/jsontest/testdata/golang_source.json.zst b/src/encoding/json/internal/jsontest/testdata/golang_source.json.zst new file mode 100644 index 0000000000000000000000000000000000000000..7ff5f04767bfff6023a7233c8d4d85faffdd0050 GIT binary patch literal 93459 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 z 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�VZm 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 yQ3D8Mn1=JT=s7JAvd0P1#ks*1+HU- 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 Or9OR7%k>W(sU%qTQmT9a literal 0 HcmV?d00001 diff --git a/src/encoding/json/internal/jsontest/testdata/string_unicode.json.zst b/src/encoding/json/internal/jsontest/testdata/string_unicode.json.zst new file mode 100644 index 0000000000000000000000000000000000000000..dc13c4f47e4e9fe9a8e747bfc064c029adceb906 GIT binary patch literal 11000 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 literal 0 HcmV?d00001 diff --git a/src/encoding/json/internal/jsontest/testdata/synthea_fhir.json.zst b/src/encoding/json/internal/jsontest/testdata/synthea_fhir.json.zst new file mode 100644 index 0000000000000000000000000000000000000000..3f869a2713a17198f4ce0b656959258ced48d81f GIT binary patch literal 53517 zcmV(#K;*wDwJ-gouB9FTRBr(4fb(7)nOt%vY1Rg40H#^s5 z@z&EgJ3azZmh@fiJ8pyH5?n&(Z#H()1K+wm0?HW8dL07VSb~yXjO;q=ZT2^i;KJ|P zG!Sw7(3Q6I)M8KZY%F<6i?OYhtVFq(yp=HQ{MHf2trS1}V;vI7*zwyR76TmNR({ms4+&2aEcmZ#a( zp_$JO@&_ogpacw9L|~oWR4}BJi{Z#PX%-R-7A!Cr43E8=MFa)tYn=-808%Oa?fLIJ z^s{~IUGcQ;`*r(7l<%nWx1ItMjBZIZzDrF>n3UP*GMzKf`aBFbKQ=F5?ZYe(f*_Lj z{fq0KuCM3430S(2{>8e9-=x6gVX=e2dl-(u|G@b*!+eH@9@ z8mm2~EZ*9}XNL%M@z%G&3ZTE$%I1_JC0x)&MI{O*$Hj|1+2VqWxfPS;o<7?rw)h>c z7@SJYF*JpNF$#_)sS;btsTAmI|M^t4)KpqcE{Mfjcb>^l-@#3DOr{oHQWWJcZXOPv zZ+|ZZZjB#s7KzRh6_xq?STtBT)_m`;`96e&BQp44TODUA!{FEN_+2=#BwSHN=Rl^} z>D?JWwDt)J6BE+O7aIyoTH;>cpEUx~0c3~>5b4QuwWd|)XMO`&uM;;Kja_$kM<>>I zeQ4a|Uz|q*q8ZQkS?NnqNHoP%Vh~0ni8!#uw>tTEi^_vjE`{-zqx<62-bx=R@f1A4$(to9J)-Oh7%^y12$QX|QN4 zuz2v)d%u2~=~}x)en_Q20iWl@+r_P|%e1a2^3&KOM!vtm-*N15QlTv?d5 zUtjze1h|!Ppnt(Ap#fAw6;E$PTDyZH6gwzNxfkg>xiUI2kThqd7e74d=5pmN%2s0B z;)ylIrGK;~CN@ePTz*zaejOD?&-ok5tyUj5e|B!Fq>|6+ zk`lco`j%R4r3Q+K1|ldZAdtiY6A}##pvR5?;QNs5w_ae#E)wV4Zg^41H^&4X0xMWw`V%_)&7%PBI&2W4UwY%w|Mq|e1Q23LHN zDH(rCH6cxI!X-HxU1DTZ>6OPQudxd%QI0zes6l`4>QD&#nUM7Ja2RHS{J9@LDV-H2 zB<`G<7H5A7pZy&?_k#hr0OjMs*+FXs$4BmLizfV$`OJYpASTPi#`U)cgDqL7(2R_TBuSDE zk{|?OKo(|!BFxzVvTzi{F%SYG7=j=K2#pv*2qJ_KLj;x>LWm?88g;STy)(wlqO%#8 z&4k6-!i!TL1G*CxhR*>>jwrw-_P-Gt=Q+b4&Qh&MM1&|#z84q%*+O-s0Mfv{4E1(I zjhd~SCf5s7Xj>k4c}fIA$ClC3>`;rO{?3`3PHWbEd=?2gONC`c!i;~@>zU?z3ZcN9 z1#q?@b0)08`?gPer9eG@W!{Pb>-?QXC>DspVv%>91z!j5oEurUa_6KJV-*enfAxH$&$}Y0uK^ zxp^`90*_q&n2 zGCRCP&7KfQLl;35YCxD6e5kJNu)Qwwl)=CXnjZxdh@1jiNtI0E_)Z8rA09Lyfgc*r z$*sqaX-L-5>rFVI%CdAextduE8=n;Ngpkfyjr}wjZ;^5DS^u1lnJH(YX;5H<5MyA2 z&ys|xm*8u2gwm}*EWgLSFRk(Gy(Ixr3l$ul5!PF|bFKz| z?x#Y-*CT}QWawJk%Lk93cA;db|p)wu=ymi`9xbLk51w-Xtecxmm@rK zl%fg)Yhgxm$Ye(ofHI+x2V{@Z3u7MPfpYxBvhMT0jz%`}{#!6E^A(bao6qXDWu#lQ zRyYutZwxyfeQ@yKtXIj?hu~VOHhrQi@X#A4AO)bRkhvR%qlXkv>??fq7VCb-E*e#`@ zba=fNjctS8WNb*Mj5nmk(p)H4v2`!EWy^{AybSx&GbeE+S9(GrW{xU(R%2m;=YqpV z(v=l(s>PSiaW< zZ>D8jiq?LplGwe^^JM)r908NJyor3G#=vZivF)=!5#uUYh4Z%0=0t28_6H3}9)9=( zNhy(UZp;idgXb>I3vEuql)|wxqcRri%kfC4{Xm2pV>Lf80^wXaL*jliw7fdFnM9}w z9D1TNc{?a+GY&t93y+Rhbr{Z(|C>b?;L+DITP5V+bgcXWaj_7zYsm#5qMcpDM<Vm3CnXzbUWZGYBiseJhqey%pOl(7qsVlt*DcR7^oh8{EwIO4BR;|MLkhN-4Qi%CzlSTAR02hb31pS2sYg99!im1< zZ-+;sxx;ZwQQzVYnm>KYoyo%zHdQ89?uj8#kLyG*tS$)xrJFLKFPQy5^RPLQMdXs| z3w=ETPz7X7#=dbEOMwXMgF+}Yu37LRj&C97QyAy#BhtZ9jBM6Mkv&te!4#K{kX4Y? zd(!dz-d^fUHl&A16s6kiwIMk36Rr5-Y4T=fURzgenO3=Lmw)E?sIT{B7QNHfp*B}R z*9+18+MMD2Sfs0}tRn^eAD~ANW&s6Mf-A(cOHl;w8-3O6q-*mwf(hX@U_i%EfA)Er zn*zu+;lqUxxRfH-<|!sem>~S5d7jSocn>;P$1xZ8i#2o! zvWKKss!oubWt}8@Jr&04jkc#UOG(L8?CbG%!?!hP?c{k|*!#<=Drh+{*3-T;EF#2g zifWBwM+TY86ZHSrd@(Rl%f|gjF#Jmi_{!z6uh~wUJcS8&3V871k@1YyE#f+PN3vxG zmE$yUBP*MhTEj{Ob)!14F;6jx=Rn_&p}{{Y5ub?9|9OC73G6^ZF;**LAc7kyYb=1z z`7BC5>*dD`SV{;?>Hr(t6-Rl8W{05&ujH*T9`(!iw!lYZ{g=k*7@!$UURProg7GRT zn5hDJfPNnQAdHX4Kd zCIE3$2c7q518@HZQG+B>@Tr}6+a7MupnGMB>8_|GK==Ru|KSC90c`05hfQ}Z8S9bZh^pkaKug z71ev;LygaSjnqLBDp6D2I*FWX?HQ&zjTH~`g`X;&ZQfS8bC4=^sTI1jLvHsLE|v(P z_9z){Oe_o}N=`phA9k=kX6$#0?Oh>CC)2rtc*bJl7m8n0=GjzdLRlQxSX4N8`NyJ{ zS1TJr3x>Qu{xF7)J{So90x=4NpW%R5BoG4#ERq2PlE?rP;PDXzAQm^^;f-l1j3nYP zi}H*KOVeVicXiBv$kkJ{i(1R`$+{E%e?yg)%7*PRbH!`fY5t58QtAtRIa6$@mSy|a z%$`G=xzwS1jq%eJ*D1%f-0l~CfUR6dGjpYF>%il82{m2q86M_Z@zEmW%u_Gi_>zA# z6a#Vd6aL8trIA5>CPuu!4gVx+|8S{q-_pPF%S3}G41nc3iS(lJ8Uz>xVm{)=D<~lB zP?pFy9*j?%SvZOGgEdfmKjht1Bn!WRuwg9N1b`^YGYK;kW*Bdk z!$qv<(n{F+5odA}3m6QO4I=fJ4NCIP{1S0Ev)~7hG5Y6(eit26i>#j_Q`gwoD^GPc z4~Az%5=8^VFPPQIZ0tPlRbK zoRUl^44ilkzo2k@Q~0DS`V&?Zq=e49X2RB-!&mJjh3B!eRtKq8*50Zm$K#E$8B$cw zIXdSm77`K>3>XOn4GdI}fDi#e{aVDj)KP`6n8Qt*kk51MZl`^2+H}%cZ1*fP(pJ4pG@emFOjDiOyQ@>9T%53v-zfDJ!a@hwK>pr+Qg%&}xdUd%jF{ozswA6`7GkUH#srcVK0zN>%T4 zn4P)D5(c`QdRKeO+)>EGskFqD%|@ulmPrC3Ng9Ht+X~ zPqlQYRaYbv+EQ*V+oiU>J%zs9;yFxFbtZ1ITj$eB+twX&d7c?+I}KY?ikY$@V(r_h z8w?IgyAXP$T)QD>j3H{qxO^1duVd2g=2*(JkD*@Gv+yj$n9me>ed52bSKXREq{YrU z?y9xKiLaK!#cQdB>Gbl3GLcCZ426T@&oB*!Ec|#z65#=oCaYS%~k|Zp1Wy_Y*TZ%nq{p+!%)R4JS)aDO= zo4r;2pi5OW8tfpgdL$oNpwvQ0m6AY!V1fh%BVho+1PO+zOmr3@2N4nsBv6R3z#vc% z5rZ(Of<1-|5*ZO05h+Xx9>OUC1fggQ;!vKZnjL~j5W+AB0wEB=2!|j93PO+|2*MK3 z2tfjgAZwJwZV;rI6DTps|K*}y$TTKm@&~uRwvW&Nh`>?E!#J+x0uT#Uu*4Oy4VWQb z{HR#z9rxyr*p?nQ<*w7sxZ2|qt#R(m8IQna>*94)n(ApMD{y~w`0t%{m+U)n8}y9! z4SA*UPJDpNB$g*!X|`I67c^B!z7R6$zX9G!f|4U9537pFRe1gi>3HP@aZG> z^Z4WC!)5)uRpATCxw%?qlZ{_RDI2D+{vOS60#Jm->+k}P8RzsN`9Zvs9PuG^|OV!IXD_{G?L-%J`{cd7lJG6ysfwuUOZd5eU`+ zg+P6Y)lS4L-qn$I5ob*4c4g$9J_sbbRw3cKJx=+b{_N^oXb%Y z(K_V+(d+(STPO|GY9uLOYY0`p#bFKU=3 z?@{if>09vd-T`B#0N_}B6Sd%X>%NKm{@0p$YH`0DlTNi)1CygJ?#n)@lU!2_EcHcM zLfv&Wo{eh?LiBX9NmL5WF#bTRPq-0i&kU9(OrN$NsLe2gJXwsO1L94K(%Tk}hkLN> zvJ_5e4f^6Ee32Fzj$`8R&5DzfkAfk;l{sghaoWyDolCF!Hjr1|bURz@`zguE`EHa+ zQ7krN3PB)tW;v{|pNCX{-hE#b5^Er%)#Gjij1T@IP6b3h`dy>zDnWF@H4qn}Pt^<_ z4O=u2QK`~bL`EiXoGqr zRYQ|V5~ukOHMW?@sr>@FV*>2(3 zCn!Q~T?9=nUQf!$_KDd}w^|6G7SHGB_~GtV8#Ql)&taqJ6K&%#=9OgA6!n~0-NxRq`CY3xj|9tm_uSF&=ecH;g{Ay;1ngP3;IdC7;MAtQLqmVOVA^=!+- z;4gwVcnsZEcm(O9yC!-1a6{S;vgbtB%d>EKPbO8rK#Jyy>I{VN>LPj#2pWbabX-9z z<5qi-kKkMSF=S+_Sh~Op@%mT7HYM^?|>eXTj_+lQj?aZ&>o4=#5?k zEWVu-yED}7Pe8S#t{i}J;scL8dW~H9H3BfHy4sSqnNhhP`dV)~W= z&g!r=X2dqdIBpf^xc=JqCb4K6Roer#y}x@kMe4de1aICiudd=nT=p9jeHRJX-RYRs zb9&%P#vNRT+uxbN*VxN7pK1~Nyvu?N$f05G<}SEE zmQ-iJ!uKFX$?5@nxACMWAS(P2A=6ruOl6NYIY?^?v&Tn4iRtyi{-WSJMpOuuo&|Vo zH2YPKbF3*{B}MYg)^|$cEn{L-DFxljaBtWNC~Sqx#Y-^r)?9SwvU3EA^qB(R1|3Ix z>Lo`!G=GkH+mA2cfyN@d$ex^1Q0@?ya-Z>p14^9WReomMMVO!1BY3EOau~@QRh^<_ zhc`X?L8h~VhHlB>)z0i4Ig2j+ZCmd>fEygt^?Id;9D1lD$Te>u9|~d6e_s78Poew_bUC%rbOopR$!&3kH;d1FM(+}USo^9+4Pe+rTt9(684t$ ze?Zzw<3p0&OIB(sR7|9$C!mWqhK*vfiTeyTUBq*P0pB`e7=n42q}&RzkDTJ;$0XB; zI0>Ko)LxNMLDr>`J+a$6tZ!kJmn?jUDM!+_#kcgt zwQDQ8mx{S_HaE~Lh*WS9eV09~*ke3IyVhn=Es_8P1d;%{pr9Em@QgO?CX8c;NA!kK7ET%Yr)YzS*QmvO1)+$~KWrWS? zVk+9Zi&3Oh;oC`$%p|jxdx|}lvOXmrKkIp!Jw9lT*mep^39WQ~8>3hidHfGo3b9Wc z-LpOH#(dUL@nWo~of=P}{v-adh&!mpf>*EpAwU#3g}LyE_)9Prk%pu3;2#{~P!J+C zh`?|`0!M1ncnArPe8S9Dx=Eea@U}6d+d03s=-HNcar^i@K@wz>c#wbjs4iDa>9non zYy55uS0OCISb7TTRgwgweb?MjhZg7SwokT9SGq>q^EbYV6A z!=_NzkV0y`RO)Ge#&SR^pkUFMSk}`keOQa00Vq{;56H?b{0$ks}0zjq8(LMfdO@{jn(L?rpcK|wC+ zEkx`o^NbeLn)<|ch-=5Ut?^F!Z6yGYLNnT-C1k-+o-iDaNAmD^Mnm59@)d^$1R5Tl zqLzy~N7?#ErmWWXP}xz*q%j?hwwRRcFp;%twY994c)5z(;1<0tMfI`PV{^QIWe4e( zD<{>fq|zqk#W0H->RT%%jX7UZ)yhXe1|TtE5Tu7l0jL&6MOr)|PRGhz-R(TBAw+DZ zsNFkcv=y3W(R3~6kWhu5I~WoM46l$H4@hI-h%6!&$sI%#xcrCy zZ6%D7ZDx2=V3y2@HHtn60T@kB&W zu|>fQ5)Meo!+WNSV(+>W7ZWZh#{~!pe>^xA${iw$2Lu=z3=nBJobe3>!;uy$!j*%e zZSovKaPjyFE5bxW{lYDnnviAn8NnRQG_1ma;4kwpIC;gJM#0pi>p7ruzA#xod>^bX|?%PF5bY*Sd5aozfI z3Z)gZY)kptD^#ZM2=(g5A|bC;IJRk*WlG(ReDIXi<~XvH>ZVMtwIAD|=T%qc6y~B1 zuFSSuY+FXHokM8n60LG9S&~)KREn>al$lhvUSDH#Xt`Y1TqpJAN_|bL{ZVeIZW%k9 zcdj?$8T$B~Gv*k+EsCSX(NL8kjfVxqET~lqTK=yVO`*kxkn^-5#Km9b88SkgD6yG0 zZi-slm_qH^tsWW>5=n$V78a6^Or!;u#+1gA3`azQaRJhI<(jYL%5{|O3$u0dweyI7 zx$(y{xQax)JydX>@@**y59((_Y1~TUTJnBVN{tr%qPTTL9u5hK3CQV5coexKlnh;w zm6K%+G5Soi6OyBXf2`y!{6iuUps`2jOkZj9w1OuK6lczLJtGS)NH3w zM>XEue74Au;VMLHWQ6WC1xAlCSor#sO*w_Ce}}{-IFLmAktD`MBZ+X79NHz8yoHA( zjfJZ%mOPtMo#FGw->?_izgl~B%J`Qm*~v(JtC>en@l0CYnUwPEA@ebl+NKb#@zLXD z)Bu2HOY@mMrx#g)xC{$87yuV&aA2|awO@x6)W-piHunr z6`f_Z`lN-{X&SR(D+q0bFsQ@pD8>|0%7~1Jh>R2-^MVir5GXDNgaeboB1+;1voKL0 z6b1tULU0fW1OkCWFc1g=90UTvKrj$o1Q|3uMgK;D^;(O6bKle5N_Sn;riz|s`>(t=$q}-lZW3}RAoIG;78{)V zS!-C0jVmt2j~n<-!WgUAPC`s3i7sQK2sOI9rWc5s%fKm@F-c-7v7~*I-OeTr^<^_< zJ`aVb;Mh!G1;;8*51CRr5=nE)JjQy6 zd^}+i97-46K>D)xU@9>?+>nU?Aonb8aJd{j@Pw7FGWwRnDHa4PFmyhU9gOa`Zu2}H zSviIeSLu_qgr1k;G(C>6(M)!jdZ&Y5`bspB6@Y$zg(Atj;03B|CGZzysqk4{Wm9Z z-i+m!$uZx9Flw^+Vk++)5%7N@NYb7=miU zc0dr+39{y`MjHOFaZzt@E-R;K*C7K8Nf5ogy+$IgT->-TJS8sybY$7VuESKu4UUE2 z*2Y+~khAhVJ*Y-5rBTIO_4%e>nDEAU+Z5tsEQ z_|BWL5l4I_n;ppIh&UW+b4yFkRbZpS^to4+4)KU6t_BMJ=DpfqGMOR>F#`#-lt2v@ z$P`0{PymsL|KLhU2=1zvCppy4q8MT;67&q>4|Wc{$D4Ot<_JWiO#zg_EdHghew*Qk zbEs~h2A_bHN09P*+)7I`yX+c!e)<`*oVmVe1C+O=cwj}xexN9r7l$9x3zy1@jO@Ph zl@OykdpUJx)EWu!0@{XKBU`KPFITU7+GG)_Ke0i0K3Kyo4xXJl4aPWcqt8rud#i$C^Z%z-@gw@pk zg8XBHU7@dbo^7`Hd@1Qyv6tdA%_RgjeT+M7j!Mur71AVQUXB@*O!(km_QzlOnnoHX zE5<<<1tRc=#i0e8YdB&l*n-)Hc5vOIkfOPQojq(c=2UY>6GTkGB*|I50Xayts6{0O zn-9WqTzZWeT_WwD9AQVGtUFqdXexcyf#9aj$tLhNb zHKpC99Ttmj=H{-h^+=Fc)uD5lAdlQy(4 zTEPs{Ad~k(Oz-|`aUNZ~;x4DdjQ(^MpJf@iTvJ6WkD??&pbQt`hmzs9Vq`cSf*JTved>DO5x>R2u93^t32h`+)yqieMjQ~}uVDcZtbd(dv5;ikJ~lsg zW#B(7pgf?7=PFLX97CmYD~+ffHKxhjMct+0!YXH>(N?=BpjU)}qA6;ynm~BCWxSn; z;paHidUvcXhJv6Ym1L&UdgSNi2(eF22;UJ>hpY!oo+Ce=3{blxx_SoiMpW3k^2GY( zY&*#OS$a+n4-LeGsnCr7>OB~tegB~`CZJ*p7tejJNnb(L8*5jeD4Do$hi-^3I%ViE|$ZmyRuY z#WYd6hZFNR71ItAnSI)pqB;_JTL2^2qR~}Bcbx{a(X%^@dlPOj`W0Dxyz-Oj-pa_F zWdB@H#%7ekA8*MBFx+>s#punq8af|uty#BzU}GG78(41;dHPxvw~;-4taP-tRFHr_ zEL_rM)6w6mof|fW^p6CGFs(2PeR3E_P}0Wbf)xO<`{z!wd$&VcrC0@Un=;|LLA_-} zJZp-B8*;w9P0+)&AJ74Qo$k4X z$`&5VZ5=lMTofMEscIYLm2(WPzgQ*L;!j;y@PKHfB;3LL6``6S zw31R{hTMMG*jRW&2(~K$4vjCcy#e>fy&=)qnj{b%b8r-OGR(VyfaL~p+~WU9J>5uv z!-Edd4XcOZ?X729T>-MZ3TlxQcc)!w!|ojkfJa`gbD}|Sii)`g8}adkHpF_q0P7Y# z(D}aQy@;^=8pQI$!sc0vRsroEJ zvc?s=X0Vc|+W&CnXkIOh5y?Zt^l^Iou2H@ zmL3PfYn-!PGOZD14SSLw?#JwH(n~a)oT9jFoO@%Cf;~78CNIHBn-&d@X4L>DB5}9Y zvT@Kd8bdZw&nhqnB{!_pWD87&mG1OFE)g#L3q{M^zSWzRHsXbn{c|IUgh*tm*?Z!} zL>%~L?^EC<>Ds^qi%D=x!$)CcA@^p4w4E*witju{{gISmo`~YF;kjR&04{CYfLgi) z!O`t#c0!dXpJSqF4iia#(`86E9zbvLox{=@52tJ%7#E-8(!=)~K_C32)yBbd{XAo~P7!$!lH`K(+Y3XAE;rdG3Tu?XkGmTq z`bskV_$J@o* zK98q>!{1n1NL5;JIv9BkzW|fy1lt^009o2cjgccCGD{9XDx}|_Hsjb35Gej3e|Gmc zk~Gg$5`#3R(E;XKzj$dlw8?;)MPNM$oHbK)ivTL)Xdos4bCPuY4D^2x7Q6EBghb-M z{BJLvxd$DQWH;FzD>^&^{r~?E0tNyW0`CD3AS6^E3?tF-3k?{#dRmtGei{D}BFlrE zEvfvN2+>X@<`lD??;5$Q&~?(T`)WNjxwYm8)!3w>>4esYqp-2l+YSA&$eyrZ9uf@{ z5)>999Dv{;5rJ;6Z76de?}6Bq*G=XmshBUMcgPv0Yd6o@O7FHPhbCk7!M$!p?xtzK zl8pDEl4zx`rEH9Pb(UHvI~az91n709W;AOPE2q=!WXMVS#v-@V`(<}bQ$AmAE$TW* zEF6HCkRT0Bx>}cJ95d(5W74M$s>7u8)AUA&@lwWht9)s;SXdd74^3oK#y>iJIZit= z$(In~9i_K%Z`ZSJaqCXKKnI8Mka!@e>zt!;wzEb{s;P}79Wy6>Q(X)MhJ=J`&E8zc zJ-_}rrYnIg{G3h=@#Pm?XZhX}UoQ36UYQ|axcabALjD`dm3Cf14-GZ>gt`w zTa754JWg(g?yw#cPXYw>+;rXc?#?Uv0(Yy077$YN%qeHsez`$eoKTOEv{{sTwe;^+ zv1tShEZ_jb0g?D0elo6^pM*Q3+AgkH2JM-j4Mg1Z$gGmQkPQ7!aeI|o!q_Z zSiZ$7J{Sh`Fc_G5I1*(WdFSN%gsL}(p>yMDY$zvNyRl-IW$;iII6zb&3k?qlrK&PV zX7kn5lH}MtH|u zwQ|?mk;}9wbZ{6F7Fg2eV|Ct4VL>LM=VV2UCYiEsJvk?HwXL_$>bB@c`~m_MASf{4 zh?@(XSZtmI!U75^Ks+ooJW{4+S+807V^-?x zLHNh($&&CtIflModso(a#TrvvVX_3*hUTDg*Hnxq6=FxFU@>Fws#|upR$H@{721%& zg9i-=cypgUr3zvd;>$s5&>W?qZ(Fs>oW0~~&OkM^lgE*fdTKkowrn7!mvG<=m;$A|S zoaB~<(%6lU-bVG^L`*%sB25)yawLy^B7_GO_M|gkWTWg@{PfeT&O1iWqFrx|i)bD{ zm0CjEgdmMCJxVc}rfgn)rQOA;Z?)B3=K2*|^1e-Oo^#a0n#NwzZyvEKn=$`BjNwz? zd5F(&D(j@JM|$o>}YT%B4M*) zuewg*m4U7YldiIfO(zvAgZ#Dduo8j-LI4S)A%g%A1{{@T>s|RV#}F+#r3axYNm@>l zVrq`}vVhdSeCze6Wx6OwA>^q^TY2m+!xbC8)QROsa#B4=8f8x|-;fK}MccW1no!Koza?F^d(=7_O z7O`NvO2i^nN1nsj-C7PY61JpOX4>auLLPYw8|rm5o6l13I+ z)xF1zk}F-*tH+bR5y`r=S+VCggwqPv-wnP*X3_;>6T4yn1;XzqzdQiEV!|mDugHWh)En8Dl0y6=S{y6le$q?4warkzq{@&?Zt+E&au&+r zVGZ#BlPbfBF%lY-$h?VX2iQt+%@+i%)`3y$o7ST zjbv_tF!vPU&|yfj;H1K={H;=JbyZsktSxZzo4RauGV_ddXxT!(;?-@uF) z8UeffbKiVz1XnGnFL@0^{{70z`NZ*_;c1sp_fy^)o3WH8cI&CH5zk~lD0V;d=1a`y z1VAG*exQ7e_@CKKj+YL3!DWt=BS3bVfm{g9Rb?{NtssJgL~08+ieFr_vt3H_6?xzVMJ@+$z@{q7+`kD9(6boxt; zCeKmg;_cp~c}b@oc0AcD9c4QPyIoAF8MH5|c64q~b<@j2iHp^)nwro{r^l}6BuwfQ z-+^6s{u+zE9V~e>EbN_uWaZ`;Vh?FR)J>ang;h1N7ED~0-6UyD%>(sfX({rBq}qul zgj~FaH>zj@R0Umbr2HECEVSyT--77DRJSe%o@{WR0)5?#Rt=pqE0!aI2C)n9KTU+9 z5;B1E8k4idI~7l|2teJ)2B)=AZ6Sgn)Q$;Ry#F{BS_U;Qw41n4lbwm&0m3_o!ilOX zxZn((5ok(4OQb=ht`F-I7tcPctfDZdPm&~BJ(l7krhB|SdsOtuq{5<%%(!yvi8&ta z6k|+e-^;S&r$_UQ3t(t=%%|j*Dt~Ht&Vm}TUo)Qs75WP83*=XvD=GLZ8?7LL`1qX& zru^nIu2S4LShOd(19AT0`~~&Kc9-CLCz+ao!o1k5U|II}nNfXwr_>yxNgdY+q@1ES4RH$oR`Nw&$eunsk%W24g@b%D_2lM>Q zjpm55bD952DVjqQM^%@@CP!`Dc=Q zYnW_#Kz@y86a-qbd`No54F?=_!LgeVf9c>4=y|fR_^sNS$yBR3LHPA$G%7?g^Dg7+!q{rRnf8G=%gD zr%My!11qs_Z2o^ypGM<<`%zR@IXb{SCNfd^$tCs?r3^9_8IQ^ajd6xI3NTRg=F-;W z(c2A311q3%T{7lgZPsn$45JvxpNBX%rxjo^^0s|V)XQbFrsDkzLi`2(Zu4QY8z zAL5+uBc>=iQ_( z^8@6LYMOP+hlNr7N1TyaE@3)>DZFBs9{^scGG8YR$7jhT{|~XkfT8VL3f9+Kr50mO zS~C|Z^qWR(Fy(LmHOJ!W`zSqx-3%}`xfs@EddAeYLC?I@Y(UY@^j&_ddG~XU?2@_Z zOSxIj3gQ5VX-?D!JC^U80h-VWG$*=i@rE~QzHNWe(JIi|Sk)GcNb8;%z{uc@jR~EG zO4woBOaap|M%<+>+90j894cm(F4&^bwW-kC-#f2Y`;k~dc`^}TUlKvO2waJd+7x{2 zVr-khbEX{h)nCr9phmJI`UCy>dKSRmCEYCaO{&quiA(rnv)W@@d}PYX`Sna)z@3Cv zLlqeW43EkkFPDf`NX&?GwelZ=)ohjAv^1a9?JemW_Db4?uPnPcVtbW9yZd_qKgpC; z!R5n1u3hVvX(zNCYHh0_ajcGpK)Tu*vKL$>(c(EjSN$QmdYAdiV$y}z5}mpfeD{U4 zVRao&szq)`gNjgC`dYfDS_-_i-HHLa=JZ#p-j7|ihBc4zL>(hx`u37B3C#7%v5QgF z>9ma@Z$;z$JsbISluLwZZWn-^B(2DmJQ2$E#Sp)q=)u6d?w2~#l1OcjK}l5y)`~Zr zPqM33T_7%J`G2B`7Qs<;fkPE>gu0hHqI*%#p}eiSlC2>Zq}N*L$_P{Wn`yZR>9f)w z$0p486P-X+xxxb7?U+(*`QLj5D2e8~`ZPwT++N%qsG4^(`U*%uM9~4^1T7S)rc03K znac3y$x+mKi6Yo3YFDC4Q3$ZMhS5s=E6Z`P6h%B~=qCLdx;ORg=tK4Kikc9B!Tz+9 zGMb9qwsapVv#-C&-tL*4uu;m1L$Uji_~z3ItH>3nB~aZC9fhY;MDYH7Q{NduuQrY7 z-loS4t_A~SUon$JPeSlwDoB>R%k)VApqu&;-J42>a+=I_H5HVWd^kOvv0PC)4wZA{ zJ6(Q9yF0<}UKXHJs(KWrA|gcuq15EQrOCH47?5*jkOPJJ9oPsFP#Wd*F%IU=Buf9R zx&$zqJ@r>uZAr^NmcheagAGy3P!hU?G*YN!;|=s|j9OE(d064=ZMPzoR#^*5ZDBRq7pRyShpDOm1@a{kRf29g4BA3-W%&S?{B9C2{HMl zDwzm?dYWE`XJ@k|g6QHUVM*V*zCW zI)>IgdozZGL^$c$Ae}m%)A4GR^0X#1!35K_AuezY3HU>t1QjkigDQ~KOcLAf88G&c zx4tuYEP|ZnHk{b@ke$5A!KpFCTjeP_G)H4%t&-$i@w5}a4uX0;euQGm-LadiLP90K zMORTCD<~?rL?jo9uFM@}%fq|@I73sfmyc3()a4aV_~)hUaCmeoVHM8MsVQKq51BZ$ z>`#bxr9_=?~h$rcLf9?afnWK=|^HORd0{pg8w zt779=ME!A)GB!`Dx?MKj%)6t}4JWYed0onUd~1p;AvbzzlBV)faS=EQqJxh9fI)`I zWyep3&>?Mp^dT_kAlA*{%>t#>v(zEhBVxd5x_?J4)?Y;P3Ll<~5<@Cf-&m6nGkId4 zP3o8B$xl3tAk<^nT`28PWS!wCVQ`DTIGME(Ulzk5wOFUhj?2EOvZCeI|Uv&JPD;j`21nf{Yd|+?^YwXdr*uSTI7vw2(6TE;;f9^Z=afZ(sW2*KrHEisJIbBHXiTV#&kE@c6DO>UGERGjnP4#jId=1qb!)j`Cu1ojQ-fdgus!PzE1c!hrzdBaf=g$( ze_5%eH)}itGQ;3EqH!HWxU#I!lh#b>M-0n@wFXa_bFC&#Y^j}!9Fs8ZGJ7Y#SMtt> z0(S~4N9oomZ99Z!7td1KEaCK|GNh0mB`#j#5l$wR2BB_-pPL4iv4dqaojgs-A^+2O z&Qqh(+O;cCRTG?j@DfY7f=hQenP4nGgtm&Hm87akqX$-m?=D(OL>WyhE&L?$hp7Jx z%3H-kuDr8jpsM1rirThhc(LT`$%H!@Ib5uWlZnT=$2r8M1>aCYi1@LxdZ{PEM+7g_ z%1u@?TuMH0iame7Ld8i4zCvUtjVEM;*6!3&C6ftGv~lx6Li)za4n4WBE@+Gy$m(>J zsczIV=&Q54=d{r9l~ChE^l(uzhG8p?B_G{T?wUFd=cM5|(vB^H`Xo=Ruu|X_ovO=> zw8?Z@pxbHuj$nZ#(OS&L&k&{;LGh-t#%gUS_10w#9`b!HIQpbJO>p=w9OYcjj1Acg zNK>?9G%_+nU^}FNwugrlUT(SZ6CPqj9dQIfBZgh58ZU%+xr;uZ@C=D&|Ah~WBdtV< zA1W)n#TidT7%$C?BMPT;PLRYXRZ@yLCDa> z!k$#%l11^i;+Cs+Q5Q3}2vk|-pz3PdkRfJK`x8$+A)UIvN21wq%-iKcbl+uboXmw6 z#>?j{Qb38b;8LdmF#=m#fQ(pNcpR(*uCOzU(l{x$mj9#surwf%_W&!u{Ur;b$|*Cd z<+)CfTL?NL9||9%dw2|{u)52JJXw(^+CG{=O^1!!g*VB1vFwTyu5~G)8-`uCo_4V2 zA;jld%=*OZgCy;vZk=wun|O^D)IU+jA^$agj4*tnLJ(;h1V^`|{s~UI1mFId>9$b2 z0f|I|ugtgmNYER1lfuMsY~PV3p4TE?oMfj-m_2Q;nLOhp(>s8?JipDjBZN_1{_!Nq zU%V9OAJKG3H+M3JubTeSa?VwrcZD)fLb4E?M_(-BHOw+58(wgE14mKt+1 zKQDJ;gdql|(atdGkP+a5L+GMfgdm|2gn=pO9JB6HAP9mW2!aqG1UCdH1PXzLAO#3Q zz!4x!!eHfg2v{h=5t0;aDxp>BG*qFbRxr&ZD|w3ShFlVf2EDkk%5~8>RNW|h^u;K$ ziZT)DVmh4y=JiWlPB_#Ef%kZ{fMNPdBAwB>3O31D*K;fiC`uBhMpbhWJUfUQJd+FX z!?s|A=esg}WkT1Wg)ni*y12JN^BL{thBIXU@aUmrF&0@7sgJ_Lh}w)8{34I;Ot=D0 zftKk>#1oyy$I+CHkHPlyv36^Dpc`0Ck%%XW`L6Z#1 z#jM%OpcBNLy4{^S;6hvuGPK<@_g|ibg?YlZ1mI+#k{yZwug0@6Ez^M+tRQB!4E@oc zJ8s0gvtsr?voGh4%LLv({jnq^A?byN*yWm+;{ziKf)x4(51rIYcR(k+lv}3t63@Qs zOLs*sWF?m;+?QzdvReM*9Z-`#g)!C77sTfbz#DC3$ceg(1FS5qp?Dk3ZUsOio4kty zn|y-kW|Ke1vzIR{=Bd0Lk$+}|=7@w~CdwA?@L4ISwa=lZkxqw0VNU(FGFf?%uHAJZ zg3z|8QU@G#iJu!cE)Fx@38oA}C&oce^dbl`{yYk;HK~tixv^r6$N<|rDyg1zHx+x7^ zHRU+!I0^XxFjU>tTX=Ek+wFN6jhIw^Yoxm;b1D~XdkaP;=xS$Wn@HDCY`4VNu30ec z5ndeVyLnUhwEu1SFrUdHt7?iXu<<&o(o5S))r0(wX6e*Nz&P}D+a|zhs?>7iLFG@) zM2thL_p=&;7JTi!!(}eFJO60)yG;nm;8B5>t}`wfnuPj28Zizh8Q^;&3Plwe;&}N4 zMdPD3q`v@EV?&6S^&Ns?>IF3mMd6}h>7P`i<6RTulI*N@XPNi6p^JtLm4Uu3V`m5x zW1(OZRwBkh$vZYN1f4M2((R(i0M-wige=LeVF`+`~@&c?xoxbdfMNA?c;K&doHV&SrT zX=RvM2Z{zX6sIsP<`qGMJ_3MU}Fnliy`1PP*4dWx`^VTOpvlor4Sp_{KrdaeuL6}_cAfP*cMiXh4795fg3Z1}$4K?k{o9I{a>i}F#CyQ%D_MAQzeIDU)pDQ5LYcgdg(*L8sC;KF z);K%@X%Lw?CI%A_kJoa;I*%84N&p`MmiHY6%jcFv zOCkV`aD1TLEYa{TExdo_MdY_kynvXq<+ei8oiDUXbDK;+UC?_41*!hHtf;l_9GpJ4 z^tX~Bf-cF{Bv1&AzfA|c6jXurVS~htwR^1*T5tN+(#u{fGo<+lA#vAuXV@MjQyZ4| znr-p_ko1SE0Ep}F-bW6;JibB25x25S{GUKWRkycrG{0dkS2D`rNa9wzlQb=A*kMl$ z$1)qGCTnR5I`kjv*rJPsfs)25n+O+NpYRo={aEOn}lqgQ}> zvdMBr-I#ubC@LB*J(y~Dv?FnSxz3Cvb!=t;+O}928GxrG9l!|NV+@4b(kEcUglU?l z^2$wu9u<25fB}I4;lf^r6M@$<0Z)2RkT6;JXLfl+h{{WwK^EaUqAgy^2W-*Wli+LO zEqu7Dr70z@?bc;*WOUXT`Tq44GxFXrw(NmKxzKTY$TmOToSTUXsU36P+a2 zkPE(ol#O%g^o}$$u-2jW_mf&u+$~jm(zPtFoxS0fSVsqU41=9$>o&QTd7w4CnKN(eMWcNN+c5*c#{_Ep`YPA*W(k3Z4jEc!m! z@Q-eXs)%kdHTbfnYrjO5%fK?*dM7Tt-cXs7X%{!Jy`y~Y9_AJF!6GJ4KAelYXbQrX zuaI&x$v7a~tG(6ay9!vzYZ#iM_?CBd;hB@MMlu?6b(U-KRHShRx$!R4Yk$%zK6&t9 z3}GaVAYN$23a6cv`HF?S&M=0?WwT7E${R>0&fU`UZae6gu3KN7BaOKdFi&pB3;OS1 zrwNiK)Hv~_&>)xcKS>`i`}LONQ*}Tj&<0MCZ(liW355+oeil!D64Ld#hLr;xXaU*) zox#NFS0dJaAweu;;>71&ngrtzLnDdd1qGc^L1JnxmwZAOSRS(OO+sEFbdZ)JUjwSJ-#W z7z$W8O7tw)iQShBE4^y<7ELNp#yQJ}aKfoV&hOSPWNM9$F^$bT5oRt~!fr2!QruQF zEtFu)vOG?TpYbY8w$QNc))Ywkx{zxI48rjaWlclQ4tHE4u=pUV)`DC-E?OZwJWX;^?I>zM zH9p+x_w-f>`=q%?b`4)moN3t@B2&@?`F1Tz%?5+i3KzUO(KVKJ7pygDm36~oL>uf7 zZB3Eo;iUzpK4ylAUOE*O!-AFug7kK|dR>TSMRiXEG6zOXSFsxG zLh`tjA!ucKPlidwo^B-n4uM0o|E>h5ma!I(l0LY4_kaKb0sxc_R}~-#5CuNaOfGWO z&JY%Hfj=>`)>({Vh%I9#A(WjEw>L0|vAfuDs325g`~NHhO$x+Mem%}5nOWv-2p7tF zMV}s%O#K*6$c?&?;t64?DAF^NkJTX0D6E%&%3^yb)HLL=1l|bIK23vTUWJNXDr~-O z((6*VO%z8xUdF?Uo}S=V$v6zcN>~_8Mk6}N7@?&yoN$GfdmFzhb3v@;SDeyLE@Aux zxk_{ejtPnDb>^X52nkp$wc{l4uxPOJra&}HqnTGz8IaP{B@&7p$={ZzJK@A2ZXK-u zESD51tH=hAG_)upy%Cyp`OFiVLJ^r12x|~?>ZqTM67qSjrfWGvd8AdG!CLzWBh`f6is@J(&Xc1K;uS`d z8eh%WWeo-6V&EZN5vPdJ6}mj>ZkM1m$WV=MX%CHz%{Cxh8~$cENS4SYw@+|6NN=6z zAy%F#4^J{zz$wIq?;LzAL%PBnPK4P>64|Y7(M^JQ=yGZT2S*SF#vpPGr4Mo#1VIpl z03l!qZU_(pg&;u?gn(n1f`ouW5F{Zg+75=Wr9$w%%gaIUJX(~aEAQkED}cI4LN|A{ znJbpFeJ`@|i&dqZj9@wBw;&=adw-6>55p-B-N{eJ+i=(KvR`I(i@C|pLiShGQU8%( z@Iqs#yyCl9a(sRpohnCxZ}iNG`Bw8e;G;NYsvgePmwux^V);-+`Lpa{A7n? zv`Vmg^Thn6L=RR1rm18FgEb1~#&>yq3+`J0jc7c%Oq^(%S)Pt}+FuyWH+i___yH(1 zSDEzI&D9Fxrpo7{pw@b>nO;cE34*msP$DfN-<5TXfyTYnZ^ci;^!gN7E%j#I(n7PQ zZ9i{|nsnyPXRDo?vWGVD7s_W@R-K=bsn>7u6Y#S50)k{htS0Ln7PkM&7rnVMcyr^>ea&V!G zZ5E^1Q5D+yBM{!?=2Hl>Zf)2-Jn}$mO^w-w3D%11P9>+gPa4$*J?UG3%DG z0ca;vp{P;@iM;rBB$_z2Bl!zJO&helOgjvKPd$Txf>5QTaadu{%c$mo-89Tgc1FQ7 zzzbl+LMR@hC*Le+mdf?%^K;jEg z>*S9g59AGKdGkuRf~-kd^Zyuze#C%rbY6oN@aLYd_MoN%KjW8p?hrxf;wt&P62FLJ zZG3)$pmG<6b7~<=rh6?~s1l5 zS;5)JyP+YR{9hJe#o3q0+g}0sC3{fsWL#$uGp^MQ4b^6C^9Y7cg0M`g<@Ir&S>z!}+_9Np9 zi)+lyX{0}d1snTzE+gSbba1P9s2exoE$#uST10&(>%;M8s%-dqEYpM*Ro%J$^BR2B zkW_hOLvSYw9BakBB)V`?5pF;uLmt^r1ze(r_yRy97SAd(BZ_8*r{mS`7k=}L{9rkL z01M64CEdG@LJ~otmd|HFtrXo7jegq`1Z0)UVvFGGN~IC#+EipK&PSJB;{VTPkN&gk zn=V6-qD~!i`8(B?q@g|f(yjz|f<8ypnp{<>OlU%mcTv-2v|}o!%QecMIu<(WTFN`o zz|=-NcdA?zEXN)lgEuT)2z^RC#oJE^h?R?yB14ZZvD+q4y5p^JsLTJVo4y!a>~bF% z;U3kx+8p}WQRzf&*}69kU5Yw(byERJE4kYFK##t}%XfhWz3|pL$o)MknyMD^=nvf` z2{HN|>wx!o<=+OCl}@oE^ytE_O|`tV@-4LFKk1c*xN709tN$!Qi%!MwG`0syZ{tfg zm?yxz3$BBsn^JL(jdpJ!mjF}&-#Jz&s-B_cWj_!@52TJrfC5m}hAyvQg8+Jd==p{j zlt&*5L&Z(Fc?BD+(2A&haz_=QDmzo#G~BE+L{wS;E2$6`UjbhMU;(<7bpZv@f=Z03 zPniiWgTskn@uC5Du!mhst)V2N%p6(PX}M^1G>BBj9euf7^6}~n<>CZsJ7dQ0 z55k)(>3l-}LxLbI5pKBBip}y^0}Sb;XZju_eOJPWlOc9lU)ma&NV!R+XG0q-58<_g zu69ps+a-gZIE5hf*LHqNv&qP`Qygq1k|`;hU2O%nA@9yd>`=KKEvI#|c`SUFc{d)u zdgjN+ONDfq)RU|$p--XhtGI%OC}av@_-_eEZeLkchCIW?8O;8xxNQsd66RXa{-lFQ z$x!2zK1kH_IE7-NK9>%7f_*1jwAjnYthIPaYItMlCYV2@262Hc*mTl6CTtVvmr8T~ zx_xSJ<*io?!-;5qAs;W&hiwC!1Hbw z0u2?bnnBQq<=0Ly&7?<36c=JmZWouctd_n@(hq%)Iun;KbUuajU8ux~(Fqa$s7$5! z?X%a#D(bqze5@#;$yfB};YcxOLqbHj6)}3!!dhl^3 zH=kf$5G=dEmU)@RrDMbu)CBafj#L?5grZ9r&1?xp-R!S-lc#y6H5;JdP80tFGsv1) z(a{rnV)23eDFoLf9~wj2~F*Sbd8l!b~3dkM4D<`#-l{TGs{Lnjfu+f;l`6- z7?*(IWEid}>AODN&CJ=VVxT&{w}Dj8Pr|H=r=H;5%4kl%I<_Lh+9bjY@_+w-Xz;vP ztBVNZ#E^mrCw#6gJ6zQZxO>N00kR@y*RFd?3ul>{6R#OEwxu=#IZ^p+_Hbi#xdU+Xpi1N{qsazt$U>V{T1>4s>j0wei9E!E%y0U zqC33ce%X;!2S;w43#H+Z9y~lDt>i`&4XFcJuqWBp7Ct-)wm~S%JLIGV#D!%UVCu~U z#I!^KYu)xKkVimC(JOz|7^H*XA2lY>G7GXSYc(JV@%bfVyd_zjq$0@)VSjr3(^f;& zGKx5IF%>86HKasy@F&G)Z0xF5!5o(H9Agq%f6jS&#Ow$@f|AQq$R#1n8-acxnT?qb zysLYPLE4M) z!%a9HNe*lA8`_gePNA)cdS$4rcf5hlV%YjBotE%nh5k<7O9<)+s=0PZccybV6im_v zRI4hYh&IMLn%X720Dr=( z-Ebe1CTlU_Clm0b%nps(W*G~K4RiC#e8#u_3h}WFsuPjJ1^e>+@pR5Of*0tLa)h9v z2?+~P+c;~L%KIL^oevw z^G13WzH}_Tju1Y^6A@#e06RiZ+9m8lHhs%=X=U9vHzu%<=26;HsOAPIl2Pb?fi0A2 z=ek&)OAI^9Gs;kLxk#0Z%rSeDpIp!V`$)cj)DTaDqDdDTVHfipJs*q~8&b5dd31t2 zxC0&SKm9VLFL&;1f5qHhuS z`2f(!B@g29etD!6qlq7pAIH9MnH}W>M9R+wJF(Cl!X#POW#i}(&_(Y6(BGzgVXfdf zb6P3&a3qXDIclW8zC-q z7b>}<6LrhHJ|GNBz3;)^>Imrw zB*)YwpxX6hqJjR^`qw)x>q0hFkTo3)&bzMyB0=W>wXa2G_BXw~{;Jf8L|CMI6(68+ zQEMj`UkNr63m&?^p%l8|S6Q7Q*W#rbJSK?V9ia$-MxHVv_|6Dbsh8QTVX zj>f(F~ymd3L_BadtaWtC*t5 ziE)YsW(+C^vy}}Ig9aQ!%28RA!1jhaG6DP7L(H%QeFXz_85x&_6=WYOea$kSxQyh` zQZa9E5RjQoI{~u@u(NM4E4)^Q#{8c^#)ZFKk4CXmdkRvmc9UpF-MZ|RLo}elXirgk zPMXu3gxRVM$zItNujRkPznqp~4tN@UBEoSysMgr!qyp-6Ar_mkV$JSeAy-h*)kki0 z!AESxQPE-fOk#MK!9%9F$V_c-!CIeO1G_BQIb|<5w@JwUq<&A0Ta7#+^ni?BdVhYh12vJct4|+eLdnC* zk80l%UcfpWPQ|qVj`*Q^i@7rPSpbcEJhaR<*hD#*2|k0bqWVO>QTRgFe1oEt9F_P$ zYJ}z(lhwP_fiuv*ufMkfR8I>}P-}P&oJLA54nd%jt)NJ>OF?9Kbiq`7;2f2>ifq{0 z6}h_59#22-7~~q2B8@hYjD6je7%?QeH`T5A<4R@{4b9jk>COT$P*Ihn~h_U1IGKm?`nx@ypE_i0HgwW>&3opab3EDOF8ohF^x=AX)@d7G2 zqtBs&16>xYbV4|Y=_+Uf9Uj#Ryu(~vHqv=iZhmC3UYk043-u1==wwZc1suA^JyPf^ z>K4j@lz@nXfU!$}hv;Hk`85&?PP_APWWKgKlvAB#aF=6BOnYejRI}57 zu}cczJ#`30A+c?A<}FL|2tWxNu<^FT2Q*~rB$TJcPZ&xdO@uZj7C^3V!TS6?#(r&^ z$!dK#enby4>MMOTCMx5x0x^Q> zZvW&wEhOcT#Q*913eUY5OY$I}ZWlR(Lx!+*eK;DmKwrUtL?g?UPB__vO0R{+$0T#+ zX$6Ay=K9X~YJuY*TVXdeG~)ln*%xtd5w^73u~A9Y8YdFc*X50Dz+S57jk!97B^xS4 zXs&HZaUmh(NCVVb086ZR7H0uv0cQa;m1h}HF?I+$s?6jnThjN0LwKQJCs1v%xQ_RR zu!__aH+Um<3YxhX&dC&(>{oM#sVkc!Ue;vAfVmnnsZ(@Ni|?HH(U8y;vPYbn*(lG= z8Dfy*wHe2(Q@YJ%2s)wgExUcYS-3-bW2v>sa{=}(yX6lXvNc*v=cGlp>{BhTu{L%aGs`QM!4jL6U`b~9kqJ&VJ<{) zUy?9(<#0|!jTGnfL&1n!nzJ;P%xHloi4kTlo1|~UoOrmAs`u0Jz18RP@j^)8U%_A} z8(hd;_GmK7JgQZ@L=7lKI3Dyd#t;u;1qn}tamf{9b^Z^mL7yal2|F%b<3t6&xY_V` zoFGl-GN2r~05f?4%E8kLFau_^WI~rlaV~}FTzK__$5Uz`j(ODpPdS6x%xfA*$t-Bv zvT7%7mNGf5mOe0F>kJuH@QQ_LoMiV%n+yIO-85#Da*Bj!T*kr)6N_N(BRklvKo+NG zq4JXt;YHK>ZI;}LHy5*4UeL;?o`|7C#MYwcj1de+onTif@0Qv>LA(;JHXWTbOjOLr zQ*td~@(eF=qmOK7GpmL6j|2*thb5LBWVlsriig-c;3$0|B)rsXnE}u z8zf|&dmM>$>@EW~x z7&jv-0t+jhxZy|$HeG#|i-$UEyO@D4PO+Xoj<;-0OK+$o3nG6jM{#T}Amid&yGy;( zAT|5iSU=>jOjV2WoN$Gad>lfw4%dtPen*zRKAG?(y2e=-M3}+g6eHM85|=X+J)+yWfIy}R`iMTVBxZ0{FPpIkiilrOPsDN0LgH-19-XQn`Pf4w|?Cl9@rku_WGg@;G z@t&(L&+o#!C#|W{+ab^`Rq?1uL79R(Eq{LUJx1_`7Olp_6RSDX-I_Jx-A4M5wJ} zDjd4OL=a-1EIaDtCIp$GtU8AE3XxabB-o`$oTLetzA^e!MTo_QxkSi;()0M=N*fVl ztgzZANv9ZzDQst|3}=?{f*CI7asDwz!rO}IB8iWq_qsgJ9i^)bIQ$q)bV)}Nx>}C+ z=9w}!6 z-x(SeIpgdS8h*i;JIU9^PTFxEFQK59FDhD*xs`FNNN(hR4^iAW(yi7YahM{9Ae6v8 zA!Y7N3hhjehE4xIzXK5Q0OpMl9bITZ6M#-(Z|vl1{MIPMX=HmQ9&E~k`-~*t(8I0m zne4a~4{je&qO~!e9G+Eorjda?j}@U|=Z*L%lYZrf3*o*o(5NSPe2YCAb|XVp^*;JJ z0iggaK!8Su{14gx63lWRk&p3TD4C)14#%82@gwa`Ei_j?VW~|Xu2TEiDy+4RE-7a& zx}g+-n-nG(9OPD+t+fOJsxKkR>eV|Qywp;C)lILDjzKR>s)TalB+&5#9-Hz=`UqT=(SHI86<9C_f?8M<@qLF4)wu#tMX?;iDqscN(&i4y2q>0sugI zcJ3C6k{K4_J;*1}Z%s8xDgXsxVh;P#I7U>sp7p8Bx zr?FJ?GtyabYvaq^0n#Ojtzh{OPJo4tBG+J|EUzn$oOT9b)69xRHvvz`;9i*m;oL){ zvz5j}mDx2nvq*=Pw{0N);-y1Uk@}a5WgIibfI{llq`C|cw;RYLB z6+l;9-fA*=@+mGW+ADld2FG?%o49hfv|xxqnh{*hbIi?2X8q@A>bY<83;4GOLL8yU z(S?SUmq(CIxp{st%9W&ep1rA)>_H}Uj5lYQX+A^$zVd?{k~3~?914eL_4hJj;Kw@( z6Kkid*HroX*vC;><(|7VLT0%b;;H^dA96^X0%#;g{t%o%{z|;f;J@S5gD;%Sm9hbm z{)C0*E=wSHuW0fNq4sCHuoe>C8=VtPe%jFzR)o-ttF|;nRCI?a8J&)5(&AU@F(s}F zZlvpS=M=q}>T7b1Ql!yk$Vwe|A!v}f2nct`eB^p1GdUSj5rSiP@;!py3TFfQXjzYC z^5Imt(Ko!pP}!wTQJG+FqB}}PCF4d24g_@kt?QsWGp&!}f)a0NeugUQa&(tV1LY-S zCUSrd07alJB4iHTY>I^vg`uhmSG>zd6toX&8rqG@ z&IIn*=q}#SJR6Ow+HlngZH1a*CD`a}ynWwv^eLt+QEj~xc;rKsN)J#pS?iLpW1nf_ zR3QqA5S%y4*2PSRVK5;B!y2o3xlP|S>{XG>@Uo}jGBoR z*|aOcbL7uv`Lq&pTD6o^yJ$$qLJ%WMWMwwv!p?pijrl#ZNMNi=#N?zq4lqx)JIJV_ zo6-_9hJ9ER9TAAK3Zl$BIx1b_pi0%qws?tYD}05Lelhis(;dEj`Uoc!95x8)LYi2L zNUcdEF?ag;OPDrMh;HMMG+6`Lo>CR_@edARQ|BsK2#@oSYOhOm8`v14%C;!o5AE^% z_ObAsE8W*hwX4wEKBD;^PoeN>3_Jz3ltB8szuj8pZjhy6kQI5|T2rCy6du+&I&2VC zWlhqoII)3C%$_4e*r`H3M@2&<1NGMedNGj7fnELBP@FZoEQ7*kevG=vY|Yf?$svVuA&C~^^m6G+(^Wj|z)Pa5`}F6`#ss6t~Ww}|-& zpRKwHqf4=^DwDOaJ6Lc>@c4w(niEY<$W>6GHJZHC7+Ic|`ykOB9oIvM>TJHn!32Aj zTI5HZddfNwl4eRm8V1|>NSu)Ha1kLj$5QNE(1&h0{GZZl z6}9e5YxzJ)p)O}1H^UxA+&@gIGm;Ry%_~{q@gkil;NO;9p+a=e7`9UdKgs8*D_Ks@ zd8o%{o6iSV?!@>DS!no5n4#k%g0Kk>gxM->)%f*gn>AM(m@-fas_IC7k zhjrI2{wL;DB0OvAjffy09fJo+2!l*m7G)+SC@a}R3dMxW&mPGz*^wfoq_%n{=HPZDnlSFG9u~B7tlkhqIjvD6J9a?Dk!z_wtwhEy?IeI4y{1h!4;&3;9TGQ0q z7Jnb88e1T_WIPA?VXXhrk~?HXm=>NPYQF9?>n*NNbBsWENVk}#{v25{n{;;l-rZRe z8{-C6b-8$$tA!feEcJ{^MGtM^*&#k?9Ogd8!#EZ#KJctik5ClDH$KFMjb*78MDkS_ zx|k;Hx`v5`A*~O&p|e{dnA(vQ`0Ns8^AW=I>4O4$3JGq4c1eDTgl&+aLB!~a&jz9~ zJ~FOQFA+szZ^jW=!U)b!GwGot%W&evL}1?WKTT?fi1FE?Jz;z>)sD;)cp+Sb=^8s+ z_Ic5qQAD*a6k?$`FeTYMV39l;y%Kw+iw z&c(N3-mo?22zQUe_4!-?$3KBmUT8e>n%r?N@AE0+FPQ`FVSCSze(Ea=@c=mcf#3Uv zl=-2r$rQIRi{81PZAuYh;Kw`MLHkkzNe;!=%=5(%MRkZ6f9#0~02*QZNtumk)5;nq z7mU8ZnB%f?LANJ_v)57O z$iyv!yIhDm!A%`4c66gq8-LQ#nuHu?DdVyc8ExGigxN0oC^dz|F)skPGzGXG z{@u+M)X~pWwE2+HN<{GZqMlwvMx&eQp z%37t|kkLiD2=5v+71e@+POUICwZ&1$jK23pbGj~YYH0Elr7}4bFjD~SG`HU|ABs-p zPcWt#8SRFqO}#Dp3ODa67Zuxb&?j25>_{pb6q#{G|Lrb;Z270v`lbN&5z0VJL=f&yw^qeMt%EsECD`bi7QPcDbUu|F@?R^e!9PKAyZ^`m z%`4S>HUigECj+d7Qw+_#jujMjDO=!r2BNxbzr<8$B?NJ4Q)~zeeBf8OJOQ})ewtZ zJIBJikmDY0pPib|6?C!3b@93xyLtSR0#vWO5nI}Q3b;yag+CvfzI_K&u3|-wO7f+fG2ofiLXb}^oI6=V z5_J0X&zd8r0&DSE|v2JjaU^xBdWacNHoZ)Bk5<% z>i;3XwZHJ0Mddk>`C$vqIhergE>;*cW!D>#q1i+}7t|U7d4%AUbl39rRBj>n5#$ z(#v)v_4HIKyL9wPoJbvP9HQxdx;OOVDfD13kN3KuipfI&?c&I9j~=2DP0n{!)f6o# z+huwZOCNR6Bn~tah2~Ys4O&hI%7PVzj{e`4cl9Iy+X3sPVa7k0oVl z$wa2&Z>9!6VLNe^uSsnYKA6LX5)(V^u3LM|JXTPO}qs1I04@vQ%iyB2Otx%Zp z|5@hMPGn!&>M|nuun2KTN0>AFoSEh&Eew*CCqwPGD~3D6a0nkkhz%iRAnzhs4LuQK z#T=m+lH^mGKHP<s=KTgY<0YeU(3%w;a<(I;zOW;LP#cqqqR(h!OTlu8Sq2$-bl|7l>DaH7&C-mTV~kNaOyLVd27c7|bE_H% z!`y2e#SU4+DR3KE5n*rm2n!OrrFy#i zV%|3AAsXgKn86p*)=_=)6 zK(MM~9kFTa@>?6V(|Y^s4=8yss%Evq}P=# zsH3tgjFrS^J9Sv>AeX(PtNf(uKBpXpg?@%mo-9?C%aJbeb19^J zYJ5jr_!LrNvb)O=w~lXCe;H>$N+`9KLxH2GVnW`B{A7lbD&c^nS45UelF1`am42#- z91*N)huBTyO4B17-FR~0B)c|6=efjBj-q3tNe2t8VHN@fV(2p&JPLE=bsTbph*mBu zZeVw1!R>7WK0^+ewz$bCE(M2pnO-7h{|}CVVy%9Xe3g=#6~M+uC>!RKIC)^@32uQvxJ7Ku$(+B zlBb3EkdsaKmQczHo(?Tt6n9=o3~BNiN;boG^N>y|sfFk(&8QM{C_ZicVJsqX1pSe# za*yc(9aKV{lm;WCrFqK6#{#**LMjzle7LKBy*MaBh`6GJu-Yh>7$W|;C_LfILx0;n zVogf^DxImSc}(fUbl-=JB*V)`kPz|c5~=utq9$FwbWu2^sG#$x(2%81i(hn0*VGLt zP)HW`f|-+LVXWAPjCP2IMRDe(FX1hzVQ?0DFOmswLso>*avx>>j2uc*QB{u^muLeE zM|91B>dGkLFd|0>8*+-IRgb8OTWf)K5iQvimT@|DhhUQwBaRe%IiHwHW)Ffc=)zh8 z2PY&f00>+z z1r99cM!s7vqr%ptV*m!!trRsEK6TC&$>eI1kn8!XOy$6IS9Y*G;h-La-aw8nG=>Sc zRdLnvoA-hTz1vvOv|#z+XBg41U0KU$(vHCh1o_Zx(w3diK zEiH5|O5ErGsIZ3wLOM$&XSpI;yE!;QM!%>@N8BT&)Z|wiPALtebO%a;=9)4c^wOS3 zII1nu0pm<2(N3p{qb_*bdh+%-C6d_PVZw$+Q3M1fZ!E)S2`Nu_H1#`P0Uw- z59%Q(ikcy{mv$vH+8BjinWdfpsOJV_ZKUHvLPm=v7VgN}H)intCgogICd#uSh3V`12z=k$mNrxJ>hpAxkp{xqSiSjjsPzC~$&YlHd z6<<-O@h7wmst_v1M}u!r`kIMsHP~GqoxRu!?hnIXx_zjaBr5XglEMiYM30#|KvEX5 zUplD4EprNF=wX&dg$m;80Wi{F%9A(Hf8nITW^Vlp3}BWegn%_Hque1sG1vdZ9JWX) z%XDw>tmOyj7MDeaINf@b-dB?(Kr31)fKCQpJ0}g}rCSSyKnyFVyEml96)0x2azK?W z8!UEzP{%+@BF77jAzt<#D&?s6d$iw>0$b;?P;1fQ>G4wAmBK+d|2Ekcfsw>6n4*O~QIjN2 zYzM7MTYXKhu?M4rP@!Qta8aShNw!L&Vmn;();aQumcQi=^0gwBwukPa#-3&OW~esM zRa?=`!HEf|8w+DQpzr>^g&sO3>^au+Cm98?Td^I`(H814q-!Z5G;^VgmpTW9&PC0D zF5yl%Q85U+tWI>3F5gO99X^JqC8Pu$40or)5CE;ZJ>BbyJxj{W&o1Usu|yN6Ze4DZ zG-0!#*t5Rt{hysirgAm8PEn;F-Z?`^sfpg4%5&7GaQ4ftLAPMAJHH1mTJRf<@S2M) zObebTxZlMWK%fDs+}6gthi?oOPp$|6N&`!%NX3*;lw)k6G`!KG)YD0fJu3mIe{E`c zQlsaonF%rWEP_z~q=nKD(Fah~*A7m0cDqBy3#jvo4!j1uxz&h7Dt-)X(ws21!}%2f zoyY9$4=V`!10OHlNIs#PgKv?NAd&6RYPNZlP#VTylshhDqZ^SB1!8{Bp6zRIR3fvs1N+bru@%e@ zX=t}F)Hrs!WdHl0G`v=$mb zXAhoqt3tpW7A>>e!R9tja{fWr4iEcFVHn2Yj!v^X%rxZ8jA(X~++C$QVNq=y1n<88 zC3%O;1X+tTJcth82EAdseEms7eJZQdRX#)Sh1{6|L|6bDsNfY`0bT)G0f}}?TOLY& zeOl%vcbbfkRTv}e_*w-jE#Hv~Z)XcGMyYy!Jg3E z5=h`;VMZEHSpJl79#YPF>?5+{5iJ=~K;eC6d1Pgb+KSox=gDivIuz{)>1wp!y$kHb z6Y+)!J%w(E-j`y+ng%k8(Q+*hZ^nATDfvdBq=lPNE~brq3aO~u*5=Wdu4q*p=vr6M zQfyEu^PxQB{z)!mEE})NA)nG%)AV>2;clGh&~iz6 zDDaLL<3s3SkOb~Rk_L<3aDkZ277J5|Kc!2fdk;qEGS(+@9T{eDs25!HlnJMrgt_9U zsp#w=ZTxR;2*bp#sNp4IRQFRIXx>YnW_X54g}UbonB007R3-=#21|Z)b;m-6K!O-O zC=0K8I?uw>Bz&0s1~qT!^AGU6R)HNZ1$SkO0aO7#sg8TdmDEXdDtOd_ork*;cQHV- z{Mr~b296;^#e`6wLS&PKS&D)11*9Q(uxn@V3FHwkWQZbL52uZ0&4VL%ddCOH>Xb5W zbm28oO4!b0&P(p7)hB6rM%9T~ZXpx+ElYSxK^|-VAPHj>@2x0;K*4Od)i{DtOcMGO zQf_YSby$)_TI_)`&>UY(=|9LsOb{c4?a{krkGW;Z(Bcjm_9;Wy=m!}?)auz)apo(y zv4e{q@+clHgQx8ue%2)EIL5Q9EGu0r{q&G=+SYgiZTHWjg@ zZys|ahzWYzQ_O;r5T2%Zw&&q$kbRN7d!(efF?JYFq>NTwp`g53L}$JdorMzx#_w=p zG4$}sn~ntyWe7Xcjm`lt8kP%>6iG{;Wg1*)?n##>gj1r3vF=Y}z$s@+eayIjWm3c;ot zVipE-KlZgDRA&~trg)@@uet?#YSlu=2u&tn@307>8xWhi59GkiSnR2~)eDB)y~Iq*m_mxyyWs zrWeLZP?bx~Ztrl9&k?%8YAmAEYN&=QBq@qiF4*xP-lvS`3>HgOFe^x^+XGt63`G^5 zlmc@s*Hjr#;P7eF96HhihiZ^XR$P~)SUBvb#PJXbB%FR{z0h%oI2kgr*&{rh`xzy~ zSlJdL1WK}_tCTQoiV=_C@Cmv!T@tIVT|9_Muqv5CC`=#u=%}7ybfi|o@gH6s$q_nu zti{Kg;4B?u9|w-ZMx)aYF$|MipIi~f#O14btdSNXkQ$QT@wv(#8)ky3oJS91BCy2} zLNR3j=+AN#pY7>(%k+VjQ9{<{NJ3@{@sOS_OkqpXRL+)5Z%+{)RFV-9ai!grQl$K45;!IA|!+mkr*UmLQ;6L zY61sG1O!76Ic6w*Vh{u&NC+4NA^1NbU@$B~fDk|g2-7fFm^XXig2Dd`_75_y8gPAW zb|a0*DM4$bGn~h&^J}0En;$C-Lrwq zHkWnF0ZaDm_#gY9eQ-V!IlR!|@j8q+EjP{wj8^0*q(?M!lKvg@IsIQ)Ws3jE*H`*? ze8_d}XEGS2KkLf)Uq*KPfEK--C;#H}P+(wnH&eL^5bGdtVeA6{jg)wyJc5X3D}Tsq z)E9;{OCD#*H$wypE;N@3#3&09UOzQg$N*}15(M0hu)7mi zRF;NY)uj30n8~l`8}}Giwo4EY&|aqS9i3qvG|BC(D&pH^GjX4Or(#u(9NAV!^d3XU zUABijY@;?UUz9*~Pz%Lp9C~ewbkI%ot5iRX9quvQ^UgYuqTf-2pSqr`$_KqN*mq^1 z&8NSpt82hqsCP02@A6w{w{|k0jO*-8S?J-2|&87hTi1O?Ktlcjd4If$4 zWK3PfKJl68(^diDn(}dyrs4$|`?m{uV8&^ME##T2?YEVIh;SCD^e4JX1(L{@4=)1< z%CDib;Mn~fm@&;o#VEPAQfXX*5q802G4zR)#{=7-AOwis4n5NgMUk^X?JJ?Uts;p9 zpk{48iL?eTL6!xf2&BmxyPyfFhIZc};Fs)d*Cr1EGMW@sgu(@zPzfNtPUB3aSGJh6 zwX88!C1YPS$u5;opK?Az9J{1nsCh&fyEo!lXy|H^Z_$Z@6} zQei8UASfEt#is%`lft_KS_?#yY+ZLlLjwLU&b}OPciS;hGiZ%GK4^(7r9on1KI$VS0d#mVSG zj7URERrf3ma2PwoHA!9SA7a)Y?v=EE=V3OSM1)u7qws?Py$10FGu9d*!Vz-hg+@Z03>%qe7GC%g48+P>wV? z1&)@(MZ=+jt=yMv%Nt_Q1qwo&sQw^PDjF|QAR83WvXH$hR*KyU4VPMlIWsVNL_CHF=xhtN7y97Z=uoirDu zy+#+X&`4E~P=2~o8552GY?lK15xs>9Z*m06HaQDgyUZJ9W5nqrbh6yDG7$aG6!VqT z`06CeFUL{&HSW|%AyPsUQ|btlG}k%V;9SAwyBNkHS{oIl8*c(+mjn{?WuYpes9Oey z_XCb-7$~t>(o6sfw!!OF2dQbA1QiwnDUeMN3Yy^WEwq^h4T%c$b^-2hva_nqq9a{- zrS8z4P*Z=jkOCL<@+nf+a01L~eO?t%+SQrUSI&|U$1dqF6&^*j93c_&C51aAit3O8 z6Zt@>;Yfp1$U`(4Q@5c2eWca9)<3O9S!3^JUD%s94b%8HEZ;chMh}wxLLtu9arA zTL25HI2K?5V*z3TLT`qL`@7vcW!eWv&^83RaAT*Jgdu&O9GkPLnj;iuLfq$G-5rD5 zXfx6$M&*RjJOlAT=bQpbJ)nvM*Xc4$^-#JU{(@Ksws=@JDS?ue-| zvApRkp0uN*sh?^WDMs{4V3s2@#PNuiPdVecOR^6|5&Ld86y~TYk7oJcOU-yGNWQPk z(>eL9fmtJ!5CSKQ;Tw;j{%EI8D5R9O8dgDc1;TbyTRi9R8H;6Zhjy1&Ounf|Q38$Z{^^|sr zJ|poRy-2flo?H5iOPy>ZtAc;B&`0PvjNlH#Rz6!+#)LgZX%j{4iRw{1y(p&7K3d#U zLRyxA<&3{g!fg(V+c^mB_OA zg1Qc5O&l)a1QcPq;AaOZ)6@cgJk~7J)C$tnmh4a^?wj9=31xix@+K5*>cykIXpd>p z0%hh`lOl+=&MAk%p`T!djE=4*-IuJ(q`Ud+6eY%L5j#lALkK@*6K4d6w`r-Xx0u1X zTohrWw8Rv0VJ^Za@ycv(=+h$J>93YWoOU7?yj*zrC+4*uLV9y9D46O$L`pi_KzB=^ zQcPlX*fPv0;!{IyXm24G>GH?by|N6rk4u`T!*<4CF~s(>bT*j0Op@qjk=^Rou=v5f zj6QK|p=?&1m<;1qCXJL82~IQOr21Co23U5Z5aCXw*-SAl?861aj3OcYhLsU(^2=;B zcrWSAp(>h89rB1!pBhBWrKqqMs&@Gbk14RaqB|;~YH5(GEv%%4yDJk$?HJk5>&l27 zM3}M?5+n7*5IL;pxzne@$e}xep+p$&5owprCMiTM$~8oW|)GxdU9PQ3nt+yQBzsTZsfF$GF!RU|JOQ@|^AroC4gc9R zsrxs;gQ*txK^+QMY9wja4PsTXI&epz$SeJ+erQH6-?NHUf&YY~THGJt!Y(ai+)vpR zHc`5Zy10Qa0JXT>PH)A;C8i{=-5@09x+b$enPSVH8XCcWvI9p;qTz)`tc0agas=ew z>9L$>G~AJbe65~N@V{w62YP*enaJPD+!uaLZsZ{L`ezWf_4i8fcMR{~53NUClh5$s z47eB)s{du98J7eQ+UI=0M;K-RjY#sxv5m=2MB)s4MBZieLOCFVJdKm~aA)x|B)wcLj=Y7=tdQE(&~!Zg454km(T^&qA}K z&dJkesjVf^w73Rdi~K;Mp_39gG06>;$s{nuMCacXJ2;2lceRfrkuQ(1&!W60Z+*&4F__)dWwLii2><46WvI;DA5Z~-qKZI{7|8`4&o=9vXZf9 zXFy+@255D`{Kqo$$__}n@5vW>ff@+yX5bc;D!6EjTHGnx(G+&*p1Y{Ynz9Q|^yFPE zV~%NNDs7Pmk~>im9(QxNhd_fx33gD*5h}k1FyX&fhI^QQRHl=2DwD==?9DwZ+C|Ny zInZH&tN;k{yPkGXl%WO@?@KC93Tp{vGX_3L!)-?jj6jJPpphg!C)b z=J4mR$djG%*<7M?(Yd1jN31+VEpC@!lTbowUUO;{J%Nh5+NA@Q7D4%tmHLQV$d>31 zn*LD556*evNq?~tV0tjDWKwxLTnK^F;-+`OME|Ah44#ImX!C)6zjhVf%1Ge-m@oiB z@5j4a2=L2Djo!j;hGdFg9+oU@Z%oL}wAhN~4=M4dJyh^l$Td|^A?Kk*Mf~TmwStlM zZ1Vza(Z~UvE zmN zw=(#JUy~m>h`lx%gl&Dk64-skJNVEjx3kFy&!=chjUY0_aUGancoT>e0F8L$;pHIIkpDE5K;c+e?Y563BAvfI-npVmMyWd5>(9Ja580tN zQ=@Gik*1_IP>Okfu05z|Tv>Y8hi%JygK;|X{H7AY0#tr&w z%A=d;V_ytdvNPN^-|HWG0yQ}Ffowgn35-CHFK;*~(8yG5)=^AUSp}Alk!3t0l&m*e zDCj$Nh35lVe2uwV%B5TdD;hLrlsr61eHVmPiXK!>ef6%#i0KrSHGedDEpJyyO1>|y}gw>TIRpcSM81E($JUpEm&QA9xhbub2g z0e%600pi9OoE$_uU06f4+=eGn%SvWrgiLow9#o7#OC&$uf*ZI;27iU=W0jrdts-Y7 zr)6_*iQ>0V?#Zl;oS32#M}xeVXo{H_-0uhjjmG$pA<<$rPE6aygHpKcxl&rIIVS2V z)e)^JG;F1KoK$wpuU$SXD$#BdA7=)8;zafK`Jj}B6=ggrMV7tgyrFxvHe_3Ss^U!Zdv&}eX)o53a#jr(cu zeIRLZEt{s?;#X(NTmcW-?a;dy;tBjTq2Mj`@3oXQ6Wvjg|9@bbP|S9ezw!vH>4<;)N+m|0sZ7OH9v*k4LnW3)xs-S z`sW0vT-MKTMki@klV-V=jb0p+a}c*(5p*Xm8inu*RqqsUwYGQ}aYJ(6DVfPFwfxqJ z&a(t@XC#s_xkjzBkOFMV7cA@#1!-K?#L0$Qu_nPrpSgufQ?yyZ7^c4sJvNmcf<@Gx zpb#seXL7huL_Vt19O4YNAC)rvnGovYBwQdFRJ~40h?Se$QqqBy#c8H0Zs1=Mz z8}5TW;pN4!?BvN~;{(P=jM~LLAqnM#rLj)zSEYH^XrPuE(k)Y7=>}pc(CFBG zmnv;}-IH%5@!ZA9VYBipnR6F!Kgrw?^%A{lSvHn)$GzCfW29)6y|6MQPIf{{$Wc0$ z0s{g$!?>4wgvH;6HgZ)gmf>Vll(-OXy;CC?bS?PmxK+8K7Wz7Q?iU{$zurW@mV$@A zP6ek{$oJ&FgA^(+;n{s@Rr-<~i57M{(ZDBRMz!k{y;_ET>k=Nj!a?avezW#Q2q3fT z>3*RvrecLYPX@VV2Zx6S0HVOdPiYuWf=`QBKdCTWf{Pa732QIs#pJP!VVA2u`E!!B zpo<~zv=o;v-Nu({W4z1bU}5_cybcnav+Az2Z*y#&O6Y3w?Gvp(Svz6agw2UFL<&vO z4=cg%1m}YkI_@$Nj!qVjGM#czxO6DauZE1_3X49WK`Ufu6jnt@CL3mAttHt{jzvo7 z8sSxBH)^cKR9S)Aq1UjbK^I(F(YKT2C#k@#VZu$$@@@@ZfQfuww%RJMwqn#LLyZ!~ z2sqAngL}hf#k&Dln4+JX^A)^z5ril2>r#RP+E~6C<3vrHv*30g2aWAzJ9*s|Hr-3H zBwJB7dc=E&Tx8YSg>yOxMWn7Q{|VBNVEH~zoz*g_6;p8rw{x>N+_Icy-&c0%2?tzx zd`}z3O-kA&w6YdCRx4K+mxanta!Ck@b`=&_xr(pyXi~)+ZTC4#`>*ui6H;wCec~IJ zv&@RD#6>fE?>Uc;^pYkxoQx?HNBS0zNo2`0rF_CK=&zA6l7rIF4O zsVyS~9JX3C(`&0}A1+~L<%Tr zg*tC$h)lhpVlRhR$*9Y1#4mlKV}^26)uC&2!NsGJQ9>DhcSmz3X!ukwr7zdIuhA;o zqgusU`I!5-As1`86EWsyO7PC2Y4x z?mVe9)`Iu0lYj=a4mDQ|EByd~W}^QrQ9_VV6a#Fdl+#5=Q)kjn0P4G8jdxpdXe<>V zRH8(lAQU^{*SlpfJTp`-V&_fyEXqx4h)HQY^k3uxC>941bT zY{V~G<|7GWD)xgN**%m@G!1GW`2ed3G~6B>r$S75OQIhk_Cj$URK(9^WH!Szt!Y?l za8ziPvMep)NKEoH6d^RCMuR+vO9+^vBpizY&7NvG9q|^*Awz9?v1uk8m^#Vznw)F+ zmPxKNEfRS~P58LfWbwAxrEvS8OA#I%Gp2GHV7-7<*iZ4|#Duz`0aAz=Qvkpq>hBOs zto5b8S>Lu)4DlhhyTV69Tffkr%1q^Z%wV##ut^*v zl^4XNoxid2KFv@iPVB~EksDWIr!(55Rhg6x2mUt7r^*Io!3-ox!`&%-6WFbFC5Hgl zjYdjOe8<)!{gMCA{ohGsDB(itx~WOpdV9R&Za5hMpq-?3?5)tayz*t2xl!T*NFc2D ze@8zJroUnXGU*Ndj4Zs2NJHT_G~&E81*#TkMHXg2#{hZ?J-t>zBQ=2cGFqblB>cbA zP}MFNV>kcw22v3I-)Ys9T+foV0_RC_NPHRv6{aHnz-UF)P=s_dgl~2}Efn>?Hkn50 zJ&ix{SM%ZqM0U@Mv1lMd3*xVEG_RG2tzrg+;QL|=;^&0>>=Xz>k1}at`8*$F(2jkyiVMyq{1~gW@mNRom)dJ{r$ahxUM90 zUTEBy*C2W&FW^rjA}LagZ|2eo_{&_-0k6zoCh(Uz_XS=VH*!Gs-e(Y2>)k8iUl`uO z2Q3D}n|y#@W$cR~R&~-g5IF#ixX6c;29f!Q@GQvJ@u&C&$n2Kg3Pe1KfKozp&P+mH ze)hqm`KjQbCeC^YYh|MQFLgj0p@I(y;v3$X>Q&IG%729 zmD_;=omV}~(vi4tGe)nUaOmyGw{%p z-MIWfw3*sL-6omS;IKoVy$hr|hGtClJL-?*+yEt7$}!z4CcfbxopP!-^t$lBWKHWV z9HL9@eo#nRI*Gcwd{OT7EnrO&OZ0ykAx&RB;k3379MJE*1X29`&02~^jnw*SFDsk3F04iPE z1MxQW05wXe#90A?P@}XAJxxng?mF}t`)abY^)^3Y;b^hd+sWaOiL(rXO>of|uGi5) z)Z1XaL(gOgGOlTkWJ^mT7}>)05}GS@Q07bQ8r0csHR^Rv7=Y+u zx+2e2^jxIjV$sw>*Jih}noP?STwefsrt}pMu%!bBwxu(JlO#zJ+87Q4S^-!ATLFCM zo||Ub)Wb*Cvc-x{@IOc`Yh^GD7}R}lZ$zD2%>6`bn6M~24r1AMk!tPG4h;+OMb!$F zk%FadoH!oIQIGi1rSvXz`U1<`t?<=}s7B%AnUH~trmB)>xemxmqF_3BVo3hDsQO9L zk<94ivoV#R7Q?7KNcOk*xD%p3v6-;nqbT{=XqB~2#)=iSIT@29|9nF=cfW?|hu|Q3 zgQZ_je3pdh1`7{n!EkcWp=cV*k3Q+tmEXk{2PEzk9#A+%fYDe2!=?!+PmyEj*$pR@ zWOi0W*2&wdIDT-y#6tjc(16_&`8~RFt5}G!Wlc|bcgWb*K}05ekw~}Pg?d=R`^kqb zDW$9M>(!4!XXpsEvRu`P^;qH0)a2O(#`In6kMX-Cql+Lv@#Y}n6laV=2Xr@Rw?jAa zUvRBuy-!Aiisz-LSlsLEOdgU@(=vpf2p1<~oA7c3(qk%32;H4Kwn0uzAiaLZ>n25u z@MW3%lOgbh);AGHP3cIpcvKUhc2a*a0=r4Kw(RuD;1SZt)P+O5HD}6(W<$gH{*gkN zEMI%#UMHB96-?1~l5}~QZUIgFcC|q~Nj{bdbt1$V!eQIF(DrhY2XpfVfg73~R&p|u zve43GC&Y}BUp?k~qgq=@NvVTX>J&cdPg)#{X`XOkmHZWhKPV>~?Q$_`rC#!nQJgL@ z7AtuA>e75k*Hwl(gK3uar0VR!Nr;DM87%CDs2;nxSDv|>Ib2}(FW}#Yy_7<738*Jx zRFLqefjWz8W;2lrkAZX~41an=4(TQr8T%dRHym-Wye1umtz;d{A4Eu~Mme+!t zCl*LZ;u&eHrtu7)aRI#TKQ?K@zn~A6oe^>c@4bcaAw#Y$+*<>3GAC`2(U5`D8!Gg} zaFY_0U5f2wP?{uOhJ)0-9%;C`JPw!nKWRZPYS`KMMG35(9Fiv7A)7hlGi7#Vu+4+pQsWd zp{lX=0;*J#Jp6>nxfR=Hndg%+h-3sO zW*!0iOY;uh)L&M4J4)O0WDF&L7+l1aK@jn(XaiTm%1P%3d^U5^}gPlsk zj25wf(!VFcl?cf&2XV4CDvAiUA_h;M@kG!hIpx@>Dq6X<;P%RmKM83=Sn+GjFdQEL zgPd7VeMRw3I2ehVYB*MV@#rRmwMq}N&=~9{{^(>qeT83p8(d|yvkab*@L32sYp9ky zmRP$AXbmskF{U?2KexEniH%B`YeYy`S? z@5+R6xzUr`P%^Zbw@XHqONfO4-sL{~B-u({XyMb7AaKOSI-Eb@tPNvbodL#mN+R%u zkNvF35hO z$tI+TCOC7AjnU2$SX=S`?#Mk9`~*dz^oRedfIOvF#>1#=ZZyO&8c{E!-=#=TOz=x6 zv1KnryAzq~ulQA%f1&%3(XnsS0Z9M37d>sm)f^x)Pm*deSIRgNjt z*yEdGG!am-UqN9&ko=BJH9bm(JSQZ2H{IUzotC6do)X4$I`4K(%~Bd*o9}bVEo{rL z;cCIzjiz$No}m&+>y=whHaPNQZ6y|(2Q{6ITu=d}ucPqvvLiw$YHzmVIZo4~=u+s+)N7EJ9hZVoy$N{UZQY>>)SMR2t=4lTw5U7oy-^i>&?Z|H3Mtsc zBz(MrU&5g2vtl`oZ7oF;WK%OA*B5?7JGM`=QT;n$cHG(6QF?lzgMmK=Yzb6k5h9>9 z9L*~&8zCO1qQxu3$EB`D5lS|3%!Ifawd@x)m)i2425*YrA~L17HnQ2(Rcu952y?X_ z-DnOW>OH}9X?ya`hS18h@y~bU=muR z2@m>LV~dt`7UBWti(%z3Nx&_8>y-2wb3j^)SWcm{jP<+b>i zSzZiz)U8ENOaRb`D=$+BoQy`~v*2&X58W3qvnh)r!UIxhjh@h)n@Qa6+G6kN0<1#G ze?S#lLno{?o@1utty5pHFMznl(=d|uUf4YAE_WEhgTWW zbOX*5qCjU;S0mpjm&tm_Za0PNG13{LzR1E_y*%-gA6(9;xC(xlQeNUUUeS%IJmhYc zjl}if?m}1Egl2EkXu3%0pnw7Z1D0$xb1h+}jlq@K{%f=uHJ-Vfb z<}PI*nnF0cJxf@k71V5(i>U+~E))DPV!i%Vt>~dsijI1LU$j(HgGm!Oyn1XsqK#2O zBt*}K)nCv@0W*b!C!J7~mmxYYDrG)84Aqi+0#IukeBSd=ZfFG+j8H*q=m|p&CtU75 z1EvZcje2YEGlk6JsWZ?WDsL#11Lzv++R#C3=m|CriRK=-iatoC)f!C~q>Ga|)+Cq2 zJ2sy-QNdFVT0>(+#S@8d;9knqkGP;BTN6Qjyly8w4-uCckzhI}lXMkRH*b|l@$7+`yC{a3#CZRL7t!qhtWjN~pAwukHd^Ut`3!NON^1L^Y_I#sWd0@$jGdp{} zNMyCqR+3n@jh)59K)E*c=CoU(xLd2-fD;C)3wR<^8d}RNFkM5qTT8bAIBQSO=@RG8gV zM7~G%0X^MvX*PvYA@<66y5+#>iw9?ifSS9C6gQVuDR-5Rz>nGQee@6rH+p>!ZACFXge-fQ?5w7{u?Q< z1imKvRw2Az0IRf&7kL490e1m{qfWy0%LznbQ-aek2r6up(a+L2f=2}9=ocgE+>L1R zQ2KR6L!XQV6Xn6;Q<14~Q0+1m889E`;XCH&g$K597$>j4vYXRAI?m^lQPV_Iu!t2W z{l79ySZkQHE5oYQ0{p_-@HhF87Q~|CPP9KrK27MsG*UZ0RV=-+iO*P>#S>vlvSA2; z;T7QG0d8(?2B67SzDf~3tOyS*Nq0hAD&|P)Yc&AQ7=;;QUWrvGMA_ouPEy><7VVBT zk=SC0u*WW2xH8sHs#k^1j&Q?G+XkHvH^j%b)aXg)zVu*)o5Z83hICO?KF-vmwD}QrBBTD`>Y=yDo9ASSuLN26B=sO9*U)U`S&Jmn=!V0m*B6M6T z&`Fruw4t#l@}XFX@)uet%t;LkGC_NWhh)C3wVdNawW8x6dnFOvEfwm-@l%k71@u8f z%REyh;{^zxhODdyOEkGfjIMe)=NsR6q((-=L&|_=XNfZ`$E-Mp7D7KUZ53PKK5jZ8_jPOw@M6~&@KPfBnQ{NwD>tVE=ntD(8#o4wXMCwcak)^2e!o>vE=k}@+@ zZ85++e=9y`hma!gKbbKl!+Krp;_#f}BG8#vf(Hf)@XoIDn1SR^1f?Q?J*|d;?F4)rD}zr2N%}+!q3IFzAYs zYj)Avlcum@0gcD-z!lb23}NE7EK_(QPFHS1du^i{T_)R2t|kL>)1pT_i4Br(Eq8uG z+$bxCjxqP0uH@pBPs(PcDNo#l2_`c(;}&VLsT$%gACVpxt+JT#7Quaze=R>mgLt&0 z^7Ql>F%`ucb54wIMW{ZF-2rHna{(Pp2C^7n)n(wA%&&KBaJEGWT>|5Yp1bVqeq$|g zmJ6Kn(9CO~J`E#;&scEmq~=w8ZmREu?vtRy6j&$L<0bt>Qz&v{4caI4ggvZ$&53wj zX5hr;*gpenM_0#h+5}|~Kw-oBhP*+(eJ0HWfY*TZ@$OPc_ruXuS zb9r$r%Xgx9B&ax+t(33yN~;PDK-E4Ty_LNx&hKIvPB@&(sFASKo1Ig$D;u^KL6uyH zTB+2Q>~TWOEMMC?Os(p6D-nkh$Q3t4$PLWjlIAR1;$(Q8WDvv2an4Tf))Gt9>!=q& z)JQ%qCCU(0o1sd{kZfo246q$EWj}|rL#&LY>Q1;;g=x4YYO0aV6<47NvpKGyD_$sk z3-zB|Ko*|z+8~zEPLq{*(tu+ONw#18Bx0_`a!$g)7J)%G9MPd7q74r}=8L17WRmuF zxy6&;YZ=Nz=-nI>ks%vW0cmp+dOT5g5dG1z6i)aJWdzePwgBg?6*h@XRWNwXEo}v_BPk61>MwDm!adgnTz*Z>frj{sGODo#zZ(DH%oO!Grh3?P}7+)Dha(4OLVgLj|u5 zoJnXts)Nv#EIY4YK$v&kY&7O z)Ks}tC7UjgA!p%%4y8hqn>?kR$b{eBf-gL$`%0m|JIqNT!3Ktr31;)ID#@?t+*r?Z zkva5X73`A@)=5Czg($U0ql<)!qv&=*QLKi4-v4Vg0iw%aUgM41;V4&nzxVN|;WGbVK5b~~^WN_IBgx$lK^=8N`F0fnOICW1|L(zeHb z(Oc8CTbB?N{Wr-fd_G2&{D?@9?Wh-{FVww*{HTK=OS>iVWVD}9CR0ELiQs~mV9upc z@N*SyB+_!R=mqM|i%@5kRILZCK=5L_McjuUq&+LsGg6nmMJuN;eYUGH+GkH0 zJI6P5XuvlNdlG2#Q310Tgk}vztSVOB9Tt5-jue`OIJ?=Z#(va4^t zL^(!bhoNAUPxNwoArBbHZv z{VAVCo=h@WvkHueQl$9b`_c*j-9PB?>;9OD|L*s`_;vpy$7OGf1}U{>y^{XL@DAqC zGSRx^|M<1xjSMkcPiWZP^ldskTsVQ~t!DXGRS#j+lVXu-j0w?=1@2}7>xFU^41crL{o03 zv`(`Bwv?3dfoQL*-N!8>rhnYI9WB`f*S~s-$?Sl=Zt6{ma^Up0&M_RJ{sN`X7@;&h z39{2IuxP%ZmX-eK-Ey+h55+{V*Nwcs#^3b$G}z}rv6Nk-uuyt893qphy_FUE!XeMe z2A>kd^190O7#&4b8;4%m4_<(xz0#9VD9X6gZKpIB{jpSZ83PADFhP4qyKcpt? zhC%N2yfD;9TIx+O(HH6--i!81B|9VT{PISZEkRQopqvUwXmPtg?2IFP91q&SYvok22 zMUOCOK3a-xMlUcrAh6 zbmJ6TGenl%Phm8uh*dK;3cq(M$7Am;0r7~*R(>tFJ~xfD+@)T2J!?GK>(PJqM{xZ6 zNVIsN5tXn;ZSZ=?{EI(Elyal^@BPaO|J}Xk@asOx#Q*MBU;K67kmGW19)py&=3GhJ zRml#{&^977@^JGP*L7$NtdwdhCoJQU&zxZU#Obn z=xpRF@%>;6&CyJLccX@NobE!4C*1`deI13c)|HNlhT-<x5yy?f?bcNG z3umfv;8rG;6=V;2PUz`Y?r(&$bEITjEK=ToR1{zU>aHLc6_BMMd5tuf8CjMTA<1BG zU6~!^0|ILSY5{El+^Ds?nOk}~!EC`IVN_F3o%?xH5{&Sh?(S6a-=7|bHU z%(21NpS^HFidgw5ql}v@hFnV`Xou^ta4x);63DHLBvg8&Ue$%&FfUoe(e|z>?mQu= zQju=WTJcSq6$Mik2?^GwyH_**Fun_M;;*H(i zY?{raS?a)nqaTd;%;ip0;6>?6+!_1Li|7W26;nd=(Xnp~ZTdE`H)f>)FXA zh3lw0u91_WnKLBW;>7A6a_o-@_e$Q9p{FFb#+i>#aNKE&9)@%ny<=6OvvrMJMT(cn z`|;q1gb2fkH+mFQLWaBo*#{MO6{VbP2(k)dAQ>SNM3av|d$zl87Qa}p1*%FjJwn8r zqAU(smAtINh1h$OlII|_X*fJtL&QVDGOA!C$>a1*I-F7OtYIIx3^tw7tYy5hrGU$f z9(<}xCt3XDyr(Unh^&wSH&G2Q`BH;A)?L-=k^QiNUoPBvO`Hr(DCgZx4KvyVOPrKx zX!xGmXX2fPLc{=BknW+Z#4}Ds;z#t3!N!Hz6{b<$&6Vlc232J{hUn(% zhgonc%811;l5(*|?t4L5v-xCbk#eu)eUZD5)bgQ8bO*_)Iji0w{37PQDDso16?)lB zwQ5$=iV$u>K}W})M22_fJ-LYE7=f*d&hWOV6(N2Q6;2iITPcnkG?R&p83Vi8U@{;; z9U7Hw&5a8yXq+3x59!W}chY&SGZ)+#1*To0B5qgY^AI_eG0T z(yGCO$RIOxd<1u^X(iLBj%31j%N=jUNeNbyJMyrFwF#Y%weZq=R}%U~1r?5AU>iaV z+qMf_eg(T%**h6_HmHb7Bq5OrvEPh189B(m(L2)ErCcsvnJ_nk;#+tRjpc+*YG-hC zPR%$gbpyJB7EE-r(Rs_hGVfKyoKO+lX-rmC9J9P!%b^y;bos#BGtoV4lcQD`MzF4K zcCpHGf=MNUWDG%L$jOFJuI^|oZf1lQv-T9rY;}hNt*xRmXx~HJb@py$M(MB;BN}Dp z2C1P+xF&hHZ>7DOQT0u*R@%-iF&W+&&DJ2NZ^HWwbElz}))I9yu-af{FcQsq5G9;cxECSgG-EapLR02a+ngGj8zSZu z^O7EJh_}_fSk_I8X4tCRRn41RoePw4;9RLLf<8)!>+zj>^QEuYZrH@KAvD&UO+knd z>1a_;O6^Ajf;_0=9YP2p0wEH}jFgNhJhEDZpuq_V3ppi~nz{#45C}q$5HJJ?!T$*X zMPU&Fga9Hym)x)p*g1jb$a*&DzYuSNOADAyexyMg-)M>JM(6;C36olqQ$|}@AA`OI(yjw z#9Ux1nk6(;kHxT0oPdh7fNa3r?aU5At8k%7i&X!6v{RG)$l!2nSoYQ;Nra~Jx;F+n zvsnj?q!%TljL^ij<}VD9FsP7>F*?ukKpB;V=FDpPrs9Njbvo&E`k>vVu)5*&2o(={ z&oISwA?V#(^qoL!P+?1Me^X8S42Of)*!7zqrmJi(4*C{Cqy)s|p0TDorKhWCJCx;f zi~4D6A-SujyqgX}orBy`M*b!SmkENrFD8g2ZBTva4JJYWd}C5be|>_2Ulb*_alq=) z$<%RT`vVo+;OjM{UrFau=g90olyCx!m*#uTE0rT;my(^0Y$kN3Z5ip;Cpz#oRTGJW zcX|)#ohm;8kYtX1+Z{?a;}~a>qZ{|h-~=_k0Wx2n|n2NV%}- zFqj%UMn{o;DEu+^orpdxj7P>;aDI^7i0_PI`U2xUV)LeCfhl2-Q1c!;KLdvOwSWSUz=z7f$?E zd$bd>Vo=4*6;fDyDucmBrc|+R{3sGWRr&nPudX7zpQ1QEj z3Kr>0a^(luRs_N=@BaTgOfSV?UVEO`cCq_>-( z6?C*8F^LNANr_C{Lm%9KUiy?8q1#eSijJIils?HUK)AnmYqwFLJEuoxo_Ad+&|Z~` zYI3WI0V8yzwjbPqiXf_d(06MVEE1uqsJ^3)ww!K~7=U+wLBy91869lv`Utn;14*4I zFvDQcSDLYvTm7j|G5sTOc6G8DNROwMeXb;@nsf#CyE8jz&?%{Hi*Bd{L?eQd7aa%# z(%Mu9HD{v)8?c3OUaamB(l@6-vcu)?vRNDN1@Ix|{L30J7WThRiqeaI~i^6v~=t#0N+MRS>)9Qg0 zDixXwHsOe728b#;kjg7Htsx-oG6{1M|ZQkqCLJLDMWY0BjDnuuijpxchYCA;An z3Zw2y9rFpX0!Q`kF1h{F7z;KZsEZt-s3kHMQxSP+cm};A&cHh01dH1u=P*gi8tpOf z?fx0yjDD3bCC-1cgK7ZtQrj-xtQkY?_*k)b2vFCvP!Z{lWf-0@JADcqt_`Wda|Xc_ zKqdwD9NS+}`tYnoo+kYaz!NU*9liu|O5tS`up4eB337vMm9{*h-AxC|DctFi5gf>L zp)ok&a!QUtPKz&%k=hpcqi}~IKc1VF@?UxH@kiklN`Aa&W5xTv(U~{?IP@(z1;rbh zWF8B?^xw#b`TZ3YG7_T9$bON(wO?>%emPHsf8s)OyR@k!A_27&02Y`z6tETK0SrKs zt-&;;xk4COLQgWQ5;6cZ05Sk6W>tI^1J1rlAs5T!zTz5822st~d9LpBst=C)m7wPG zIIEN3$!?x-@~I)p;B69S2Ud#6QwoI{FIi)#maxf4ZBEgBqIxKVFrvl>TXAeDS4Tm% zz`d4D^#MDst1dbs3~M-@0XEc2@Z+X;nB#qR zWSA0^w3eIEF&AnocPZC^0YLC`0O@thfH5alV6S!+1coi-zBpt%4BPm8HHRzoie$Rp ziOAwn&Rb?s;UVQdqf3B@s8Nf8f~2UN@*$Cc2#^vIg(Q)Y8apBo1P}-U6#+x={}eK-Rtek+QNGl;pgvfG>ZP9;GmosfhvEE7I zMYXku#}#%Wf`G-?7Td}55EuMNNTy$tjv6^l)VsS(hbXp!3h^n0w2cDVg(+3dS7J6K zh46I|Dg|I9uSk2cd8D5$PKLT8YL%oq?fW_!sQ%LTS6~!lLaV~cJ*3#pZi7nc9lhSf zN0!>Ye}UAaO$87jtv~}~nc4X8EE9c`z5rmXLc;zB8|9SpGHaYEaAhFrxWr#2EMv#J ifCttUPSRmm9QfgdMnHn`6bnaad8Pa~lx+!FH4Ut3PxHnA literal 0 HcmV?d00001 diff --git a/src/encoding/json/internal/jsontest/testdata/twitter_status.json.zst b/src/encoding/json/internal/jsontest/testdata/twitter_status.json.zst new file mode 100644 index 0000000000000000000000000000000000000000..f5a456be899031039657e6e4b5fc18f4931d26b2 GIT binary patch literal 37733 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 BiLC$t literal 0 HcmV?d00001 diff --git a/src/encoding/json/internal/jsonwire/decode.go b/src/encoding/json/internal/jsonwire/decode.go new file mode 100644 index 0000000000..42eeedcddf --- /dev/null +++ b/src/encoding/json/internal/jsonwire/decode.go @@ -0,0 +1,629 @@ +// 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 + +package jsonwire + +import ( + "io" + "math" + "slices" + "strconv" + "unicode/utf16" + "unicode/utf8" +) + +type ValueFlags uint + +const ( + _ ValueFlags = (1 << iota) / 2 // powers of two starting with zero + + stringNonVerbatim // string cannot be naively treated as valid UTF-8 + stringNonCanonical // string not formatted according to RFC 8785, section 3.2.2.2. + // TODO: Track whether a number is a non-integer? +) + +func (f *ValueFlags) Join(f2 ValueFlags) { *f |= f2 } +func (f ValueFlags) IsVerbatim() bool { return f&stringNonVerbatim == 0 } +func (f ValueFlags) IsCanonical() bool { return f&stringNonCanonical == 0 } + +// ConsumeWhitespace consumes leading JSON whitespace per RFC 7159, section 2. +func ConsumeWhitespace(b []byte) (n int) { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + for len(b) > n && (b[n] == ' ' || b[n] == '\t' || b[n] == '\r' || b[n] == '\n') { + n++ + } + return n +} + +// 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. +func ConsumeNull(b []byte) int { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + const literal = "null" + if len(b) >= len(literal) && string(b[:len(literal)]) == literal { + return len(literal) + } + return 0 +} + +// 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. +func ConsumeFalse(b []byte) int { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + const literal = "false" + if len(b) >= len(literal) && string(b[:len(literal)]) == literal { + return len(literal) + } + return 0 +} + +// 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. +func ConsumeTrue(b []byte) int { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + const literal = "true" + if len(b) >= len(literal) && string(b[:len(literal)]) == literal { + return len(literal) + } + return 0 +} + +// ConsumeLiteral consumes the next JSON literal per RFC 7159, section 3. +// If the input appears truncated, it returns io.ErrUnexpectedEOF. +func ConsumeLiteral(b []byte, lit string) (n int, err error) { + for i := 0; i < len(b) && i < len(lit); i++ { + if b[i] != lit[i] { + return i, NewInvalidCharacterError(b[i:], "in literal "+lit+" (expecting "+strconv.QuoteRune(rune(lit[i]))+")") + } + } + if len(b) < len(lit) { + return len(b), io.ErrUnexpectedEOF + } + return len(lit), nil +} + +// 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. +// +// It rejects '<', '>', and '&' for compatibility reasons since these were +// always escaped in the v1 implementation. Thus, if this function reports +// non-zero then we know that the string would be encoded the same way +// under both v1 or v2 escape semantics. +func ConsumeSimpleString(b []byte) (n int) { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + if len(b) > 0 && b[0] == '"' { + n++ + for len(b) > n && b[n] < utf8.RuneSelf && escapeASCII[b[n]] == 0 { + n++ + } + if uint(len(b)) > uint(n) && b[n] == '"' { + n++ + return n + } + } + return 0 +} + +// ConsumeString consumes the next JSON string per RFC 7159, section 7. +// If validateUTF8 is false, then this allows the presence of invalid UTF-8 +// characters within the string itself. +// It reports the number of bytes consumed and whether an error was encountered. +// If the input appears truncated, it returns io.ErrUnexpectedEOF. +func ConsumeString(flags *ValueFlags, b []byte, validateUTF8 bool) (n int, err error) { + return ConsumeStringResumable(flags, b, 0, validateUTF8) +} + +// 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. + switch { + case resumeOffset > 0: + n = resumeOffset // already handled the leading quote + case uint(len(b)) == 0: + return n, io.ErrUnexpectedEOF + case b[0] == '"': + n++ + default: + return n, NewInvalidCharacterError(b[n:], `at start of string (expecting '"')`) + } + + // Consume every character in the string. + for uint(len(b)) > uint(n) { + // Optimize for long sequences of unescaped characters. + noEscape := func(c byte) bool { + return c < utf8.RuneSelf && ' ' <= c && c != '\\' && c != '"' + } + for uint(len(b)) > uint(n) && noEscape(b[n]) { + n++ + } + if uint(len(b)) <= uint(n) { + return n, io.ErrUnexpectedEOF + } + + // Check for terminating double quote. + if b[n] == '"' { + n++ + return n, nil + } + + switch r, rn := utf8.DecodeRune(b[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. + case rn > 1: + n += rn + // Handle escape sequence. + case r == '\\': + flags.Join(stringNonVerbatim) + resumeOffset = n + if uint(len(b)) < uint(n+2) { + return resumeOffset, io.ErrUnexpectedEOF + } + switch r := b[n+1]; r { + case '/': + // Forward slash is the only character with 3 representations. + // Per RFC 8785, section 3.2.2.2., this must not be escaped. + flags.Join(stringNonCanonical) + n += 2 + case '"', '\\', 'b', 'f', 'n', 'r', 't': + n += 2 + case 'u': + if uint(len(b)) < uint(n+6) { + if hasEscapedUTF16Prefix(b[n:], false) { + return resumeOffset, io.ErrUnexpectedEOF + } + flags.Join(stringNonCanonical) + return n, NewInvalidEscapeSequenceError(b[n:]) + } + v1, ok := parseHexUint16(b[n+2 : n+6]) + if !ok { + flags.Join(stringNonCanonical) + return n, NewInvalidEscapeSequenceError(b[n : n+6]) + } + // Only certain control characters can use the \uFFFF notation + // for canonical formatting (per RFC 8785, section 3.2.2.2.). + switch v1 { + // \uFFFF notation not permitted for these characters. + case '\b', '\f', '\n', '\r', '\t': + flags.Join(stringNonCanonical) + default: + // \uFFFF notation only permitted for control characters. + if v1 >= ' ' { + flags.Join(stringNonCanonical) + } else { + // \uFFFF notation must be lower case. + for _, c := range b[n+2 : n+6] { + if 'A' <= c && c <= 'F' { + flags.Join(stringNonCanonical) + } + } + } + } + n += 6 + + r := rune(v1) + if validateUTF8 && utf16.IsSurrogate(r) { + if uint(len(b)) < uint(n+6) { + if hasEscapedUTF16Prefix(b[n:], true) { + return resumeOffset, io.ErrUnexpectedEOF + } + flags.Join(stringNonCanonical) + return n - 6, NewInvalidEscapeSequenceError(b[n-6:]) + } else if v2, ok := parseHexUint16(b[n+2 : n+6]); b[n] != '\\' || b[n+1] != 'u' || !ok { + flags.Join(stringNonCanonical) + return n - 6, NewInvalidEscapeSequenceError(b[n-6 : n+6]) + } else if r = utf16.DecodeRune(rune(v1), rune(v2)); r == utf8.RuneError { + flags.Join(stringNonCanonical) + return n - 6, NewInvalidEscapeSequenceError(b[n-6 : n+6]) + } else { + n += 6 + } + } + default: + flags.Join(stringNonCanonical) + return n, NewInvalidEscapeSequenceError(b[n : n+2]) + } + // Handle invalid UTF-8. + case r == utf8.RuneError: + if !utf8.FullRune(b[n:]) { + return n, io.ErrUnexpectedEOF + } + flags.Join(stringNonVerbatim | stringNonCanonical) + if validateUTF8 { + return n, ErrInvalidUTF8 + } + n++ + // Handle invalid control characters. + case r < ' ': + flags.Join(stringNonVerbatim | stringNonCanonical) + return n, NewInvalidCharacterError(b[n:], "in string (expecting non-control character)") + default: + panic("BUG: unhandled character " + QuoteRune(b[n:])) + } + } + return n, io.ErrUnexpectedEOF +} + +// AppendUnquote appends the unescaped form of a JSON string in src to dst. +// 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) { + dst = slices.Grow(dst, len(src)) + + // Consume the leading double quote. + var i, n int + switch { + case uint(len(src)) == 0: + return dst, io.ErrUnexpectedEOF + case src[0] == '"': + i, n = 1, 1 + default: + return dst, NewInvalidCharacterError(src, `at start of string (expecting '"')`) + } + + // Consume every character in the string. + for uint(len(src)) > uint(n) { + // Optimize for long sequences of unescaped characters. + noEscape := func(c byte) bool { + return c < utf8.RuneSelf && ' ' <= c && c != '\\' && c != '"' + } + for uint(len(src)) > uint(n) && noEscape(src[n]) { + n++ + } + if uint(len(src)) <= uint(n) { + dst = append(dst, src[i:n]...) + return dst, io.ErrUnexpectedEOF + } + + // Check for terminating double quote. + if src[n] == '"' { + dst = append(dst, src[i:n]...) + n++ + if n < len(src) { + err = NewInvalidCharacterError(src[n:], "after string value") + } + return dst, err + } + + switch r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(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. + case rn > 1: + n += rn + // Handle escape sequence. + case r == '\\': + dst = append(dst, src[i:n]...) + + // Handle escape sequence. + if uint(len(src)) < uint(n+2) { + return dst, io.ErrUnexpectedEOF + } + switch r := src[n+1]; r { + case '"', '\\', '/': + dst = append(dst, r) + n += 2 + case 'b': + dst = append(dst, '\b') + n += 2 + case 'f': + dst = append(dst, '\f') + n += 2 + case 'n': + dst = append(dst, '\n') + n += 2 + case 'r': + dst = append(dst, '\r') + n += 2 + case 't': + dst = append(dst, '\t') + n += 2 + case 'u': + if uint(len(src)) < uint(n+6) { + if hasEscapedUTF16Prefix(src[n:], false) { + return dst, io.ErrUnexpectedEOF + } + return dst, NewInvalidEscapeSequenceError(src[n:]) + } + v1, ok := parseHexUint16(src[n+2 : n+6]) + if !ok { + return dst, NewInvalidEscapeSequenceError(src[n : n+6]) + } + n += 6 + + // Check whether this is a surrogate half. + r := rune(v1) + if utf16.IsSurrogate(r) { + r = utf8.RuneError // assume failure unless the following succeeds + if uint(len(src)) < uint(n+6) { + if hasEscapedUTF16Prefix(src[n:], true) { + return utf8.AppendRune(dst, r), io.ErrUnexpectedEOF + } + err = NewInvalidEscapeSequenceError(src[n-6:]) + } else if v2, ok := parseHexUint16(src[n+2 : n+6]); src[n] != '\\' || src[n+1] != 'u' || !ok { + err = NewInvalidEscapeSequenceError(src[n-6 : n+6]) + } else if r = utf16.DecodeRune(rune(v1), rune(v2)); r == utf8.RuneError { + err = NewInvalidEscapeSequenceError(src[n-6 : n+6]) + } else { + n += 6 + } + } + + dst = utf8.AppendRune(dst, r) + default: + return dst, NewInvalidEscapeSequenceError(src[n : n+2]) + } + i = n + // Handle invalid UTF-8. + case r == utf8.RuneError: + dst = append(dst, src[i:n]...) + if !utf8.FullRuneInString(string(truncateMaxUTF8(src[n:]))) { + return dst, io.ErrUnexpectedEOF + } + // NOTE: An unescaped string may be longer than the escaped string + // because invalid UTF-8 bytes are being replaced. + dst = append(dst, "\uFFFD"...) + n += rn + i = n + err = ErrInvalidUTF8 + // Handle invalid control characters. + case r < ' ': + dst = append(dst, src[i:n]...) + return dst, NewInvalidCharacterError(src[n:], "in string (expecting non-control character)") + default: + panic("BUG: unhandled character " + QuoteRune(src[n:])) + } + } + dst = append(dst, src[i:n]...) + return dst, io.ErrUnexpectedEOF +} + +// hasEscapedUTF16Prefix reports whether b is possibly +// the truncated prefix of a \uFFFF escape sequence. +func hasEscapedUTF16Prefix[Bytes ~[]byte | ~string](b Bytes, lowerSurrogateHalf bool) bool { + for i := range len(b) { + switch c := b[i]; { + case i == 0 && c != '\\': + return false + case i == 1 && c != 'u': + return false + case i == 2 && lowerSurrogateHalf && c != 'd' && c != 'D': + return false // not within ['\uDC00':'\uDFFF'] + case i == 3 && lowerSurrogateHalf && !('c' <= c && c <= 'f') && !('C' <= c && c <= 'F'): + return false // not within ['\uDC00':'\uDFFF'] + case i >= 2 && i < 6 && !('0' <= c && c <= '9') && !('a' <= c && c <= 'f') && !('A' <= c && c <= 'F'): + return false + } + } + return true +} + +// UnquoteMayCopy returns the unescaped form of b. +// If there are no escaped characters, the output is simply a subslice of +// the input with the surrounding quotes removed. +// Otherwise, a new buffer is allocated for the output. +// It assumes the input is valid. +func UnquoteMayCopy(b []byte, isVerbatim bool) []byte { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + if isVerbatim { + return b[len(`"`) : len(b)-len(`"`)] + } + b, _ = AppendUnquote(nil, b) + return b +} + +// ConsumeSimpleNumber consumes the next JSON number per RFC 7159, section 6 +// but is limited to the grammar for a positive integer. +// It returns 0 if it is invalid or more complicated than a simple integer, +// 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 { + if b[0] == '0' { + n++ + } else if '1' <= b[0] && b[0] <= '9' { + n++ + for len(b) > n && ('0' <= b[n] && b[n] <= '9') { + n++ + } + } else { + return 0 + } + if uint(len(b)) <= uint(n) || (b[n] != '.' && b[n] != 'e' && b[n] != 'E') { + return n + } + } + return 0 +} + +type ConsumeNumberState uint + +const ( + consumeNumberInit ConsumeNumberState = iota + beforeIntegerDigits + withinIntegerDigits + beforeFractionalDigits + withinFractionalDigits + beforeExponentDigits + withinExponentDigits +) + +// ConsumeNumber consumes the next JSON number per RFC 7159, section 6. +// It reports the number of bytes consumed and whether an error was encountered. +// If the input appears truncated, it returns io.ErrUnexpectedEOF. +// +// Note that JSON numbers are not self-terminating. +// If the entire input is consumed, then the caller needs to consider whether +// there may be subsequent unread data that may still be part of this number. +func ConsumeNumber(b []byte) (n int, err error) { + n, _, err = ConsumeNumberResumable(b, 0, consumeNumberInit) + return n, err +} + +// 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. + n = resumeOffset + if state > consumeNumberInit { + switch state { + case withinIntegerDigits, withinFractionalDigits, withinExponentDigits: + // Consume leading digits. + for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') { + n++ + } + if uint(len(b)) <= uint(n) { + return n, state, nil // still within the same state + } + state++ // switches "withinX" to "beforeY" where Y is the state after X + } + switch state { + case beforeIntegerDigits: + goto beforeInteger + case beforeFractionalDigits: + goto beforeFractional + case beforeExponentDigits: + goto beforeExponent + default: + return n, state, nil + } + } + + // Consume required integer component (with optional minus sign). +beforeInteger: + resumeOffset = n + if uint(len(b)) > 0 && b[0] == '-' { + n++ + } + switch { + case uint(len(b)) <= uint(n): + return resumeOffset, beforeIntegerDigits, io.ErrUnexpectedEOF + case b[n] == '0': + n++ + state = beforeFractionalDigits + case '1' <= b[n] && b[n] <= '9': + n++ + for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') { + n++ + } + state = withinIntegerDigits + default: + return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)") + } + + // Consume optional fractional component. +beforeFractional: + if uint(len(b)) > uint(n) && b[n] == '.' { + resumeOffset = n + n++ + switch { + case uint(len(b)) <= uint(n): + return resumeOffset, beforeFractionalDigits, io.ErrUnexpectedEOF + case '0' <= b[n] && b[n] <= '9': + n++ + default: + return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)") + } + for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') { + n++ + } + state = withinFractionalDigits + } + + // Consume optional exponent component. +beforeExponent: + if uint(len(b)) > uint(n) && (b[n] == 'e' || b[n] == 'E') { + resumeOffset = n + n++ + if uint(len(b)) > uint(n) && (b[n] == '-' || b[n] == '+') { + n++ + } + switch { + case uint(len(b)) <= uint(n): + return resumeOffset, beforeExponentDigits, io.ErrUnexpectedEOF + case '0' <= b[n] && b[n] <= '9': + n++ + default: + return n, state, NewInvalidCharacterError(b[n:], "in number (expecting digit)") + } + for uint(len(b)) > uint(n) && ('0' <= b[n] && b[n] <= '9') { + n++ + } + state = withinExponentDigits + } + + return n, state, nil +} + +// 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) { + if len(b) != 4 { + return 0, false + } + for i := range 4 { + c := b[i] + switch { + case '0' <= c && c <= '9': + c = c - '0' + case 'a' <= c && c <= 'f': + c = 10 + c - 'a' + case 'A' <= c && c <= 'F': + c = 10 + c - 'A' + default: + return 0, false + } + v = v*16 + uint16(c) + } + return v, true +} + +// ParseUint parses b as a decimal unsigned integer according to +// a strict subset of the JSON number grammar, returning the value if valid. +// It returns (0, false) if there is a syntax error and +// returns (math.MaxUint64, false) if there is an overflow. +func ParseUint(b []byte) (v uint64, ok bool) { + const unsafeWidth = 20 // len(fmt.Sprint(uint64(math.MaxUint64))) + var n int + for ; len(b) > n && ('0' <= b[n] && b[n] <= '9'); n++ { + v = 10*v + uint64(b[n]-'0') + } + switch { + case n == 0 || len(b) != n || (b[0] == '0' && string(b) != "0"): + return 0, false + case n >= unsafeWidth && (b[0] != '1' || v < 1e19 || n > unsafeWidth): + return math.MaxUint64, false + } + 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 +} diff --git a/src/encoding/json/internal/jsonwire/decode_test.go b/src/encoding/json/internal/jsonwire/decode_test.go new file mode 100644 index 0000000000..549c1a1f62 --- /dev/null +++ b/src/encoding/json/internal/jsonwire/decode_test.go @@ -0,0 +1,443 @@ +// 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 + +package jsonwire + +import ( + "errors" + "io" + "math" + "reflect" + "strings" + "testing" +) + +func TestConsumeWhitespace(t *testing.T) { + tests := []struct { + in string + want int + }{ + {"", 0}, + {"a", 0}, + {" a", 1}, + {" a ", 1}, + {" \n\r\ta", 4}, + {" \n\r\t \n\r\t \n\r\t \n\r\t", 16}, + {"\u00a0", 0}, // non-breaking space is not JSON whitespace + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + if got := ConsumeWhitespace([]byte(tt.in)); got != tt.want { + t.Errorf("ConsumeWhitespace(%q) = %v, want %v", tt.in, got, tt.want) + } + }) + } +} + +func TestConsumeLiteral(t *testing.T) { + tests := []struct { + literal string + in string + want int + wantErr error + }{ + {"null", "", 0, io.ErrUnexpectedEOF}, + {"null", "n", 1, io.ErrUnexpectedEOF}, + {"null", "nu", 2, io.ErrUnexpectedEOF}, + {"null", "nul", 3, io.ErrUnexpectedEOF}, + {"null", "null", 4, nil}, + {"null", "nullx", 4, nil}, + {"null", "x", 0, NewInvalidCharacterError("x", "in literal null (expecting 'n')")}, + {"null", "nuxx", 2, NewInvalidCharacterError("x", "in literal null (expecting 'l')")}, + + {"false", "", 0, io.ErrUnexpectedEOF}, + {"false", "f", 1, io.ErrUnexpectedEOF}, + {"false", "fa", 2, io.ErrUnexpectedEOF}, + {"false", "fal", 3, io.ErrUnexpectedEOF}, + {"false", "fals", 4, io.ErrUnexpectedEOF}, + {"false", "false", 5, nil}, + {"false", "falsex", 5, nil}, + {"false", "x", 0, NewInvalidCharacterError("x", "in literal false (expecting 'f')")}, + {"false", "falsx", 4, NewInvalidCharacterError("x", "in literal false (expecting 'e')")}, + + {"true", "", 0, io.ErrUnexpectedEOF}, + {"true", "t", 1, io.ErrUnexpectedEOF}, + {"true", "tr", 2, io.ErrUnexpectedEOF}, + {"true", "tru", 3, io.ErrUnexpectedEOF}, + {"true", "true", 4, nil}, + {"true", "truex", 4, nil}, + {"true", "x", 0, NewInvalidCharacterError("x", "in literal true (expecting 't')")}, + {"true", "trux", 3, NewInvalidCharacterError("x", "in literal true (expecting 'e')")}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + var got int + switch tt.literal { + case "null": + got = ConsumeNull([]byte(tt.in)) + case "false": + got = ConsumeFalse([]byte(tt.in)) + case "true": + got = ConsumeTrue([]byte(tt.in)) + default: + t.Errorf("invalid literal: %v", tt.literal) + } + switch { + case tt.wantErr == nil && got != tt.want: + t.Errorf("Consume%v(%q) = %v, want %v", strings.Title(tt.literal), tt.in, got, tt.want) + case tt.wantErr != nil && got != 0: + t.Errorf("Consume%v(%q) = %v, want %v", strings.Title(tt.literal), tt.in, got, 0) + } + + got, gotErr := ConsumeLiteral([]byte(tt.in), tt.literal) + if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("ConsumeLiteral(%q, %q) = (%v, %v), want (%v, %v)", tt.in, tt.literal, got, gotErr, tt.want, tt.wantErr) + } + }) + } +} + +func TestConsumeString(t *testing.T) { + var errPrev = errors.New("same as previous error") + tests := []struct { + in string + simple bool + want int + wantUTF8 int // consumed bytes if validateUTF8 is specified + wantFlags ValueFlags + wantUnquote string + wantErr error + wantErrUTF8 error // error if validateUTF8 is specified + wantErrUnquote error + }{ + {``, false, 0, 0, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"`, false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`""`, true, 2, 2, 0, "", nil, nil, nil}, + {`""x`, true, 2, 2, 0, "", nil, nil, NewInvalidCharacterError("x", "after string value")}, + {` ""x`, false, 0, 0, 0, "", NewInvalidCharacterError(" ", "at start of string (expecting '\"')"), errPrev, errPrev}, + {`"hello`, false, 6, 6, 0, "hello", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"hello"`, true, 7, 7, 0, "hello", nil, nil, nil}, + {"\"\x00\"", false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidCharacterError("\x00", "in string (expecting non-control character)"), errPrev, errPrev}, + {`"\u0000"`, false, 8, 8, stringNonVerbatim, "\x00", nil, nil, nil}, + {"\"\x1f\"", false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidCharacterError("\x1f", "in string (expecting non-control character)"), errPrev, errPrev}, + {`"\u001f"`, false, 8, 8, stringNonVerbatim, "\x1f", nil, nil, nil}, + {`"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, true, 54, 54, 0, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", nil, nil, nil}, + {"\" !#$%'()*+,-./0123456789:;=?@[]^_`{|}~\x7f\"", true, 41, 41, 0, " !#$%'()*+,-./0123456789:;=?@[]^_`{|}~\x7f", nil, nil, nil}, + {`"&"`, false, 3, 3, 0, "&", nil, nil, nil}, + {`"<"`, false, 3, 3, 0, "<", nil, nil, nil}, + {`">"`, false, 3, 3, 0, ">", nil, nil, nil}, + {"\"x\x80\"", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", nil, ErrInvalidUTF8, errPrev}, + {"\"x\xff\"", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", nil, ErrInvalidUTF8, errPrev}, + {"\"x\xc0", false, 3, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, + {"\"x\xc0\x80\"", false, 5, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, + {"\"x\xe0", false, 2, 2, 0, "x", io.ErrUnexpectedEOF, errPrev, errPrev}, + {"\"x\xe0\x80", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, + {"\"x\xe0\x80\x80\"", false, 6, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, + {"\"x\xf0", false, 2, 2, 0, "x", io.ErrUnexpectedEOF, errPrev, errPrev}, + {"\"x\xf0\x80", false, 4, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, + {"\"x\xf0\x80\x80", false, 5, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", io.ErrUnexpectedEOF, ErrInvalidUTF8, io.ErrUnexpectedEOF}, + {"\"x\xf0\x80\x80\x80\"", false, 7, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, + {"\"x\xed\xba\xad\"", false, 6, 2, stringNonVerbatim | stringNonCanonical, "x\ufffd\ufffd\ufffd", nil, ErrInvalidUTF8, errPrev}, + {"\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", false, 25, 25, 0, "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, nil, nil}, + {`"¢"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"¢"`[:3], false, 3, 3, 0, "¢", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote + {`"¢"`[:4], false, 4, 4, 0, "¢", nil, nil, nil}, + {`"€"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"€"`[:3], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"€"`[:4], false, 4, 4, 0, "€", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote + {`"€"`[:5], false, 5, 5, 0, "€", nil, nil, nil}, + {`"𐍈"`[:2], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"𐍈"`[:3], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"𐍈"`[:4], false, 1, 1, 0, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"𐍈"`[:5], false, 5, 5, 0, "𐍈", io.ErrUnexpectedEOF, errPrev, errPrev}, // missing terminating quote + {`"𐍈"`[:6], false, 6, 6, 0, "𐍈", nil, nil, nil}, + {`"x\`, false, 2, 2, stringNonVerbatim, "x", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"x\"`, false, 4, 4, stringNonVerbatim, "x\"", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"x\x"`, false, 2, 2, stringNonVerbatim | stringNonCanonical, "x", NewInvalidEscapeSequenceError(`\x`), errPrev, errPrev}, + {`"\"\\\b\f\n\r\t"`, false, 16, 16, stringNonVerbatim, "\"\\\b\f\n\r\t", nil, nil, nil}, + {`"/"`, true, 3, 3, 0, "/", nil, nil, nil}, + {`"\/"`, false, 4, 4, stringNonVerbatim | stringNonCanonical, "/", nil, nil, nil}, + {`"\u002f"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "/", nil, nil, nil}, + {`"\u`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\uf`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\uff`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\ufff`, false, 1, 1, stringNonVerbatim, "", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\ufffd`, false, 7, 7, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\ufffd"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "\ufffd", nil, nil, nil}, + {`"\uABCD"`, false, 8, 8, stringNonVerbatim | stringNonCanonical, "\uabcd", nil, nil, nil}, + {`"\uefX0"`, false, 1, 1, stringNonVerbatim | stringNonCanonical, "", NewInvalidEscapeSequenceError(`\uefX0`), errPrev, errPrev}, + {`"\uDEAD`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\uDEAD"`, false, 8, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", nil, NewInvalidEscapeSequenceError(`\uDEAD"`), errPrev}, + {`"\uDEAD______"`, false, 14, 1, stringNonVerbatim | stringNonCanonical, "\ufffd______", nil, NewInvalidEscapeSequenceError(`\uDEAD______`), errPrev}, + {`"\uDEAD\uXXXX"`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", NewInvalidEscapeSequenceError(`\uXXXX`), NewInvalidEscapeSequenceError(`\uDEAD\uXXXX`), NewInvalidEscapeSequenceError(`\uXXXX`)}, + {`"\uDEAD\uBEEF"`, false, 14, 1, stringNonVerbatim | stringNonCanonical, "\ufffd\ubeef", nil, NewInvalidEscapeSequenceError(`\uDEAD\uBEEF`), errPrev}, + {`"\uD800\udea`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, errPrev, errPrev}, + {`"\uD800\udb`, false, 7, 1, stringNonVerbatim | stringNonCanonical, "\ufffd", io.ErrUnexpectedEOF, NewInvalidEscapeSequenceError(`\uD800\udb`), io.ErrUnexpectedEOF}, + {`"\uD800\udead"`, false, 14, 14, stringNonVerbatim | stringNonCanonical, "\U000102ad", nil, nil, nil}, + {`"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, false, 50, 50, stringNonVerbatim | stringNonCanonical, "\"\\/\b\f\n\r\t", nil, nil, nil}, + {`"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`, false, 56, 56, stringNonVerbatim | stringNonCanonical, "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", nil, nil, nil}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + if tt.wantErrUTF8 == errPrev { + tt.wantErrUTF8 = tt.wantErr + } + if tt.wantErrUnquote == errPrev { + tt.wantErrUnquote = tt.wantErrUTF8 + } + + switch got := ConsumeSimpleString([]byte(tt.in)); { + case tt.simple && got != tt.want: + t.Errorf("consumeSimpleString(%q) = %v, want %v", tt.in, got, tt.want) + case !tt.simple && got != 0: + t.Errorf("consumeSimpleString(%q) = %v, want %v", tt.in, got, 0) + } + + var gotFlags ValueFlags + got, gotErr := ConsumeString(&gotFlags, []byte(tt.in), false) + if gotFlags != tt.wantFlags { + t.Errorf("consumeString(%q, false) flags = %v, want %v", tt.in, gotFlags, tt.wantFlags) + } + if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) + } + + got, gotErr = ConsumeString(&gotFlags, []byte(tt.in), true) + if got != tt.wantUTF8 || !reflect.DeepEqual(gotErr, tt.wantErrUTF8) { + t.Errorf("consumeString(%q, false) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.wantUTF8, tt.wantErrUTF8) + } + + gotUnquote, gotErr := AppendUnquote(nil, tt.in) + if string(gotUnquote) != tt.wantUnquote || !reflect.DeepEqual(gotErr, tt.wantErrUnquote) { + t.Errorf("AppendUnquote(nil, %q) = (%q, %v), want (%q, %v)", tt.in[:got], gotUnquote, gotErr, tt.wantUnquote, tt.wantErrUnquote) + } + }) + } +} + +func TestConsumeNumber(t *testing.T) { + tests := []struct { + in string + simple bool + want int + wantErr error + }{ + {"", false, 0, io.ErrUnexpectedEOF}, + {`"NaN"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")}, + {`"Infinity"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")}, + {`"-Infinity"`, false, 0, NewInvalidCharacterError("\"", "in number (expecting digit)")}, + {".0", false, 0, NewInvalidCharacterError(".", "in number (expecting digit)")}, + {"0", true, 1, nil}, + {"-0", false, 2, nil}, + {"+0", false, 0, NewInvalidCharacterError("+", "in number (expecting digit)")}, + {"1", true, 1, nil}, + {"-1", false, 2, nil}, + {"00", true, 1, nil}, + {"-00", false, 2, nil}, + {"01", true, 1, nil}, + {"-01", false, 2, nil}, + {"0i", true, 1, nil}, + {"-0i", false, 2, nil}, + {"0f", true, 1, nil}, + {"-0f", false, 2, nil}, + {"9876543210", true, 10, nil}, + {"-9876543210", false, 11, nil}, + {"9876543210x", true, 10, nil}, + {"-9876543210x", false, 11, nil}, + {" 9876543210", true, 0, NewInvalidCharacterError(" ", "in number (expecting digit)")}, + {"- 9876543210", false, 1, NewInvalidCharacterError(" ", "in number (expecting digit)")}, + {strings.Repeat("9876543210", 1000), true, 10000, nil}, + {"-" + strings.Repeat("9876543210", 1000), false, 1 + 10000, nil}, + {"0.", false, 1, io.ErrUnexpectedEOF}, + {"-0.", false, 2, io.ErrUnexpectedEOF}, + {"0e", false, 1, io.ErrUnexpectedEOF}, + {"-0e", false, 2, io.ErrUnexpectedEOF}, + {"0E", false, 1, io.ErrUnexpectedEOF}, + {"-0E", false, 2, io.ErrUnexpectedEOF}, + {"0.0", false, 3, nil}, + {"-0.0", false, 4, nil}, + {"0e0", false, 3, nil}, + {"-0e0", false, 4, nil}, + {"0E0", false, 3, nil}, + {"-0E0", false, 4, nil}, + {"0.0123456789", false, 12, nil}, + {"-0.0123456789", false, 13, nil}, + {"1.f", false, 2, NewInvalidCharacterError("f", "in number (expecting digit)")}, + {"-1.f", false, 3, NewInvalidCharacterError("f", "in number (expecting digit)")}, + {"1.e", false, 2, NewInvalidCharacterError("e", "in number (expecting digit)")}, + {"-1.e", false, 3, NewInvalidCharacterError("e", "in number (expecting digit)")}, + {"1e0", false, 3, nil}, + {"-1e0", false, 4, nil}, + {"1E0", false, 3, nil}, + {"-1E0", false, 4, nil}, + {"1Ex", false, 2, NewInvalidCharacterError("x", "in number (expecting digit)")}, + {"-1Ex", false, 3, NewInvalidCharacterError("x", "in number (expecting digit)")}, + {"1e-0", false, 4, nil}, + {"-1e-0", false, 5, nil}, + {"1e+0", false, 4, nil}, + {"-1e+0", false, 5, nil}, + {"1E-0", false, 4, nil}, + {"-1E-0", false, 5, nil}, + {"1E+0", false, 4, nil}, + {"-1E+0", false, 5, nil}, + {"1E+00500", false, 8, nil}, + {"-1E+00500", false, 9, nil}, + {"1E+00500x", false, 8, nil}, + {"-1E+00500x", false, 9, nil}, + {"9876543210.0123456789e+01234589x", false, 31, nil}, + {"-9876543210.0123456789e+01234589x", false, 32, nil}, + {"1_000_000", true, 1, nil}, + {"0x12ef", true, 1, nil}, + {"0x1p-2", true, 1, nil}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + switch got := ConsumeSimpleNumber([]byte(tt.in)); { + case tt.simple && got != tt.want: + t.Errorf("ConsumeSimpleNumber(%q) = %v, want %v", tt.in, got, tt.want) + case !tt.simple && got != 0: + t.Errorf("ConsumeSimpleNumber(%q) = %v, want %v", tt.in, got, 0) + } + + got, gotErr := ConsumeNumber([]byte(tt.in)) + if got != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("ConsumeNumber(%q) = (%v, %v), want (%v, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) + } + }) + } +} + +func TestParseHexUint16(t *testing.T) { + tests := []struct { + in string + want uint16 + wantOk bool + }{ + {"", 0, false}, + {"a", 0, false}, + {"ab", 0, false}, + {"abc", 0, false}, + {"abcd", 0xabcd, true}, + {"abcde", 0, false}, + {"9eA1", 0x9ea1, true}, + {"gggg", 0, false}, + {"0000", 0x0000, true}, + {"1234", 0x1234, true}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + got, gotOk := parseHexUint16([]byte(tt.in)) + if got != tt.want || gotOk != tt.wantOk { + t.Errorf("parseHexUint16(%q) = (0x%04x, %v), want (0x%04x, %v)", tt.in, got, gotOk, tt.want, tt.wantOk) + } + }) + } +} + +func TestParseUint(t *testing.T) { + tests := []struct { + in string + want uint64 + wantOk bool + }{ + {"", 0, false}, + {"0", 0, true}, + {"1", 1, true}, + {"-1", 0, false}, + {"1f", 0, false}, + {"00", 0, false}, + {"01", 0, false}, + {"10", 10, true}, + {"10.9", 0, false}, + {" 10", 0, false}, + {"10 ", 0, false}, + {"123456789", 123456789, true}, + {"123456789d", 0, false}, + {"18446744073709551614", math.MaxUint64 - 1, true}, + {"18446744073709551615", math.MaxUint64, true}, + {"18446744073709551616", math.MaxUint64, false}, + {"18446744073709551620", math.MaxUint64, false}, + {"18446744073709551700", math.MaxUint64, false}, + {"18446744073709552000", math.MaxUint64, false}, + {"18446744073709560000", math.MaxUint64, false}, + {"18446744073709600000", math.MaxUint64, false}, + {"18446744073710000000", math.MaxUint64, false}, + {"18446744073800000000", math.MaxUint64, false}, + {"18446744074000000000", math.MaxUint64, false}, + {"18446744080000000000", math.MaxUint64, false}, + {"18446744100000000000", math.MaxUint64, false}, + {"18446745000000000000", math.MaxUint64, false}, + {"18446750000000000000", math.MaxUint64, false}, + {"18446800000000000000", math.MaxUint64, false}, + {"18447000000000000000", math.MaxUint64, false}, + {"18450000000000000000", math.MaxUint64, false}, + {"18500000000000000000", math.MaxUint64, false}, + {"19000000000000000000", math.MaxUint64, false}, + {"19999999999999999999", math.MaxUint64, false}, + {"20000000000000000000", math.MaxUint64, false}, + {"100000000000000000000", math.MaxUint64, false}, + {"99999999999999999999999999999999", math.MaxUint64, false}, + {"99999999999999999999999999999999f", 0, false}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + got, gotOk := ParseUint([]byte(tt.in)) + if got != tt.want || gotOk != tt.wantOk { + t.Errorf("ParseUint(%q) = (%v, %v), want (%v, %v)", tt.in, got, gotOk, tt.want, tt.wantOk) + } + }) + } +} + +func TestParseFloat(t *testing.T) { + tests := []struct { + in string + want32 float64 + want64 float64 + wantOk bool + }{ + {"0", 0, 0, true}, + {"-1", -1, -1, true}, + {"1", 1, 1, true}, + + {"-16777215", -16777215, -16777215, true}, // -(1<<24 - 1) + {"16777215", 16777215, 16777215, true}, // +(1<<24 - 1) + {"-16777216", -16777216, -16777216, true}, // -(1<<24) + {"16777216", 16777216, 16777216, true}, // +(1<<24) + {"-16777217", -16777216, -16777217, true}, // -(1<<24 + 1) + {"16777217", 16777216, 16777217, true}, // +(1<<24 + 1) + + {"-9007199254740991", -9007199254740992, -9007199254740991, true}, // -(1<<53 - 1) + {"9007199254740991", 9007199254740992, 9007199254740991, true}, // +(1<<53 - 1) + {"-9007199254740992", -9007199254740992, -9007199254740992, true}, // -(1<<53) + {"9007199254740992", 9007199254740992, 9007199254740992, true}, // +(1<<53) + {"-9007199254740993", -9007199254740992, -9007199254740992, true}, // -(1<<53 + 1) + {"9007199254740993", 9007199254740992, 9007199254740992, true}, // +(1<<53 + 1) + + {"-1e1000", -math.MaxFloat32, -math.MaxFloat64, false}, + {"1e1000", +math.MaxFloat32, +math.MaxFloat64, false}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + got32, gotOk32 := ParseFloat([]byte(tt.in), 32) + if got32 != tt.want32 || gotOk32 != tt.wantOk { + t.Errorf("ParseFloat(%q, 32) = (%v, %v), want (%v, %v)", tt.in, got32, gotOk32, tt.want32, tt.wantOk) + } + + got64, gotOk64 := ParseFloat([]byte(tt.in), 64) + if got64 != tt.want64 || gotOk64 != tt.wantOk { + t.Errorf("ParseFloat(%q, 64) = (%v, %v), want (%v, %v)", tt.in, got64, gotOk64, tt.want64, tt.wantOk) + } + }) + } +} diff --git a/src/encoding/json/internal/jsonwire/encode.go b/src/encoding/json/internal/jsonwire/encode.go new file mode 100644 index 0000000000..3901ff8bed --- /dev/null +++ b/src/encoding/json/internal/jsonwire/encode.go @@ -0,0 +1,294 @@ +// 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 + +package jsonwire + +import ( + "math" + "slices" + "strconv" + "unicode/utf16" + "unicode/utf8" + + "encoding/json/internal/jsonflags" +) + +// escapeASCII reports whether the ASCII character needs to be escaped. +// It conservatively assumes EscapeForHTML. +var escapeASCII = [...]uint8{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // escape control characters + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // escape control characters + 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // escape '"' and '&' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, // escape '<' and '>' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // escape '\\' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +} + +// 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 { + var i int + for uint(len(src)) > uint(i) { + if c := src[i]; c < utf8.RuneSelf { + if escapeASCII[c] > 0 { + return true + } + i++ + } else { + r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[i:]))) + if r == utf8.RuneError || r == '\u2028' || r == '\u2029' { + return true + } + i += rn + } + } + return false +} + +// AppendQuote appends src to dst as a JSON string per RFC 7159, section 7. +// +// It takes in flags and respects the following: +// - EscapeForHTML escapes '<', '>', and '&'. +// - EscapeForJS escapes '\u2028' and '\u2029'. +// - AllowInvalidUTF8 avoids reporting an error for invalid UTF-8. +// +// Regardless of whether AllowInvalidUTF8 is specified, +// 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) { + var i, n int + var hasInvalidUTF8 bool + dst = slices.Grow(dst, len(`"`)+len(src)+len(`"`)) + dst = append(dst, '"') + for uint(len(src)) > uint(n) { + if c := src[n]; c < utf8.RuneSelf { + // Handle single-byte ASCII. + n++ + if escapeASCII[c] == 0 { + continue // no escaping possibly needed + } + // Handle escaping of single-byte ASCII. + if !(c == '<' || c == '>' || c == '&') || flags.Get(jsonflags.EscapeForHTML) { + dst = append(dst, src[i:n-1]...) + dst = appendEscapedASCII(dst, c) + i = n + } + } else { + // Handle multi-byte Unicode. + r, rn := utf8.DecodeRuneInString(string(truncateMaxUTF8(src[n:]))) + n += rn + if r != utf8.RuneError && r != '\u2028' && r != '\u2029' { + continue // no escaping possibly needed + } + // Handle escaping of multi-byte Unicode. + switch { + case isInvalidUTF8(r, rn): + hasInvalidUTF8 = true + dst = append(dst, src[i:n-rn]...) + if flags.Get(jsonflags.EscapeInvalidUTF8) { + dst = append(dst, `\ufffd`...) + } else { + dst = append(dst, "\ufffd"...) + } + i = n + case (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS): + dst = append(dst, src[i:n-rn]...) + dst = appendEscapedUnicode(dst, r) + i = n + } + } + } + dst = append(dst, src[i:n]...) + dst = append(dst, '"') + if hasInvalidUTF8 && !flags.Get(jsonflags.AllowInvalidUTF8) { + return dst, ErrInvalidUTF8 + } + return dst, nil +} + +func appendEscapedASCII(dst []byte, c byte) []byte { + switch c { + case '"', '\\': + dst = append(dst, '\\', c) + case '\b': + dst = append(dst, "\\b"...) + case '\f': + dst = append(dst, "\\f"...) + case '\n': + dst = append(dst, "\\n"...) + case '\r': + dst = append(dst, "\\r"...) + case '\t': + dst = append(dst, "\\t"...) + default: + dst = appendEscapedUTF16(dst, uint16(c)) + } + return dst +} + +func appendEscapedUnicode(dst []byte, r rune) []byte { + if r1, r2 := utf16.EncodeRune(r); r1 != '\ufffd' && r2 != '\ufffd' { + dst = appendEscapedUTF16(dst, uint16(r1)) + dst = appendEscapedUTF16(dst, uint16(r2)) + } else { + dst = appendEscapedUTF16(dst, uint16(r)) + } + return dst +} + +func appendEscapedUTF16(dst []byte, x uint16) []byte { + const hex = "0123456789abcdef" + return append(dst, '\\', 'u', hex[(x>>12)&0xf], hex[(x>>8)&0xf], hex[(x>>4)&0xf], hex[(x>>0)&0xf]) +} + +// ReformatString consumes a JSON string from src and appends it to dst, +// reformatting it if necessary according to the specified flags. +// It returns the appended output and the number of consumed input bytes. +func ReformatString(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) { + // TODO: Should this update ValueFlags as input? + var valFlags ValueFlags + n, err := ConsumeString(&valFlags, src, !flags.Get(jsonflags.AllowInvalidUTF8)) + if err != nil { + return dst, n, err + } + + // If the output requires no special escapes, and the input + // is already in canonical form or should be preserved verbatim, + // then directly copy the input to the output. + if !flags.Get(jsonflags.AnyEscape) && + (valFlags.IsCanonical() || flags.Get(jsonflags.PreserveRawStrings)) { + dst = append(dst, src[:n]...) // copy the string verbatim + return dst, n, nil + } + + // Under [jsonflags.PreserveRawStrings], any pre-escaped sequences + // remain escaped, however we still need to respect the + // [jsonflags.EscapeForHTML] and [jsonflags.EscapeForJS] options. + if flags.Get(jsonflags.PreserveRawStrings) { + var i, lastAppendIndex int + for i < n { + if c := src[i]; c < utf8.RuneSelf { + if (c == '<' || c == '>' || c == '&') && flags.Get(jsonflags.EscapeForHTML) { + dst = append(dst, src[lastAppendIndex:i]...) + dst = appendEscapedASCII(dst, c) + lastAppendIndex = i + 1 + } + i++ + } else { + r, rn := utf8.DecodeRune(truncateMaxUTF8(src[i:])) + if (r == '\u2028' || r == '\u2029') && flags.Get(jsonflags.EscapeForJS) { + dst = append(dst, src[lastAppendIndex:i]...) + dst = appendEscapedUnicode(dst, r) + lastAppendIndex = i + rn + } + i += rn + } + } + return append(dst, src[lastAppendIndex:n]...), n, nil + } + + // The input contains characters that might need escaping, + // unnecessary escape sequences, or invalid UTF-8. + // Perform a round-trip unquote and quote to properly reformat + // these sequences according the current flags. + b, _ := AppendUnquote(nil, src[:n]) + dst, _ = AppendQuote(dst, b, flags) + return dst, n, nil +} + +// AppendFloat appends src to dst as a JSON number per RFC 7159, section 6. +// It formats numbers similar to the ES6 number-to-string conversion. +// See https://go.dev/issue/14135. +// +// The output is identical to ECMA-262, 6th edition, section 7.1.12.1 and with +// RFC 8785, section 3.2.2.3 for 64-bit floating-point numbers except for -0, +// which is formatted as -0 instead of just 0. +// +// For 32-bit floating-point numbers, +// the output is a 32-bit equivalent of the algorithm. +// Note that ECMA-262 specifies no algorithm for 32-bit numbers. +func AppendFloat(dst []byte, src float64, bits int) []byte { + if bits == 32 { + src = float64(float32(src)) + } + + abs := math.Abs(src) + fmt := byte('f') + if abs != 0 { + if bits == 64 && (float64(abs) < 1e-6 || float64(abs) >= 1e21) || + bits == 32 && (float32(abs) < 1e-6 || float32(abs) >= 1e21) { + fmt = 'e' + } + } + dst = strconv.AppendFloat(dst, src, fmt, -1, bits) + if fmt == 'e' { + // Clean up e-09 to e-9. + n := len(dst) + if n >= 4 && dst[n-4] == 'e' && dst[n-3] == '-' && dst[n-2] == '0' { + dst[n-2] = dst[n-1] + dst = dst[:n-1] + } + } + return dst +} + +// ReformatNumber consumes a JSON string from src and appends it to dst, +// canonicalizing it if specified. +// It returns the appended output and the number of consumed input bytes. +func ReformatNumber(dst, src []byte, flags *jsonflags.Flags) ([]byte, int, error) { + n, err := ConsumeNumber(src) + if err != nil { + return dst, n, err + } + if !flags.Get(jsonflags.CanonicalizeNumbers) { + dst = append(dst, src[:n]...) // copy the number verbatim + return dst, n, nil + } + + // Identify the kind of number. + var isFloat bool + for _, c := range src[:n] { + if c == '.' || c == 'e' || c == 'E' { + isFloat = true // has fraction or exponent + break + } + } + + // Check if need to canonicalize this kind of number. + switch { + case string(src[:n]) == "-0": + break // canonicalize -0 as 0 regardless of kind + case isFloat: + if !flags.Get(jsonflags.CanonicalizeRawFloats) { + dst = append(dst, src[:n]...) // copy the number verbatim + return dst, n, nil + } + default: + // As an optimization, we can copy integer numbers below 2⁵³ verbatim + // since the canonical form is always identical. + const maxExactIntegerDigits = 16 // len(strconv.AppendUint(nil, 1<<53, 10)) + if !flags.Get(jsonflags.CanonicalizeRawInts) || n < maxExactIntegerDigits { + dst = append(dst, src[:n]...) // copy the number verbatim + return dst, n, nil + } + } + + // Parse and reformat the number (which uses a canonical format). + fv, _ := strconv.ParseFloat(string(src[:n]), 64) + switch { + case fv == 0: + fv = 0 // normalize negative zero as just zero + case math.IsInf(fv, +1): + fv = +math.MaxFloat64 + case math.IsInf(fv, -1): + fv = -math.MaxFloat64 + } + return AppendFloat(dst, fv, 64), n, nil +} diff --git a/src/encoding/json/internal/jsonwire/encode_test.go b/src/encoding/json/internal/jsonwire/encode_test.go new file mode 100644 index 0000000000..6459d20e09 --- /dev/null +++ b/src/encoding/json/internal/jsonwire/encode_test.go @@ -0,0 +1,332 @@ +// 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 + +package jsonwire + +import ( + "bufio" + "bytes" + "compress/gzip" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "flag" + "math" + "net/http" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "encoding/json/internal/jsonflags" +) + +func TestAppendQuote(t *testing.T) { + tests := []struct { + in string + flags jsonflags.Bools + want string + wantErr error + wantErrUTF8 error + }{ + {"", 0, `""`, nil, nil}, + {"hello", 0, `"hello"`, nil, nil}, + {"\x00", 0, `"\u0000"`, nil, nil}, + {"\x1f", 0, `"\u001f"`, nil, nil}, + {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", 0, `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, nil, nil}, + {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", 0, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil}, + {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", jsonflags.EscapeForHTML, "\" !#$%\\u0026'()*+,-./0123456789:;\\u003c=\\u003e?@[]^_`{|}~\x7f\"", nil, nil}, + {" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f", jsonflags.EscapeForJS, "\" !#$%&'()*+,-./0123456789:;<=>?@[]^_`{|}~\x7f\"", nil, nil}, + {"\u2027\u2028\u2029\u2030", 0, "\"\u2027\u2028\u2029\u2030\"", nil, nil}, + {"\u2027\u2028\u2029\u2030", jsonflags.EscapeForHTML, "\"\u2027\u2028\u2029\u2030\"", nil, nil}, + {"\u2027\u2028\u2029\u2030", jsonflags.EscapeForJS, "\"\u2027\\u2028\\u2029\u2030\"", nil, nil}, + {"x\x80\ufffd", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xff\ufffd", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xc0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xc0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xe0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xe0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xe0\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xf0", 0, "\"x\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xf0\x80", 0, "\"x\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xf0\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xf0\x80\x80\x80", 0, "\"x\ufffd\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"x\xed\xba\xad", 0, "\"x\ufffd\ufffd\ufffd\"", nil, ErrInvalidUTF8}, + {"\"\\/\b\f\n\r\t", 0, `"\"\\/\b\f\n\r\t"`, nil, nil}, + {"Ù©(-̮̮̃-̃)Û¶ Ù©(●̮̮̃•̃)Û¶ Ù©(͡๏̯͡๏)Û¶ Ù©(-̮̮̃•̃).", 0, `"Ù©(-̮̮̃-̃)Û¶ Ù©(●̮̮̃•̃)Û¶ Ù©(͡๏̯͡๏)Û¶ Ù©(-̮̮̃•̃)."`, nil, nil}, + {"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602", 0, "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", nil, nil}, + {"\u0000\u001f\u0020\u0022\u0026\u003c\u003e\u005c\u007f\u0080\u2028\u2029\ufffd\U0001f602", 0, "\"\\u0000\\u001f\u0020\\\"\u0026\u003c\u003e\\\\\u007f\u0080\u2028\u2029\ufffd\U0001f602\"", nil, nil}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + var flags jsonflags.Flags + flags.Set(tt.flags | 1) + + flags.Set(jsonflags.AllowInvalidUTF8 | 1) + got, gotErr := AppendQuote(nil, tt.in, &flags) + if string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) + } + flags.Set(jsonflags.AllowInvalidUTF8 | 0) + switch got, gotErr := AppendQuote(nil, tt.in, &flags); { + case tt.wantErrUTF8 == nil && (string(got) != tt.want || !reflect.DeepEqual(gotErr, tt.wantErr)): + t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErr) + case tt.wantErrUTF8 != nil && (!strings.HasPrefix(tt.want, string(got)) || !reflect.DeepEqual(gotErr, tt.wantErrUTF8)): + t.Errorf("AppendQuote(nil, %q, ...) = (%s, %v), want (%s, %v)", tt.in, got, gotErr, tt.want, tt.wantErrUTF8) + } + }) + } +} + +func TestAppendNumber(t *testing.T) { + tests := []struct { + in float64 + want32 string + want64 string + }{ + {math.E, "2.7182817", "2.718281828459045"}, + {math.Pi, "3.1415927", "3.141592653589793"}, + {math.SmallestNonzeroFloat32, "1e-45", "1.401298464324817e-45"}, + {math.SmallestNonzeroFloat64, "0", "5e-324"}, + {math.MaxFloat32, "3.4028235e+38", "3.4028234663852886e+38"}, + {math.MaxFloat64, "", "1.7976931348623157e+308"}, + {0.1111111111111111, "0.11111111", "0.1111111111111111"}, + {0.2222222222222222, "0.22222222", "0.2222222222222222"}, + {0.3333333333333333, "0.33333334", "0.3333333333333333"}, + {0.4444444444444444, "0.44444445", "0.4444444444444444"}, + {0.5555555555555555, "0.5555556", "0.5555555555555555"}, + {0.6666666666666666, "0.6666667", "0.6666666666666666"}, + {0.7777777777777777, "0.7777778", "0.7777777777777777"}, + {0.8888888888888888, "0.8888889", "0.8888888888888888"}, + {0.9999999999999999, "1", "0.9999999999999999"}, + + // The following entries are from RFC 8785, appendix B + // which are designed to ensure repeatable formatting of 64-bit floats. + {math.Float64frombits(0x0000000000000000), "0", "0"}, + {math.Float64frombits(0x8000000000000000), "-0", "-0"}, // differs from RFC 8785 + {math.Float64frombits(0x0000000000000001), "0", "5e-324"}, + {math.Float64frombits(0x8000000000000001), "-0", "-5e-324"}, + {math.Float64frombits(0x7fefffffffffffff), "", "1.7976931348623157e+308"}, + {math.Float64frombits(0xffefffffffffffff), "", "-1.7976931348623157e+308"}, + {math.Float64frombits(0x4340000000000000), "9007199000000000", "9007199254740992"}, + {math.Float64frombits(0xc340000000000000), "-9007199000000000", "-9007199254740992"}, + {math.Float64frombits(0x4430000000000000), "295147900000000000000", "295147905179352830000"}, + {math.Float64frombits(0x44b52d02c7e14af5), "1e+23", "9.999999999999997e+22"}, + {math.Float64frombits(0x44b52d02c7e14af6), "1e+23", "1e+23"}, + {math.Float64frombits(0x44b52d02c7e14af7), "1e+23", "1.0000000000000001e+23"}, + {math.Float64frombits(0x444b1ae4d6e2ef4e), "1e+21", "999999999999999700000"}, + {math.Float64frombits(0x444b1ae4d6e2ef4f), "1e+21", "999999999999999900000"}, + {math.Float64frombits(0x444b1ae4d6e2ef50), "1e+21", "1e+21"}, + {math.Float64frombits(0x3eb0c6f7a0b5ed8c), "0.000001", "9.999999999999997e-7"}, + {math.Float64frombits(0x3eb0c6f7a0b5ed8d), "0.000001", "0.000001"}, + {math.Float64frombits(0x41b3de4355555553), "333333340", "333333333.3333332"}, + {math.Float64frombits(0x41b3de4355555554), "333333340", "333333333.33333325"}, + {math.Float64frombits(0x41b3de4355555555), "333333340", "333333333.3333333"}, + {math.Float64frombits(0x41b3de4355555556), "333333340", "333333333.3333334"}, + {math.Float64frombits(0x41b3de4355555557), "333333340", "333333333.33333343"}, + {math.Float64frombits(0xbecbf647612f3696), "-0.0000033333333", "-0.0000033333333333333333"}, + {math.Float64frombits(0x43143ff3c1cb0959), "1424953900000000", "1424953923781206.2"}, + + // The following are select entries from RFC 8785, appendix B, + // but modified for equivalent 32-bit behavior. + {float64(math.Float32frombits(0x65a96815)), "9.999999e+22", "9.999998877476383e+22"}, + {float64(math.Float32frombits(0x65a96816)), "1e+23", "9.999999778196308e+22"}, + {float64(math.Float32frombits(0x65a96817)), "1.0000001e+23", "1.0000000678916234e+23"}, + {float64(math.Float32frombits(0x6258d725)), "999999900000000000000", "999999879303389000000"}, + {float64(math.Float32frombits(0x6258d726)), "999999950000000000000", "999999949672133200000"}, + {float64(math.Float32frombits(0x6258d727)), "1e+21", "1.0000000200408773e+21"}, + {float64(math.Float32frombits(0x6258d728)), "1.0000001e+21", "1.0000000904096215e+21"}, + {float64(math.Float32frombits(0x358637bc)), "9.999999e-7", "9.99999883788405e-7"}, + {float64(math.Float32frombits(0x358637bd)), "0.000001", "9.999999974752427e-7"}, + {float64(math.Float32frombits(0x358637be)), "0.0000010000001", "0.0000010000001111620804"}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + if got32 := string(AppendFloat(nil, tt.in, 32)); got32 != tt.want32 && tt.want32 != "" { + t.Errorf("AppendFloat(nil, %v, 32) = %v, want %v", tt.in, got32, tt.want32) + } + if got64 := string(AppendFloat(nil, tt.in, 64)); got64 != tt.want64 && tt.want64 != "" { + t.Errorf("AppendFloat(nil, %v, 64) = %v, want %v", tt.in, got64, tt.want64) + } + }) + } +} + +// The default of 1e4 lines was chosen since it is sufficiently large to include +// test numbers from all three categories (i.e., static, series, and random). +// Yet, it is sufficiently low to execute quickly relative to other tests. +// +// Processing 1e8 lines takes a minute and processes about 4GiB worth of text. +var testCanonicalNumberLines = flag.Float64("canonical-number-lines", 1e4, "specify the number of lines to check from the canonical numbers testdata") + +// TestCanonicalNumber verifies that appendNumber complies with RFC 8785 +// according to the testdata provided by the reference implementation. +// See https://github.com/cyberphone/json-canonicalization/tree/master/testdata#es6-numbers. +func TestCanonicalNumber(t *testing.T) { + const testfileURL = "https://github.com/cyberphone/json-canonicalization/releases/download/es6testfile/es6testfile100m.txt.gz" + hashes := map[float64]string{ + 1e3: "be18b62b6f69cdab33a7e0dae0d9cfa869fda80ddc712221570f9f40a5878687", + 1e4: "b9f7a8e75ef22a835685a52ccba7f7d6bdc99e34b010992cbc5864cd12be6892", + 1e5: "22776e6d4b49fa294a0d0f349268e5c28808fe7e0cb2bcbe28f63894e494d4c7", + 1e6: "49415fee2c56c77864931bd3624faad425c3c577d6d74e89a83bc725506dad16", + 1e7: "b9f8a44a91d46813b21b9602e72f112613c91408db0b8341fb94603d9db135e0", + 1e8: "0f7dda6b0837dde083c5d6b896f7d62340c8a2415b0c7121d83145e08a755272", + } + wantHash := hashes[*testCanonicalNumberLines] + if wantHash == "" { + t.Fatalf("canonical-number-lines must be one of the following values: 1e3, 1e4, 1e5, 1e6, 1e7, 1e8") + } + numLines := int(*testCanonicalNumberLines) + + // generator returns a function that generates the next float64 to format. + // This implements the algorithm specified in the reference implementation. + generator := func() func() float64 { + static := [...]uint64{ + 0x0000000000000000, 0x8000000000000000, 0x0000000000000001, 0x8000000000000001, + 0xc46696695dbd1cc3, 0xc43211ede4974a35, 0xc3fce97ca0f21056, 0xc3c7213080c1a6ac, + 0xc39280f39a348556, 0xc35d9b1f5d20d557, 0xc327af4c4a80aaac, 0xc2f2f2a36ecd5556, + 0xc2be51057e155558, 0xc28840d131aaaaac, 0xc253670dc1555557, 0xc21f0b4935555557, + 0xc1e8d5d42aaaaaac, 0xc1b3de4355555556, 0xc17fca0555555556, 0xc1496e6aaaaaaaab, + 0xc114585555555555, 0xc0e046aaaaaaaaab, 0xc0aa0aaaaaaaaaaa, 0xc074d55555555555, + 0xc040aaaaaaaaaaab, 0xc00aaaaaaaaaaaab, 0xbfd5555555555555, 0xbfa1111111111111, + 0xbf6b4e81b4e81b4f, 0xbf35d867c3ece2a5, 0xbf0179ec9cbd821e, 0xbecbf647612f3696, + 0xbe965e9f80f29212, 0xbe61e54c672874db, 0xbe2ca213d840baf8, 0xbdf6e80fe033c8c6, + 0xbdc2533fe68fd3d2, 0xbd8d51ffd74c861c, 0xbd5774ccac3d3817, 0xbd22c3d6f030f9ac, + 0xbcee0624b3818f79, 0xbcb804ea293472c7, 0xbc833721ba905bd3, 0xbc4ebe9c5db3c61e, + 0xbc18987d17c304e5, 0xbbe3ad30dfcf371d, 0xbbaf7b816618582f, 0xbb792f9ab81379bf, + 0xbb442615600f9499, 0xbb101e77800c76e1, 0xbad9ca58cce0be35, 0xbaa4a1e0a3e6fe90, + 0xba708180831f320d, 0xba3a68cd9e985016, 0x446696695dbd1cc3, 0x443211ede4974a35, + 0x43fce97ca0f21056, 0x43c7213080c1a6ac, 0x439280f39a348556, 0x435d9b1f5d20d557, + 0x4327af4c4a80aaac, 0x42f2f2a36ecd5556, 0x42be51057e155558, 0x428840d131aaaaac, + 0x4253670dc1555557, 0x421f0b4935555557, 0x41e8d5d42aaaaaac, 0x41b3de4355555556, + 0x417fca0555555556, 0x41496e6aaaaaaaab, 0x4114585555555555, 0x40e046aaaaaaaaab, + 0x40aa0aaaaaaaaaaa, 0x4074d55555555555, 0x4040aaaaaaaaaaab, 0x400aaaaaaaaaaaab, + 0x3fd5555555555555, 0x3fa1111111111111, 0x3f6b4e81b4e81b4f, 0x3f35d867c3ece2a5, + 0x3f0179ec9cbd821e, 0x3ecbf647612f3696, 0x3e965e9f80f29212, 0x3e61e54c672874db, + 0x3e2ca213d840baf8, 0x3df6e80fe033c8c6, 0x3dc2533fe68fd3d2, 0x3d8d51ffd74c861c, + 0x3d5774ccac3d3817, 0x3d22c3d6f030f9ac, 0x3cee0624b3818f79, 0x3cb804ea293472c7, + 0x3c833721ba905bd3, 0x3c4ebe9c5db3c61e, 0x3c18987d17c304e5, 0x3be3ad30dfcf371d, + 0x3baf7b816618582f, 0x3b792f9ab81379bf, 0x3b442615600f9499, 0x3b101e77800c76e1, + 0x3ad9ca58cce0be35, 0x3aa4a1e0a3e6fe90, 0x3a708180831f320d, 0x3a3a68cd9e985016, + 0x4024000000000000, 0x4014000000000000, 0x3fe0000000000000, 0x3fa999999999999a, + 0x3f747ae147ae147b, 0x3f40624dd2f1a9fc, 0x3f0a36e2eb1c432d, 0x3ed4f8b588e368f1, + 0x3ea0c6f7a0b5ed8d, 0x3e6ad7f29abcaf48, 0x3e35798ee2308c3a, 0x3ed539223589fa95, + 0x3ed4ff26cd5a7781, 0x3ed4f95a762283ff, 0x3ed4f8c60703520c, 0x3ed4f8b72f19cd0d, + 0x3ed4f8b5b31c0c8d, 0x3ed4f8b58d1c461a, 0x3ed4f8b5894f7f0e, 0x3ed4f8b588ee37f3, + 0x3ed4f8b588e47da4, 0x3ed4f8b588e3849c, 0x3ed4f8b588e36bb5, 0x3ed4f8b588e36937, + 0x3ed4f8b588e368f8, 0x3ed4f8b588e368f1, 0x3ff0000000000000, 0xbff0000000000000, + 0xbfeffffffffffffa, 0xbfeffffffffffffb, 0x3feffffffffffffa, 0x3feffffffffffffb, + 0x3feffffffffffffc, 0x3feffffffffffffe, 0xbfefffffffffffff, 0xbfefffffffffffff, + 0x3fefffffffffffff, 0x3fefffffffffffff, 0x3fd3333333333332, 0x3fd3333333333333, + 0x3fd3333333333334, 0x0010000000000000, 0x000ffffffffffffd, 0x000fffffffffffff, + 0x7fefffffffffffff, 0xffefffffffffffff, 0x4340000000000000, 0xc340000000000000, + 0x4430000000000000, 0x44b52d02c7e14af5, 0x44b52d02c7e14af6, 0x44b52d02c7e14af7, + 0x444b1ae4d6e2ef4e, 0x444b1ae4d6e2ef4f, 0x444b1ae4d6e2ef50, 0x3eb0c6f7a0b5ed8c, + 0x3eb0c6f7a0b5ed8d, 0x41b3de4355555553, 0x41b3de4355555554, 0x41b3de4355555555, + 0x41b3de4355555556, 0x41b3de4355555557, 0xbecbf647612f3696, 0x43143ff3c1cb0959, + } + var state struct { + idx int + data []byte + block [sha256.Size]byte + } + return func() float64 { + const numSerial = 2000 + var f float64 + switch { + case state.idx < len(static): + f = math.Float64frombits(static[state.idx]) + case state.idx < len(static)+numSerial: + f = math.Float64frombits(0x0010000000000000 + uint64(state.idx-len(static))) + default: + for f == 0 || math.IsNaN(f) || math.IsInf(f, 0) { + if len(state.data) == 0 { + state.block = sha256.Sum256(state.block[:]) + state.data = state.block[:] + } + f = math.Float64frombits(binary.LittleEndian.Uint64(state.data)) + state.data = state.data[8:] + } + } + state.idx++ + return f + } + } + + // Pass through the test twice. In the first pass we only hash the output, + // while in the second pass we check every line against the golden testdata. + // If the hashes match in the first pass, then we skip the second pass. + for _, checkGolden := range []bool{false, true} { + var br *bufio.Reader // for line-by-line reading of es6testfile100m.txt + if checkGolden { + resp, err := http.Get(testfileURL) + if err != nil { + t.Fatalf("http.Get error: %v", err) + } + defer resp.Body.Close() + + zr, err := gzip.NewReader(resp.Body) + if err != nil { + t.Fatalf("gzip.NewReader error: %v", err) + } + + br = bufio.NewReader(zr) + } + + // appendNumberJCS differs from appendNumber only for -0. + appendNumberJCS := func(b []byte, f float64) []byte { + if math.Signbit(f) && f == 0 { + return append(b, '0') + } + return AppendFloat(b, f, 64) + } + + var gotLine []byte + next := generator() + hash := sha256.New() + start := time.Now() + lastPrint := start + for n := 1; n <= numLines; n++ { + // Generate the formatted line for this number. + f := next() + gotLine = gotLine[:0] // reset from previous usage + gotLine = strconv.AppendUint(gotLine, math.Float64bits(f), 16) + gotLine = append(gotLine, ',') + gotLine = appendNumberJCS(gotLine, f) + gotLine = append(gotLine, '\n') + hash.Write(gotLine) + + // Check that the formatted line matches. + if checkGolden { + wantLine, err := br.ReadBytes('\n') + if err != nil { + t.Fatalf("bufio.Reader.ReadBytes error: %v", err) + } + if !bytes.Equal(gotLine, wantLine) { + t.Errorf("mismatch on line %d:\n\tgot %v\n\twant %v", + n, strings.TrimSpace(string(gotLine)), strings.TrimSpace(string(wantLine))) + } + } + + // Print progress. + if now := time.Now(); now.Sub(lastPrint) > time.Second || n == numLines { + remaining := float64(now.Sub(start)) * float64(numLines-n) / float64(n) + t.Logf("%0.3f%% (%v remaining)", + 100.0*float64(n)/float64(numLines), + time.Duration(remaining).Round(time.Second)) + lastPrint = now + } + } + + gotHash := hex.EncodeToString(hash.Sum(nil)) + if gotHash == wantHash { + return // hashes match, no need to check golden testdata + } + } +} diff --git a/src/encoding/json/internal/jsonwire/wire.go b/src/encoding/json/internal/jsonwire/wire.go new file mode 100644 index 0000000000..6cf19c5cfe --- /dev/null +++ b/src/encoding/json/internal/jsonwire/wire.go @@ -0,0 +1,217 @@ +// 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 + +// Package jsonwire implements stateless functionality for handling JSON text. +package jsonwire + +import ( + "cmp" + "errors" + "strconv" + "strings" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// TrimSuffixWhitespace trims JSON 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 + for n >= 0 && (b[n] == ' ' || b[n] == '\t' || b[n] == '\r' || b[n] == '\n') { + n-- + } + return b[:n+1] +} + +// TrimSuffixString trims a valid JSON string at the end of b. +// The behavior is undefined if there is not a valid JSON string present. +func TrimSuffixString(b []byte) []byte { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + if len(b) > 0 && b[len(b)-1] == '"' { + b = b[:len(b)-1] + } + for len(b) >= 2 && !(b[len(b)-1] == '"' && b[len(b)-2] != '\\') { + b = b[:len(b)-1] // trim all characters except an unescaped quote + } + if len(b) > 0 && b[len(b)-1] == '"' { + b = b[:len(b)-1] + } + return b +} + +// HasSuffixByte reports whether b ends with c. +func HasSuffixByte(b []byte, c byte) bool { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + return len(b) > 0 && b[len(b)-1] == c +} + +// TrimSuffixByte removes c from the end of b if it is present. +func TrimSuffixByte(b []byte, c byte) []byte { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + if len(b) > 0 && b[len(b)-1] == c { + return b[:len(b)-1] + } + return b +} + +// QuoteRune quotes the first rune in the input. +func QuoteRune[Bytes ~[]byte | ~string](b Bytes) string { + r, n := utf8.DecodeRuneInString(string(truncateMaxUTF8(b))) + if r == utf8.RuneError && n == 1 { + return `'\x` + strconv.FormatUint(uint64(b[0]), 16) + `'` + } + return strconv.QuoteRune(r) +} + +// 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 { + // 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. + isUTF16Self := func(r rune) bool { + return ('\u0000' <= r && r <= '\uD7FF') || ('\uE000' <= r && r <= '\uFFFF') + } + + for { + if len(x) == 0 || len(y) == 0 { + return cmp.Compare(len(x), len(y)) + } + + // ASCII fast-path. + if x[0] < utf8.RuneSelf || y[0] < utf8.RuneSelf { + if x[0] != y[0] { + return cmp.Compare(x[0], y[0]) + } + x, y = x[1:], y[1:] + continue + } + + // Decode next pair of runes as UTF-8. + rx, nx := utf8.DecodeRuneInString(string(truncateMaxUTF8(x))) + ry, ny := utf8.DecodeRuneInString(string(truncateMaxUTF8(y))) + + selfx := isUTF16Self(rx) + selfy := isUTF16Self(ry) + switch { + // The x rune is a single UTF-16 codepoint, while + // the y rune is a surrogate pair of UTF-16 codepoints. + case selfx && !selfy: + ry, _ = utf16.EncodeRune(ry) + // The y rune is a single UTF-16 codepoint, while + // the x rune is a surrogate pair of UTF-16 codepoints. + case selfy && !selfx: + rx, _ = utf16.EncodeRune(rx) + } + if rx != ry { + return cmp.Compare(rx, ry) + } + + // Check for invalid UTF-8, in which case, + // we just perform a byte-for-byte comparison. + if isInvalidUTF8(rx, nx) || isInvalidUTF8(ry, ny) { + if x[0] != y[0] { + return cmp.Compare(x[0], y[0]) + } + } + x, y = x[nx:], y[ny:] + } +} + +// 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) +} + +func NewInvalidEscapeSequenceError[Bytes ~[]byte | ~string](what Bytes) error { + label := "escape sequence" + if len(what) > 6 { + label = "surrogate pair" + } + needEscape := strings.IndexFunc(string(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") + } +} + +// TruncatePointer optionally truncates the JSON pointer, +// enforcing that the length roughly does not exceed n. +func TruncatePointer(s string, n int) string { + if len(s) <= n { + return s + } + i := n / 2 + j := len(s) - n/2 + + // Avoid truncating a name if there are multiple names present. + if k := strings.LastIndexByte(s[:i], '/'); k > 0 { + i = k + } + if k := strings.IndexByte(s[j:], '/'); k >= 0 { + j += k + len("/") + } + + // Avoid truncation in the middle of a UTF-8 rune. + for i > 0 && isInvalidUTF8(utf8.DecodeLastRuneInString(s[:i])) { + i-- + } + for j < len(s) && isInvalidUTF8(utf8.DecodeRuneInString(s[j:])) { + j++ + } + + // Determine the right middle fragment to use. + var middle string + switch strings.Count(s[i:j], "/") { + case 0: + middle = "…" + case 1: + middle = "…/…" + default: + middle = "…/…/…" + } + if strings.HasPrefix(s[i:j], "/") && middle != "…" { + middle = strings.TrimPrefix(middle, "…") + } + if strings.HasSuffix(s[i:j], "/") && middle != "…" { + middle = strings.TrimSuffix(middle, "…") + } + return s[:i] + middle + s[j:] +} + +func isInvalidUTF8(r rune, rn int) bool { + return r == utf8.RuneError && rn == 1 +} diff --git a/src/encoding/json/internal/jsonwire/wire_test.go b/src/encoding/json/internal/jsonwire/wire_test.go new file mode 100644 index 0000000000..a0bf1d1368 --- /dev/null +++ b/src/encoding/json/internal/jsonwire/wire_test.go @@ -0,0 +1,98 @@ +// 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 + +package jsonwire + +import ( + "cmp" + "slices" + "testing" + "unicode/utf16" + "unicode/utf8" +) + +func TestQuoteRune(t *testing.T) { + tests := []struct{ in, want string }{ + {"x", `'x'`}, + {"\n", `'\n'`}, + {"'", `'\''`}, + {"\xff", `'\xff'`}, + {"💩", `'💩'`}, + {"💩"[:1], `'\xf0'`}, + {"\uffff", `'\uffff'`}, + {"\U00101234", `'\U00101234'`}, + } + for _, tt := range tests { + got := QuoteRune([]byte(tt.in)) + if got != tt.want { + t.Errorf("quoteRune(%q) = %s, want %s", tt.in, got, tt.want) + } + } +} + +var compareUTF16Testdata = []string{"", "\r", "1", "f\xfe", "f\xfe\xff", "f\xff", "\u0080", "\u00f6", "\u20ac", "\U0001f600", "\ufb33"} + +func TestCompareUTF16(t *testing.T) { + for i, si := range compareUTF16Testdata { + for j, sj := range compareUTF16Testdata { + got := CompareUTF16([]byte(si), []byte(sj)) + want := cmp.Compare(i, j) + if got != want { + t.Errorf("CompareUTF16(%q, %q) = %v, want %v", si, sj, got, want) + } + } + } +} + +func FuzzCompareUTF16(f *testing.F) { + for _, td1 := range compareUTF16Testdata { + for _, td2 := range compareUTF16Testdata { + f.Add([]byte(td1), []byte(td2)) + } + } + + // CompareUTF16Simple is identical to CompareUTF16, + // but relies on naively converting a string to a []uint16 codepoints. + // It is easy to verify as correct, but is slow. + CompareUTF16Simple := func(x, y []byte) int { + ux := utf16.Encode([]rune(string(x))) + uy := utf16.Encode([]rune(string(y))) + return slices.Compare(ux, uy) + } + + f.Fuzz(func(t *testing.T, s1, s2 []byte) { + // Compare the optimized and simplified implementations. + got := CompareUTF16(s1, s2) + want := CompareUTF16Simple(s1, s2) + if got != want && utf8.Valid(s1) && utf8.Valid(s2) { + t.Errorf("CompareUTF16(%q, %q) = %v, want %v", s1, s2, got, want) + } + }) +} + +func TestTruncatePointer(t *testing.T) { + tests := []struct{ in, want string }{ + {"hello", "hello"}, + {"/a/b/c", "/a/b/c"}, + {"/a/b/c/d/e/f/g", "/a/b/…/f/g"}, + {"supercalifragilisticexpialidocious", "super…cious"}, + {"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/supe…/…cious"}, + {"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/supe…/…/…cious"}, + {"/a/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious", "/a/…/…cious"}, + {"/supercalifragilisticexpialidocious/supercalifragilisticexpialidocious/b", "/supe…/…/b"}, + {"/fizz/buzz/bazz", "/fizz/…/bazz"}, + {"/fizz/buzz/bazz/razz", "/fizz/…/razz"}, + {"/////////////////////////////", "/////…/////"}, + {"/🎄❤️✨/🎁✅😊/🎅🔥⭐", "/🎄…/…/…⭐"}, + } + for _, tt := range tests { + got := TruncatePointer(tt.in, 10) + if got != tt.want { + t.Errorf("TruncatePointer(%q) = %q, want %q", tt.in, got, tt.want) + } + } + +} diff --git a/src/encoding/json/jsontext/coder_test.go b/src/encoding/json/jsontext/coder_test.go new file mode 100644 index 0000000000..4a9efb3b8f --- /dev/null +++ b/src/encoding/json/jsontext/coder_test.go @@ -0,0 +1,856 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "errors" + "io" + "math" + "math/rand" + "path" + "reflect" + "strings" + "testing" + + "encoding/json/internal/jsontest" + "encoding/json/internal/jsonwire" +) + +func E(err error) *SyntacticError { + return &SyntacticError{Err: err} +} + +func newInvalidCharacterError(prefix, where string) *SyntacticError { + return E(jsonwire.NewInvalidCharacterError(prefix, where)) +} + +func newInvalidEscapeSequenceError(what string) *SyntacticError { + return E(jsonwire.NewInvalidEscapeSequenceError(what)) +} + +func (e *SyntacticError) withPos(prefix string, pointer Pointer) *SyntacticError { + e.ByteOffset = int64(len(prefix)) + e.JSONPointer = pointer + return e +} + +func equalError(x, y error) bool { + return reflect.DeepEqual(x, y) +} + +var ( + zeroToken Token + zeroValue Value +) + +// tokOrVal is either a Token or a Value. +type tokOrVal interface{ Kind() Kind } + +type coderTestdataEntry struct { + name jsontest.CaseName + in string + outCompacted string + outEscaped string // outCompacted if empty; escapes all runes in a string + outIndented string // outCompacted if empty; uses " " for indent prefix and "\t" for indent + outCanonicalized string // outCompacted if empty + tokens []Token + pointers []Pointer +} + +var coderTestdata = []coderTestdataEntry{{ + name: jsontest.Name("Null"), + in: ` null `, + outCompacted: `null`, + tokens: []Token{Null}, + pointers: []Pointer{""}, +}, { + name: jsontest.Name("False"), + in: ` false `, + outCompacted: `false`, + tokens: []Token{False}, +}, { + name: jsontest.Name("True"), + in: ` true `, + outCompacted: `true`, + tokens: []Token{True}, +}, { + name: jsontest.Name("EmptyString"), + in: ` "" `, + outCompacted: `""`, + tokens: []Token{String("")}, +}, { + name: jsontest.Name("SimpleString"), + in: ` "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" `, + outCompacted: `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"`, + outEscaped: `"\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a"`, + tokens: []Token{String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")}, +}, { + name: jsontest.Name("ComplicatedString"), + in: " \"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + ` \ud800\udead \"\\\/\b\f\n\r\t \u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009" `, + outCompacted: "\"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + " 𐊭 \\\"\\\\/\\b\\f\\n\\r\\t \\\"\\\\/\\b\\f\\n\\r\\t\"", + outEscaped: `"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u4e16\u754c\u0020\ud83c\udf1f\u2605\u2606\u2729\ud83c\udf20\u0020\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02\u0020\ud800\udead\u0020\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009\u0020\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, + outCanonicalized: `"Hello, 世界 🌟★☆✩🌠 €ö€힙דּ�😂 𐊭 \"\\/\b\f\n\r\t \"\\/\b\f\n\r\t"`, + tokens: []Token{rawToken("\"Hello, 世界 🌟★☆✩🌠 " + "\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602" + " 𐊭 \\\"\\\\/\\b\\f\\n\\r\\t \\\"\\\\/\\b\\f\\n\\r\\t\"")}, +}, { + name: jsontest.Name("ZeroNumber"), + in: ` 0 `, + outCompacted: `0`, + tokens: []Token{Uint(0)}, +}, { + name: jsontest.Name("SimpleNumber"), + in: ` 123456789 `, + outCompacted: `123456789`, + tokens: []Token{Uint(123456789)}, +}, { + name: jsontest.Name("NegativeNumber"), + in: ` -123456789 `, + outCompacted: `-123456789`, + tokens: []Token{Int(-123456789)}, +}, { + name: jsontest.Name("FractionalNumber"), + in: " 0.123456789 ", + outCompacted: `0.123456789`, + tokens: []Token{Float(0.123456789)}, +}, { + name: jsontest.Name("ExponentNumber"), + in: " 0e12456789 ", + outCompacted: `0e12456789`, + outCanonicalized: `0`, + tokens: []Token{rawToken(`0e12456789`)}, +}, { + name: jsontest.Name("ExponentNumberP"), + in: " 0e+12456789 ", + outCompacted: `0e+12456789`, + outCanonicalized: `0`, + tokens: []Token{rawToken(`0e+12456789`)}, +}, { + name: jsontest.Name("ExponentNumberN"), + in: " 0e-12456789 ", + outCompacted: `0e-12456789`, + outCanonicalized: `0`, + tokens: []Token{rawToken(`0e-12456789`)}, +}, { + name: jsontest.Name("ComplicatedNumber"), + in: ` -123456789.987654321E+0123456789 `, + outCompacted: `-123456789.987654321E+0123456789`, + outCanonicalized: `-1.7976931348623157e+308`, + tokens: []Token{rawToken(`-123456789.987654321E+0123456789`)}, +}, { + name: jsontest.Name("Numbers"), + in: ` [ + 0, -0, 0.0, -0.0, 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, 1e1000, + -5e-324, 1e+100, 1.7976931348623157e+308, + 9007199254740990, 9007199254740991, 9007199254740992, 9007199254740993, 9007199254740994, + -9223372036854775808, 9223372036854775807, 0, 18446744073709551615 + ] `, + outCompacted: "[0,-0,0.0,-0.0,1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001,1e1000,-5e-324,1e+100,1.7976931348623157e+308,9007199254740990,9007199254740991,9007199254740992,9007199254740993,9007199254740994,-9223372036854775808,9223372036854775807,0,18446744073709551615]", + outIndented: `[ + 0, + -0, + 0.0, + -0.0, + 1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001, + 1e1000, + -5e-324, + 1e+100, + 1.7976931348623157e+308, + 9007199254740990, + 9007199254740991, + 9007199254740992, + 9007199254740993, + 9007199254740994, + -9223372036854775808, + 9223372036854775807, + 0, + 18446744073709551615 + ]`, + outCanonicalized: `[0,0,0,0,1,1.7976931348623157e+308,-5e-324,1e+100,1.7976931348623157e+308,9007199254740990,9007199254740991,9007199254740992,9007199254740992,9007199254740994,-9223372036854776000,9223372036854776000,0,18446744073709552000]`, + tokens: []Token{ + BeginArray, + Float(0), Float(math.Copysign(0, -1)), rawToken(`0.0`), rawToken(`-0.0`), rawToken(`1.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001`), rawToken(`1e1000`), + Float(-5e-324), Float(1e100), Float(1.7976931348623157e+308), + Float(9007199254740990), Float(9007199254740991), Float(9007199254740992), rawToken(`9007199254740993`), rawToken(`9007199254740994`), + Int(minInt64), Int(maxInt64), Uint(minUint64), Uint(maxUint64), + EndArray, + }, + pointers: []Pointer{ + "", "/0", "/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/10", "/11", "/12", "/13", "/14", "/15", "/16", "/17", "", + }, +}, { + name: jsontest.Name("ObjectN0"), + in: ` { } `, + outCompacted: `{}`, + tokens: []Token{BeginObject, EndObject}, + pointers: []Pointer{"", ""}, +}, { + name: jsontest.Name("ObjectN1"), + in: ` { "0" : 0 } `, + outCompacted: `{"0":0}`, + outEscaped: `{"\u0030":0}`, + outIndented: `{ + "0": 0 + }`, + tokens: []Token{BeginObject, String("0"), Uint(0), EndObject}, + pointers: []Pointer{"", "/0", "/0", ""}, +}, { + name: jsontest.Name("ObjectN2"), + in: ` { "0" : 0 , "1" : 1 } `, + outCompacted: `{"0":0,"1":1}`, + outEscaped: `{"\u0030":0,"\u0031":1}`, + outIndented: `{ + "0": 0, + "1": 1 + }`, + tokens: []Token{BeginObject, String("0"), Uint(0), String("1"), Uint(1), EndObject}, + pointers: []Pointer{"", "/0", "/0", "/1", "/1", ""}, +}, { + name: jsontest.Name("ObjectNested"), + in: ` { "0" : { "1" : { "2" : { "3" : { "4" : { } } } } } } `, + outCompacted: `{"0":{"1":{"2":{"3":{"4":{}}}}}}`, + outEscaped: `{"\u0030":{"\u0031":{"\u0032":{"\u0033":{"\u0034":{}}}}}}`, + outIndented: `{ + "0": { + "1": { + "2": { + "3": { + "4": {} + } + } + } + } + }`, + tokens: []Token{BeginObject, String("0"), BeginObject, String("1"), BeginObject, String("2"), BeginObject, String("3"), BeginObject, String("4"), BeginObject, EndObject, EndObject, EndObject, EndObject, EndObject, EndObject}, + pointers: []Pointer{ + "", + "/0", "/0", + "/0/1", "/0/1", + "/0/1/2", "/0/1/2", + "/0/1/2/3", "/0/1/2/3", + "/0/1/2/3/4", "/0/1/2/3/4", + "/0/1/2/3/4", + "/0/1/2/3", + "/0/1/2", + "/0/1", + "/0", + "", + }, +}, { + name: jsontest.Name("ObjectSuperNested"), + in: `{"": { + "44444": { + "6666666": "ccccccc", + "77777777": "bb", + "555555": "aaaa" + }, + "0": { + "3333": "bbb", + "11": "", + "222": "aaaaa" + } + }}`, + outCompacted: `{"":{"44444":{"6666666":"ccccccc","77777777":"bb","555555":"aaaa"},"0":{"3333":"bbb","11":"","222":"aaaaa"}}}`, + outEscaped: `{"":{"\u0034\u0034\u0034\u0034\u0034":{"\u0036\u0036\u0036\u0036\u0036\u0036\u0036":"\u0063\u0063\u0063\u0063\u0063\u0063\u0063","\u0037\u0037\u0037\u0037\u0037\u0037\u0037\u0037":"\u0062\u0062","\u0035\u0035\u0035\u0035\u0035\u0035":"\u0061\u0061\u0061\u0061"},"\u0030":{"\u0033\u0033\u0033\u0033":"\u0062\u0062\u0062","\u0031\u0031":"","\u0032\u0032\u0032":"\u0061\u0061\u0061\u0061\u0061"}}}`, + outIndented: `{ + "": { + "44444": { + "6666666": "ccccccc", + "77777777": "bb", + "555555": "aaaa" + }, + "0": { + "3333": "bbb", + "11": "", + "222": "aaaaa" + } + } + }`, + outCanonicalized: `{"":{"0":{"11":"","222":"aaaaa","3333":"bbb"},"44444":{"555555":"aaaa","6666666":"ccccccc","77777777":"bb"}}}`, + tokens: []Token{ + BeginObject, + String(""), + BeginObject, + String("44444"), + BeginObject, + String("6666666"), String("ccccccc"), + String("77777777"), String("bb"), + String("555555"), String("aaaa"), + EndObject, + String("0"), + BeginObject, + String("3333"), String("bbb"), + String("11"), String(""), + String("222"), String("aaaaa"), + EndObject, + EndObject, + EndObject, + }, + pointers: []Pointer{ + "", + "/", "/", + "//44444", "//44444", + "//44444/6666666", "//44444/6666666", + "//44444/77777777", "//44444/77777777", + "//44444/555555", "//44444/555555", + "//44444", + "//0", "//0", + "//0/3333", "//0/3333", + "//0/11", "//0/11", + "//0/222", "//0/222", + "//0", + "/", + "", + }, +}, { + name: jsontest.Name("ArrayN0"), + in: ` [ ] `, + outCompacted: `[]`, + tokens: []Token{BeginArray, EndArray}, + pointers: []Pointer{"", ""}, +}, { + name: jsontest.Name("ArrayN1"), + in: ` [ 0 ] `, + outCompacted: `[0]`, + outIndented: `[ + 0 + ]`, + tokens: []Token{BeginArray, Uint(0), EndArray}, + pointers: []Pointer{"", "/0", ""}, +}, { + name: jsontest.Name("ArrayN2"), + in: ` [ 0 , 1 ] `, + outCompacted: `[0,1]`, + outIndented: `[ + 0, + 1 + ]`, + tokens: []Token{BeginArray, Uint(0), Uint(1), EndArray}, +}, { + name: jsontest.Name("ArrayNested"), + in: ` [ [ [ [ [ ] ] ] ] ] `, + outCompacted: `[[[[[]]]]]`, + outIndented: `[ + [ + [ + [ + [] + ] + ] + ] + ]`, + tokens: []Token{BeginArray, BeginArray, BeginArray, BeginArray, BeginArray, EndArray, EndArray, EndArray, EndArray, EndArray}, + pointers: []Pointer{ + "", + "/0", + "/0/0", + "/0/0/0", + "/0/0/0/0", + "/0/0/0/0", + "/0/0/0", + "/0/0", + "/0", + "", + }, +}, { + name: jsontest.Name("Everything"), + in: ` { + "literals" : [ null , false , true ], + "string" : "Hello, 世界" , + "number" : 3.14159 , + "arrayN0" : [ ] , + "arrayN1" : [ 0 ] , + "arrayN2" : [ 0 , 1 ] , + "objectN0" : { } , + "objectN1" : { "0" : 0 } , + "objectN2" : { "0" : 0 , "1" : 1 } + } `, + outCompacted: `{"literals":[null,false,true],"string":"Hello, 世界","number":3.14159,"arrayN0":[],"arrayN1":[0],"arrayN2":[0,1],"objectN0":{},"objectN1":{"0":0},"objectN2":{"0":0,"1":1}}`, + outEscaped: `{"\u006c\u0069\u0074\u0065\u0072\u0061\u006c\u0073":[null,false,true],"\u0073\u0074\u0072\u0069\u006e\u0067":"\u0048\u0065\u006c\u006c\u006f\u002c\u0020\u4e16\u754c","\u006e\u0075\u006d\u0062\u0065\u0072":3.14159,"\u0061\u0072\u0072\u0061\u0079\u004e\u0030":[],"\u0061\u0072\u0072\u0061\u0079\u004e\u0031":[0],"\u0061\u0072\u0072\u0061\u0079\u004e\u0032":[0,1],"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0030":{},"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0031":{"\u0030":0},"\u006f\u0062\u006a\u0065\u0063\u0074\u004e\u0032":{"\u0030":0,"\u0031":1}}`, + outIndented: `{ + "literals": [ + null, + false, + true + ], + "string": "Hello, 世界", + "number": 3.14159, + "arrayN0": [], + "arrayN1": [ + 0 + ], + "arrayN2": [ + 0, + 1 + ], + "objectN0": {}, + "objectN1": { + "0": 0 + }, + "objectN2": { + "0": 0, + "1": 1 + } + }`, + outCanonicalized: `{"arrayN0":[],"arrayN1":[0],"arrayN2":[0,1],"literals":[null,false,true],"number":3.14159,"objectN0":{},"objectN1":{"0":0},"objectN2":{"0":0,"1":1},"string":"Hello, 世界"}`, + tokens: []Token{ + BeginObject, + String("literals"), BeginArray, Null, False, True, EndArray, + String("string"), String("Hello, 世界"), + String("number"), Float(3.14159), + String("arrayN0"), BeginArray, EndArray, + String("arrayN1"), BeginArray, Uint(0), EndArray, + String("arrayN2"), BeginArray, Uint(0), Uint(1), EndArray, + String("objectN0"), BeginObject, EndObject, + String("objectN1"), BeginObject, String("0"), Uint(0), EndObject, + String("objectN2"), BeginObject, String("0"), Uint(0), String("1"), Uint(1), EndObject, + EndObject, + }, + pointers: []Pointer{ + "", + "/literals", "/literals", + "/literals/0", + "/literals/1", + "/literals/2", + "/literals", + "/string", "/string", + "/number", "/number", + "/arrayN0", "/arrayN0", "/arrayN0", + "/arrayN1", "/arrayN1", + "/arrayN1/0", + "/arrayN1", + "/arrayN2", "/arrayN2", + "/arrayN2/0", + "/arrayN2/1", + "/arrayN2", + "/objectN0", "/objectN0", "/objectN0", + "/objectN1", "/objectN1", + "/objectN1/0", "/objectN1/0", + "/objectN1", + "/objectN2", "/objectN2", + "/objectN2/0", "/objectN2/0", + "/objectN2/1", "/objectN2/1", + "/objectN2", + "", + }, +}} + +// TestCoderInterleaved tests that we can interleave calls that operate on +// tokens and raw values. The only error condition is trying to operate on a +// raw value when the next token is an end of object or array. +func TestCoderInterleaved(t *testing.T) { + for _, td := range coderTestdata { + // In TokenFirst and ValueFirst, alternate between tokens and values. + // In TokenDelims, only use tokens for object and array delimiters. + for _, modeName := range []string{"TokenFirst", "ValueFirst", "TokenDelims"} { + t.Run(path.Join(td.name.Name, modeName), func(t *testing.T) { + testCoderInterleaved(t, td.name.Where, modeName, td) + }) + } + } +} +func testCoderInterleaved(t *testing.T, where jsontest.CasePos, modeName string, td coderTestdataEntry) { + src := strings.NewReader(td.in) + dst := new(bytes.Buffer) + dec := NewDecoder(src) + enc := NewEncoder(dst) + tickTock := modeName == "TokenFirst" + for { + if modeName == "TokenDelims" { + switch dec.PeekKind() { + case '{', '}', '[', ']': + tickTock = true // as token + default: + tickTock = false // as value + } + } + if tickTock { + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + t.Fatalf("%s: Decoder.ReadToken error: %v", where, err) + } + if err := enc.WriteToken(tok); err != nil { + t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) + } + } else { + val, err := dec.ReadValue() + if err != nil { + // It is a syntactic error to call ReadValue + // at the end of an object or array. + // Retry as a ReadToken call. + expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' + if expectError { + if !errors.As(err, new(*SyntacticError)) { + t.Fatalf("%s: Decoder.ReadToken error is %T, want %T", where, err, new(SyntacticError)) + } + tickTock = !tickTock + continue + } + + if err == io.EOF { + break + } + t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) + } + if err := enc.WriteValue(val); err != nil { + t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) + } + } + tickTock = !tickTock + } + + got := dst.String() + want := td.outCompacted + "\n" + if got != want { + t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, got, want) + } +} + +func TestCoderStackPointer(t *testing.T) { + tests := []struct { + token Token + want Pointer + }{ + {Null, ""}, + + {BeginArray, ""}, + {EndArray, ""}, + + {BeginArray, ""}, + {Bool(true), "/0"}, + {EndArray, ""}, + + {BeginArray, ""}, + {String("hello"), "/0"}, + {String("goodbye"), "/1"}, + {EndArray, ""}, + + {BeginObject, ""}, + {EndObject, ""}, + + {BeginObject, ""}, + {String("hello"), "/hello"}, + {String("goodbye"), "/hello"}, + {EndObject, ""}, + + {BeginObject, ""}, + {String(""), "/"}, + {Null, "/"}, + {String("0"), "/0"}, + {Null, "/0"}, + {String("~"), "/~0"}, + {Null, "/~0"}, + {String("/"), "/~1"}, + {Null, "/~1"}, + {String("a//b~/c/~d~~e"), "/a~1~1b~0~1c~1~0d~0~0e"}, + {Null, "/a~1~1b~0~1c~1~0d~0~0e"}, + {String(" \r\n\t"), "/ \r\n\t"}, + {Null, "/ \r\n\t"}, + {EndObject, ""}, + + {BeginArray, ""}, + {BeginObject, "/0"}, + {String(""), "/0/"}, + {BeginArray, "/0/"}, + {BeginObject, "/0//0"}, + {String("#"), "/0//0/#"}, + {Null, "/0//0/#"}, + {EndObject, "/0//0"}, + {EndArray, "/0/"}, + {EndObject, "/0"}, + {EndArray, ""}, + } + + for _, allowDupes := range []bool{false, true} { + var name string + switch allowDupes { + case false: + name = "RejectDuplicateNames" + case true: + name = "AllowDuplicateNames" + } + + t.Run(name, func(t *testing.T) { + bb := new(bytes.Buffer) + + enc := NewEncoder(bb, AllowDuplicateNames(allowDupes)) + for i, tt := range tests { + if err := enc.WriteToken(tt.token); err != nil { + t.Fatalf("%d: Encoder.WriteToken error: %v", i, err) + } + if got := enc.StackPointer(); got != tests[i].want { + t.Fatalf("%d: Encoder.StackPointer = %v, want %v", i, got, tests[i].want) + } + } + + dec := NewDecoder(bb, AllowDuplicateNames(allowDupes)) + for i := range tests { + if _, err := dec.ReadToken(); err != nil { + t.Fatalf("%d: Decoder.ReadToken error: %v", i, err) + } + if got := dec.StackPointer(); got != tests[i].want { + t.Fatalf("%d: Decoder.StackPointer = %v, want %v", i, got, tests[i].want) + } + } + }) + } +} + +func TestCoderMaxDepth(t *testing.T) { + trimArray := func(b []byte) []byte { return b[len(`[`) : len(b)-len(`]`)] } + maxArrays := []byte(strings.Repeat(`[`, maxNestingDepth+1) + strings.Repeat(`]`, maxNestingDepth+1)) + trimObject := func(b []byte) []byte { return b[len(`{"":`) : len(b)-len(`}`)] } + maxObjects := []byte(strings.Repeat(`{"":`, maxNestingDepth+1) + `""` + strings.Repeat(`}`, maxNestingDepth+1)) + + t.Run("Decoder", func(t *testing.T) { + var dec Decoder + checkReadToken := func(t *testing.T, wantKind Kind, wantErr error) { + t.Helper() + if tok, err := dec.ReadToken(); tok.Kind() != wantKind || !equalError(err, wantErr) { + t.Fatalf("Decoder.ReadToken = (%q, %v), want (%q, %v)", byte(tok.Kind()), err, byte(wantKind), wantErr) + } + } + checkReadValue := func(t *testing.T, wantLen int, wantErr error) { + t.Helper() + if val, err := dec.ReadValue(); len(val) != wantLen || !equalError(err, wantErr) { + t.Fatalf("Decoder.ReadValue = (%d, %v), want (%d, %v)", len(val), err, wantLen, wantErr) + } + } + + t.Run("ArraysValid/SingleValue", func(t *testing.T) { + dec.s.reset(trimArray(maxArrays), nil) + checkReadValue(t, maxNestingDepth*len(`[]`), nil) + }) + t.Run("ArraysValid/TokenThenValue", func(t *testing.T) { + dec.s.reset(trimArray(maxArrays), nil) + checkReadToken(t, '[', nil) + checkReadValue(t, (maxNestingDepth-1)*len(`[]`), nil) + checkReadToken(t, ']', nil) + }) + t.Run("ArraysValid/AllTokens", func(t *testing.T) { + dec.s.reset(trimArray(maxArrays), nil) + for range maxNestingDepth { + checkReadToken(t, '[', nil) + } + for range maxNestingDepth { + checkReadToken(t, ']', nil) + } + }) + + wantErr := &SyntacticError{ + ByteOffset: maxNestingDepth, + JSONPointer: Pointer(strings.Repeat("/0", maxNestingDepth)), + Err: errMaxDepth, + } + t.Run("ArraysInvalid/SingleValue", func(t *testing.T) { + dec.s.reset(maxArrays, nil) + checkReadValue(t, 0, wantErr) + }) + t.Run("ArraysInvalid/TokenThenValue", func(t *testing.T) { + dec.s.reset(maxArrays, nil) + checkReadToken(t, '[', nil) + checkReadValue(t, 0, wantErr) + }) + t.Run("ArraysInvalid/AllTokens", func(t *testing.T) { + dec.s.reset(maxArrays, nil) + for range maxNestingDepth { + checkReadToken(t, '[', nil) + } + checkReadValue(t, 0, wantErr) + }) + + t.Run("ObjectsValid/SingleValue", func(t *testing.T) { + dec.s.reset(trimObject(maxObjects), nil) + checkReadValue(t, maxNestingDepth*len(`{"":}`)+len(`""`), nil) + }) + t.Run("ObjectsValid/TokenThenValue", func(t *testing.T) { + dec.s.reset(trimObject(maxObjects), nil) + checkReadToken(t, '{', nil) + checkReadToken(t, '"', nil) + checkReadValue(t, (maxNestingDepth-1)*len(`{"":}`)+len(`""`), nil) + checkReadToken(t, '}', nil) + }) + t.Run("ObjectsValid/AllTokens", func(t *testing.T) { + dec.s.reset(trimObject(maxObjects), nil) + for range maxNestingDepth { + checkReadToken(t, '{', nil) + checkReadToken(t, '"', nil) + } + checkReadToken(t, '"', nil) + for range maxNestingDepth { + checkReadToken(t, '}', nil) + } + }) + + wantErr = &SyntacticError{ + ByteOffset: maxNestingDepth * int64(len(`{"":`)), + JSONPointer: Pointer(strings.Repeat("/", maxNestingDepth)), + Err: errMaxDepth, + } + t.Run("ObjectsInvalid/SingleValue", func(t *testing.T) { + dec.s.reset(maxObjects, nil) + checkReadValue(t, 0, wantErr) + }) + t.Run("ObjectsInvalid/TokenThenValue", func(t *testing.T) { + dec.s.reset(maxObjects, nil) + checkReadToken(t, '{', nil) + checkReadToken(t, '"', nil) + checkReadValue(t, 0, wantErr) + }) + t.Run("ObjectsInvalid/AllTokens", func(t *testing.T) { + dec.s.reset(maxObjects, nil) + for range maxNestingDepth { + checkReadToken(t, '{', nil) + checkReadToken(t, '"', nil) + } + checkReadToken(t, 0, wantErr) + }) + }) + + t.Run("Encoder", func(t *testing.T) { + var enc Encoder + checkWriteToken := func(t *testing.T, tok Token, wantErr error) { + t.Helper() + if err := enc.WriteToken(tok); !equalError(err, wantErr) { + t.Fatalf("Encoder.WriteToken = %v, want %v", err, wantErr) + } + } + checkWriteValue := func(t *testing.T, val Value, wantErr error) { + t.Helper() + if err := enc.WriteValue(val); !equalError(err, wantErr) { + t.Fatalf("Encoder.WriteValue = %v, want %v", err, wantErr) + } + } + + wantErr := &SyntacticError{ + ByteOffset: maxNestingDepth, + JSONPointer: Pointer(strings.Repeat("/0", maxNestingDepth)), + Err: errMaxDepth, + } + t.Run("Arrays/SingleValue", func(t *testing.T) { + enc.s.reset(enc.s.Buf[:0], nil) + checkWriteValue(t, maxArrays, wantErr) + checkWriteValue(t, trimArray(maxArrays), nil) + }) + t.Run("Arrays/TokenThenValue", func(t *testing.T) { + enc.s.reset(enc.s.Buf[:0], nil) + checkWriteToken(t, BeginArray, nil) + checkWriteValue(t, trimArray(maxArrays), wantErr) + checkWriteValue(t, trimArray(trimArray(maxArrays)), nil) + checkWriteToken(t, EndArray, nil) + }) + t.Run("Arrays/AllTokens", func(t *testing.T) { + enc.s.reset(enc.s.Buf[:0], nil) + for range maxNestingDepth { + checkWriteToken(t, BeginArray, nil) + } + checkWriteToken(t, BeginArray, wantErr) + for range maxNestingDepth { + checkWriteToken(t, EndArray, nil) + } + }) + + wantErr = &SyntacticError{ + ByteOffset: maxNestingDepth * int64(len(`{"":`)), + JSONPointer: Pointer(strings.Repeat("/", maxNestingDepth)), + Err: errMaxDepth, + } + t.Run("Objects/SingleValue", func(t *testing.T) { + enc.s.reset(enc.s.Buf[:0], nil) + checkWriteValue(t, maxObjects, wantErr) + checkWriteValue(t, trimObject(maxObjects), nil) + }) + t.Run("Objects/TokenThenValue", func(t *testing.T) { + enc.s.reset(enc.s.Buf[:0], nil) + checkWriteToken(t, BeginObject, nil) + checkWriteToken(t, String(""), nil) + checkWriteValue(t, trimObject(maxObjects), wantErr) + checkWriteValue(t, trimObject(trimObject(maxObjects)), nil) + checkWriteToken(t, EndObject, nil) + }) + t.Run("Objects/AllTokens", func(t *testing.T) { + enc.s.reset(enc.s.Buf[:0], nil) + for range maxNestingDepth - 1 { + checkWriteToken(t, BeginObject, nil) + checkWriteToken(t, String(""), nil) + } + checkWriteToken(t, BeginObject, nil) + checkWriteToken(t, String(""), nil) + checkWriteToken(t, BeginObject, wantErr) + checkWriteToken(t, String(""), nil) + for range maxNestingDepth { + checkWriteToken(t, EndObject, nil) + } + }) + }) +} + +// FaultyBuffer implements io.Reader and io.Writer. +// It may process fewer bytes than the provided buffer +// and may randomly return an error. +type FaultyBuffer struct { + B []byte + + // MaxBytes is the maximum number of bytes read/written. + // A random number of bytes within [0, MaxBytes] are processed. + // A non-positive value is treated as infinity. + MaxBytes int + + // MayError specifies whether to randomly provide this error. + // Even if an error is returned, no bytes are dropped. + MayError error + + // Rand to use for pseudo-random behavior. + // If nil, it will be initialized with rand.NewSource(0). + Rand rand.Source +} + +func (p *FaultyBuffer) Read(b []byte) (int, error) { + b = b[:copy(b[:p.mayTruncate(len(b))], p.B)] + p.B = p.B[len(b):] + if len(p.B) == 0 && (len(b) == 0 || p.randN(2) == 0) { + return len(b), io.EOF + } + return len(b), p.mayError() +} + +func (p *FaultyBuffer) Write(b []byte) (int, error) { + b2 := b[:p.mayTruncate(len(b))] + p.B = append(p.B, b2...) + if len(b2) < len(b) { + return len(b2), io.ErrShortWrite + } + return len(b2), p.mayError() +} + +// mayTruncate may return a value between [0, n]. +func (p *FaultyBuffer) mayTruncate(n int) int { + if p.MaxBytes > 0 { + if n > p.MaxBytes { + n = p.MaxBytes + } + return p.randN(n + 1) + } + return n +} + +// mayError may return a non-nil error. +func (p *FaultyBuffer) mayError() error { + if p.MayError != nil && p.randN(2) == 0 { + return p.MayError + } + return nil +} + +func (p *FaultyBuffer) randN(n int) int { + if p.Rand == nil { + p.Rand = rand.NewSource(0) + } + return int(p.Rand.Int63() % int64(n)) +} diff --git a/src/encoding/json/jsontext/decode.go b/src/encoding/json/jsontext/decode.go new file mode 100644 index 0000000000..784ae4709a --- /dev/null +++ b/src/encoding/json/jsontext/decode.go @@ -0,0 +1,1168 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "errors" + "io" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" +) + +// NOTE: The logic for decoding is complicated by the fact that reading from +// an io.Reader into a temporary buffer means that the buffer may contain a +// truncated portion of some valid input, requiring the need to fetch more data. +// +// This file is structured in the following way: +// +// - consumeXXX functions parse an exact JSON token from a []byte. +// If the buffer appears truncated, then it returns io.ErrUnexpectedEOF. +// The consumeSimpleXXX functions are so named because they only handle +// a subset of the grammar for the JSON token being parsed. +// They do not handle the full grammar to keep these functions inlinable. +// +// - Decoder.consumeXXX methods parse the next JSON token from Decoder.buf, +// automatically fetching more input if necessary. These methods take +// a position relative to the start of Decoder.buf as an argument and +// 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, +// 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. +// +// - For performance, much of the implementation uses the pattern of calling +// the inlinable consumeXXX functions first, and if more work is necessary, +// then it calls the slower Decoder.consumeXXX methods. +// TODO: Revisit this pattern if the Go compiler provides finer control +// over exactly which calls are inlined or not. + +// Decoder is a streaming decoder for raw JSON tokens and values. +// It is used to read a stream of top-level JSON values, +// each separated by optional whitespace characters. +// +// [Decoder.ReadToken] and [Decoder.ReadValue] calls may be interleaved. +// For example, the following JSON value: +// +// {"name":"value","array":[null,false,true,3.14159],"object":{"k":"v"}} +// +// can be parsed with the following calls (ignoring errors for brevity): +// +// d.ReadToken() // { +// d.ReadToken() // "name" +// d.ReadToken() // "value" +// d.ReadValue() // "array" +// d.ReadToken() // [ +// d.ReadToken() // null +// d.ReadToken() // false +// d.ReadValue() // true +// d.ReadToken() // 3.14159 +// d.ReadToken() // ] +// d.ReadValue() // "object" +// d.ReadValue() // {"k":"v"} +// d.ReadToken() // } +// +// The above is one of many possible sequence of calls and +// may not represent the most sensible method to call for any given token/value. +// For example, it is probably more common to call [Decoder.ReadToken] to obtain a +// string token for object names. +type Decoder struct { + s decoderState +} + +// decoderState is the low-level state of Decoder. +// It has exported fields and method for use by the "json" package. +type decoderState struct { + state + decodeBuffer + jsonopts.Struct + + StringCache *[256]string // only used when unmarshaling; identical to json.stringCache +} + +// decodeBuffer is a buffer split into 4 segments: +// +// - buf[0:prevEnd] // already read portion of the buffer +// - buf[prevStart:prevEnd] // previously read value +// - buf[prevEnd:len(buf)] // unread portion of the buffer +// - buf[len(buf):cap(buf)] // unused portion of the buffer +// +// Invariants: +// +// 0 ≤ prevStart ≤ prevEnd ≤ len(buf) ≤ cap(buf) +type decodeBuffer struct { + peekPos int // non-zero if valid offset into buf for start of next token + peekErr error // implies peekPos is -1 + + buf []byte // may alias rd if it is a bytes.Buffer + prevStart int + prevEnd int + + // baseOffset is added to prevStart and prevEnd to obtain + // the absolute offset relative to the start of io.Reader stream. + baseOffset int64 + + rd io.Reader +} + +// NewDecoder constructs a new streaming decoder reading from r. +// +// If r is a [bytes.Buffer], then the decoder parses directly from the buffer +// without first copying the contents to an intermediate buffer. +// Additional writes to the buffer must not occur while the decoder is in use. +func NewDecoder(r io.Reader, opts ...Options) *Decoder { + d := new(Decoder) + d.Reset(r, opts...) + return d +} + +// Reset resets a decoder such that it is reading afresh from r and +// configured with the provided options. Reset must not be called on an +// a Decoder passed to the [encoding/json/v2.UnmarshalerFrom.UnmarshalJSONFrom] method +// or the [encoding/json/v2.UnmarshalFromFunc] function. +func (d *Decoder) Reset(r io.Reader, opts ...Options) { + switch { + case d == nil: + panic("jsontext: invalid nil Decoder") + case r == nil: + panic("jsontext: invalid nil io.Reader") + case d.s.Flags.Get(jsonflags.WithinArshalCall): + panic("jsontext: cannot reset Decoder passed to json.UnmarshalerFrom") + } + d.s.reset(nil, r, opts...) +} + +func (d *decoderState) reset(b []byte, r io.Reader, opts ...Options) { + d.state.reset() + d.decodeBuffer = decodeBuffer{buf: b, rd: r} + opts2 := jsonopts.Struct{} // avoid mutating d.Struct in case it is part of opts + opts2.Join(opts...) + d.Struct = opts2 +} + +// Options returns the options used to construct the encoder and +// may additionally contain semantic options passed to a +// [encoding/json/v2.UnmarshalDecode] call. +// +// If operating within +// a [encoding/json/v2.UnmarshalerFrom.UnmarshalJSONFrom] method call or +// a [encoding/json/v2.UnmarshalFromFunc] function call, +// then the returned options are only valid within the call. +func (d *Decoder) Options() Options { + return &d.s.Struct +} + +var errBufferWriteAfterNext = errors.New("invalid bytes.Buffer.Write call after calling bytes.Buffer.Next") + +// fetch reads at least 1 byte from the underlying io.Reader. +// It returns io.ErrUnexpectedEOF if zero bytes were read and io.EOF was seen. +func (d *decoderState) fetch() error { + if d.rd == nil { + return io.ErrUnexpectedEOF + } + + // Inform objectNameStack that we are about to fetch new buffer content. + d.Names.copyQuotedBuffer(d.buf) + + // Specialize bytes.Buffer for better performance. + if bb, ok := d.rd.(*bytes.Buffer); ok { + switch { + case bb.Len() == 0: + return io.ErrUnexpectedEOF + case len(d.buf) == 0: + d.buf = bb.Next(bb.Len()) // "read" all data in the buffer + return nil + default: + // This only occurs if a partially filled bytes.Buffer was provided + // and more data is written to it while Decoder is reading from it. + // This practice will lead to data corruption since future writes + // 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, + // the purpose-built io.Pipe should be used instead. + return &ioError{action: "read", err: errBufferWriteAfterNext} + } + } + + // Allocate initial buffer if empty. + if cap(d.buf) == 0 { + d.buf = make([]byte, 0, 64) + } + + // Check whether to grow the buffer. + const maxBufferSize = 4 << 10 + const growthSizeFactor = 2 // higher value is faster + const growthRateFactor = 2 // higher value is slower + // By default, grow if below the maximum buffer size. + grow := cap(d.buf) <= maxBufferSize/growthSizeFactor + // Growing can be expensive, so only grow + // if a sufficient number of bytes have been processed. + grow = grow && int64(cap(d.buf)) < d.previousOffsetEnd()/growthRateFactor + // If prevStart==0, then fetch was called in order to fetch more data + // to finish consuming a large JSON value contiguously. + // Grow if less than 25% of the remaining capacity is available. + // Note that this may cause the input buffer to exceed maxBufferSize. + grow = grow || (d.prevStart == 0 && len(d.buf) >= 3*cap(d.buf)/4) + + if grow { + // Allocate a new buffer and copy the contents of the old buffer over. + // TODO: Provide a hard limit on the maximum internal buffer size? + buf := make([]byte, 0, cap(d.buf)*growthSizeFactor) + d.buf = append(buf, d.buf[d.prevStart:]...) + } else { + // Move unread portion of the data to the front. + n := copy(d.buf[:cap(d.buf)], d.buf[d.prevStart:]) + d.buf = d.buf[:n] + } + d.baseOffset += int64(d.prevStart) + d.prevEnd -= d.prevStart + d.prevStart = 0 + + // Read more data into the internal buffer. + for { + n, err := d.rd.Read(d.buf[len(d.buf):cap(d.buf)]) + switch { + case n > 0: + d.buf = d.buf[:len(d.buf)+n] + return nil // ignore errors if any bytes are read + case err == io.EOF: + return io.ErrUnexpectedEOF + case err != nil: + return &ioError{action: "read", err: err} + default: + continue // Read returned (0, nil) + } + } +} + +const invalidateBufferByte = '#' // invalid starting character for JSON grammar + +// invalidatePreviousRead invalidates buffers returned by Peek and Read calls +// so that the first byte is an invalid character. +// This Hyrum-proofs the API against faulty application code that assumes +// values returned by ReadValue remain valid past subsequent Read calls. +func (d *decodeBuffer) invalidatePreviousRead() { + // Avoid mutating the buffer if d.rd is nil which implies that d.buf + // is provided by the user code and may not expect mutations. + isBytesBuffer := func(r io.Reader) bool { + _, ok := r.(*bytes.Buffer) + return ok + } + if d.rd != nil && !isBytesBuffer(d.rd) && d.prevStart < d.prevEnd && uint(d.prevStart) < uint(len(d.buf)) { + d.buf[d.prevStart] = invalidateBufferByte + d.prevStart = d.prevEnd + } +} + +// needMore reports whether there are no more unread bytes. +func (d *decodeBuffer) needMore(pos int) bool { + // NOTE: The arguments and logic are kept simple to keep this inlinable. + return pos == len(d.buf) +} + +func (d *decodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) } +func (d *decodeBuffer) previousOffsetStart() int64 { return d.baseOffset + int64(d.prevStart) } +func (d *decodeBuffer) previousOffsetEnd() int64 { return d.baseOffset + int64(d.prevEnd) } +func (d *decodeBuffer) previousBuffer() []byte { return d.buf[d.prevStart:d.prevEnd] } +func (d *decodeBuffer) unreadBuffer() []byte { return d.buf[d.prevEnd:len(d.buf)] } + +// PreviousTokenOrValue returns the previously read token or value +// unless it has been invalidated by a call to PeekKind. +// If a token is just a delimiter, then this returns a 1-byte buffer. +// This method is used for error reporting at the semantic layer. +func (d *decodeBuffer) PreviousTokenOrValue() []byte { + b := d.previousBuffer() + // If peek was called, then the previous token or buffer is invalidated. + if d.peekPos > 0 || len(b) > 0 && b[0] == invalidateBufferByte { + return nil + } + // ReadToken does not preserve the buffer for null, bools, or delimiters. + // Manually re-construct that buffer. + if len(b) == 0 { + b = d.buf[:d.prevEnd] // entirety of the previous buffer + for _, tok := range []string{"null", "false", "true", "{", "}", "[", "]"} { + if len(b) >= len(tok) && string(b[len(b)-len(tok):]) == tok { + return b[len(b)-len(tok):] + } + } + } + return b +} + +// PeekKind retrieves the next token kind, but does not advance the read offset. +// +// It returns 0 if an error occurs. Any such error is cached until +// the next read call and it is the caller's responsibility to eventually +// follow up a PeekKind call with a read call. +func (d *Decoder) PeekKind() Kind { + return d.s.PeekKind() +} +func (d *decoderState) PeekKind() Kind { + // Check whether we have a cached peek result. + if d.peekPos > 0 { + return Kind(d.buf[d.peekPos]).normalize() + } + + var err error + d.invalidatePreviousRead() + pos := d.prevEnd + + // Consume leading whitespace. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + if err == io.ErrUnexpectedEOF && d.Tokens.Depth() == 1 { + err = io.EOF // EOF possibly if no Tokens present after top-level value + } + d.peekPos, d.peekErr = -1, wrapSyntacticError(d, err, pos, 0) + return invalidKind + } + } + + // Consume colon or comma. + var delim byte + if c := d.buf[pos]; c == ':' || c == ',' { + delim = c + pos += 1 + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + err = wrapSyntacticError(d, err, pos, 0) + d.peekPos, d.peekErr = -1, d.checkDelimBeforeIOError(delim, err) + return invalidKind + } + } + } + next := Kind(d.buf[pos]).normalize() + if d.Tokens.needDelim(next) != delim { + d.peekPos, d.peekErr = -1, d.checkDelim(delim, next) + return invalidKind + } + + // This may set peekPos to zero, which is indistinguishable from + // the uninitialized state. While a small hit to performance, it is correct + // since ReadValue and ReadToken will disregard the cached result and + // recompute the next kind. + d.peekPos, d.peekErr = pos, nil + return next +} + +// checkDelimBeforeIOError checks whether the delim is even valid +// before returning an IO error, which occurs after the delim. +func (d *decoderState) checkDelimBeforeIOError(delim byte, err error) error { + // Since an IO error occurred, we do not know what the next kind is. + // However, knowing the next kind is necessary to validate + // whether the current delim is at least potentially valid. + // Since a JSON string is always valid as the next token, + // conservatively assume that is the next kind for validation. + const next = Kind('"') + if d.Tokens.needDelim(next) != delim { + err = d.checkDelim(delim, next) + } + return err +} + +// CountNextDelimWhitespace counts the number of upcoming bytes of +// delimiter or whitespace characters. +// This method is used for error reporting at the semantic layer. +func (d *decoderState) CountNextDelimWhitespace() int { + d.PeekKind() // populate unreadBuffer + return len(d.unreadBuffer()) - len(bytes.TrimLeft(d.unreadBuffer(), ",: \n\r\t")) +} + +// checkDelim checks whether delim is valid for the given next kind. +func (d *decoderState) checkDelim(delim byte, next Kind) error { + where := "at start of value" + switch d.Tokens.needDelim(next) { + case delim: + return nil + case ':': + where = "after object name (expecting ':')" + case ',': + if d.Tokens.Last.isObject() { + where = "after object value (expecting ',' or '}')" + } else { + where = "after array element (expecting ',' or ']')" + } + } + pos := d.prevEnd // restore position to right after leading whitespace + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + err := jsonwire.NewInvalidCharacterError(d.buf[pos:], where) + return wrapSyntacticError(d, err, pos, 0) +} + +// SkipValue is semantically equivalent to calling [Decoder.ReadValue] and discarding +// 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 '{', '[': + // For JSON objects and arrays, keep skipping all tokens + // until the depth matches the starting depth. + depth := d.Tokens.Depth() + for { + if _, err := d.ReadToken(); err != nil { + return err + } + if depth >= d.Tokens.Depth() { + return nil + } + } + default: + // Trying to skip a value when the next token is a '}' or ']' + // will result in an error being returned here. + var flags jsonwire.ValueFlags + if _, err := d.ReadValue(&flags); err != nil { + return err + } + return nil + } +} + +// SkipValueRemainder skips the remainder of a value +// after reading a '{' or '[' token. +func (d *decoderState) SkipValueRemainder() error { + if d.Tokens.Depth()-1 > 0 && d.Tokens.Last.Length() == 0 { + for n := d.Tokens.Depth(); d.Tokens.Depth() >= n; { + if _, err := d.ReadToken(); err != nil { + return err + } + } + } + return nil +} + +// SkipUntil skips all tokens until the state machine +// is at or past the specified depth and length. +func (d *decoderState) SkipUntil(depth int, length int64) error { + for d.Tokens.Depth() > depth || (d.Tokens.Depth() == depth && d.Tokens.Last.Length() < length) { + if _, err := d.ReadToken(); err != nil { + return err + } + } + return nil +} + +// ReadToken reads the next [Token], advancing the read offset. +// The returned token is only valid until the next Peek, Read, or Skip call. +// It returns [io.EOF] if there are no more tokens. +func (d *Decoder) ReadToken() (Token, error) { + return d.s.ReadToken() +} +func (d *decoderState) ReadToken() (Token, error) { + // Determine the next kind. + var err error + var next Kind + pos := d.peekPos + if pos != 0 { + // Use cached peek result. + if d.peekErr != nil { + err := d.peekErr + d.peekPos, d.peekErr = 0, nil // possibly a transient I/O error + return Token{}, err + } + next = Kind(d.buf[pos]).normalize() + d.peekPos = 0 // reset cache + } else { + d.invalidatePreviousRead() + pos = d.prevEnd + + // Consume leading whitespace. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + if err == io.ErrUnexpectedEOF && d.Tokens.Depth() == 1 { + err = io.EOF // EOF possibly if no Tokens present after top-level value + } + return Token{}, wrapSyntacticError(d, err, pos, 0) + } + } + + // Consume colon or comma. + var delim byte + if c := d.buf[pos]; c == ':' || c == ',' { + delim = c + pos += 1 + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + err = wrapSyntacticError(d, err, pos, 0) + return Token{}, d.checkDelimBeforeIOError(delim, err) + } + } + } + next = Kind(d.buf[pos]).normalize() + if d.Tokens.needDelim(next) != delim { + return Token{}, d.checkDelim(delim, next) + } + } + + // Handle the next token. + var n int + switch next { + case 'n': + if jsonwire.ConsumeNull(d.buf[pos:]) == 0 { + pos, err = d.consumeLiteral(pos, "null") + if err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + } else { + pos += len("null") + } + if err = d.Tokens.appendLiteral(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos-len("null"), +1) // report position at start of literal + } + d.prevStart, d.prevEnd = pos, pos + return Null, nil + + case 'f': + if jsonwire.ConsumeFalse(d.buf[pos:]) == 0 { + pos, err = d.consumeLiteral(pos, "false") + if err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + } else { + pos += len("false") + } + if err = d.Tokens.appendLiteral(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos-len("false"), +1) // report position at start of literal + } + d.prevStart, d.prevEnd = pos, pos + return False, nil + + case 't': + if jsonwire.ConsumeTrue(d.buf[pos:]) == 0 { + pos, err = d.consumeLiteral(pos, "true") + if err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + } else { + pos += len("true") + } + if err = d.Tokens.appendLiteral(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos-len("true"), +1) // report position at start of literal + } + d.prevStart, d.prevEnd = pos, pos + return True, nil + + case '"': + var flags jsonwire.ValueFlags // TODO: Preserve this in Token? + if n = jsonwire.ConsumeSimpleString(d.buf[pos:]); n == 0 { + oldAbsPos := d.baseOffset + int64(pos) + pos, err = d.consumeString(&flags, pos) + newAbsPos := d.baseOffset + int64(pos) + n = int(newAbsPos - oldAbsPos) + if err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + } else { + pos += n + } + if d.Tokens.Last.NeedObjectName() { + if !d.Flags.Get(jsonflags.AllowDuplicateNames) { + if !d.Tokens.Last.isValidNamespace() { + return Token{}, wrapSyntacticError(d, errInvalidNamespace, pos-n, +1) + } + if d.Tokens.Last.isActiveNamespace() && !d.Namespaces.Last().insertQuoted(d.buf[pos-n:pos], flags.IsVerbatim()) { + err = wrapWithObjectName(ErrDuplicateName, d.buf[pos-n:pos]) + return Token{}, wrapSyntacticError(d, err, pos-n, +1) // report position at start of string + } + } + d.Names.ReplaceLastQuotedOffset(pos - n) // only replace if insertQuoted succeeds + } + if err = d.Tokens.appendString(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos-n, +1) // report position at start of string + } + d.prevStart, d.prevEnd = pos-n, pos + return Token{raw: &d.decodeBuffer, num: uint64(d.previousOffsetStart())}, nil + + case '0': + // NOTE: Since JSON numbers are not self-terminating, + // we need to make sure that the next byte is not part of a number. + if n = jsonwire.ConsumeSimpleNumber(d.buf[pos:]); n == 0 || d.needMore(pos+n) { + oldAbsPos := d.baseOffset + int64(pos) + pos, err = d.consumeNumber(pos) + newAbsPos := d.baseOffset + int64(pos) + n = int(newAbsPos - oldAbsPos) + if err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + } else { + pos += n + } + if err = d.Tokens.appendNumber(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos-n, +1) // report position at start of number + } + d.prevStart, d.prevEnd = pos-n, pos + return Token{raw: &d.decodeBuffer, num: uint64(d.previousOffsetStart())}, nil + + case '{': + if err = d.Tokens.pushObject(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + d.Names.push() + if !d.Flags.Get(jsonflags.AllowDuplicateNames) { + d.Namespaces.push() + } + pos += 1 + d.prevStart, d.prevEnd = pos, pos + return BeginObject, nil + + case '}': + if err = d.Tokens.popObject(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + d.Names.pop() + if !d.Flags.Get(jsonflags.AllowDuplicateNames) { + d.Namespaces.pop() + } + pos += 1 + d.prevStart, d.prevEnd = pos, pos + return EndObject, nil + + case '[': + if err = d.Tokens.pushArray(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + pos += 1 + d.prevStart, d.prevEnd = pos, pos + return BeginArray, nil + + case ']': + if err = d.Tokens.popArray(); err != nil { + return Token{}, wrapSyntacticError(d, err, pos, +1) + } + pos += 1 + d.prevStart, d.prevEnd = pos, pos + return EndArray, nil + + default: + err = jsonwire.NewInvalidCharacterError(d.buf[pos:], "at start of value") + return Token{}, wrapSyntacticError(d, err, pos, +1) + } +} + +// ReadValue returns the next raw JSON value, advancing the read offset. +// The value is stripped of any leading or trailing whitespace and +// contains the exact bytes of the input, which may contain invalid UTF-8 +// if [AllowInvalidUTF8] is specified. +// +// The returned value is only valid until the next Peek, Read, or Skip call and +// may not be mutated while the Decoder remains in use. +// If the decoder is currently at the end token for an object or array, +// then it reports a [SyntacticError] and the internal state remains unchanged. +// It returns [io.EOF] if there are no more values. +func (d *Decoder) ReadValue() (Value, error) { + var flags jsonwire.ValueFlags + return d.s.ReadValue(&flags) +} +func (d *decoderState) ReadValue(flags *jsonwire.ValueFlags) (Value, error) { + // Determine the next kind. + var err error + var next Kind + pos := d.peekPos + if pos != 0 { + // Use cached peek result. + if d.peekErr != nil { + err := d.peekErr + d.peekPos, d.peekErr = 0, nil // possibly a transient I/O error + return nil, err + } + next = Kind(d.buf[pos]).normalize() + d.peekPos = 0 // reset cache + } else { + d.invalidatePreviousRead() + pos = d.prevEnd + + // Consume leading whitespace. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + if err == io.ErrUnexpectedEOF && d.Tokens.Depth() == 1 { + err = io.EOF // EOF possibly if no Tokens present after top-level value + } + return nil, wrapSyntacticError(d, err, pos, 0) + } + } + + // Consume colon or comma. + var delim byte + if c := d.buf[pos]; c == ':' || c == ',' { + delim = c + pos += 1 + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + err = wrapSyntacticError(d, err, pos, 0) + return nil, d.checkDelimBeforeIOError(delim, err) + } + } + } + next = Kind(d.buf[pos]).normalize() + if d.Tokens.needDelim(next) != delim { + return nil, d.checkDelim(delim, next) + } + } + + // Handle the next value. + oldAbsPos := d.baseOffset + int64(pos) + pos, err = d.consumeValue(flags, pos, d.Tokens.Depth()) + newAbsPos := d.baseOffset + int64(pos) + n := int(newAbsPos - oldAbsPos) + if err != nil { + return nil, wrapSyntacticError(d, err, pos, +1) + } + switch next { + case 'n', 't', 'f': + err = d.Tokens.appendLiteral() + case '"': + if d.Tokens.Last.NeedObjectName() { + if !d.Flags.Get(jsonflags.AllowDuplicateNames) { + if !d.Tokens.Last.isValidNamespace() { + err = errInvalidNamespace + break + } + if d.Tokens.Last.isActiveNamespace() && !d.Namespaces.Last().insertQuoted(d.buf[pos-n:pos], flags.IsVerbatim()) { + err = wrapWithObjectName(ErrDuplicateName, d.buf[pos-n:pos]) + break + } + } + d.Names.ReplaceLastQuotedOffset(pos - n) // only replace if insertQuoted succeeds + } + err = d.Tokens.appendString() + case '0': + err = d.Tokens.appendNumber() + case '{': + if err = d.Tokens.pushObject(); err != nil { + break + } + if err = d.Tokens.popObject(); err != nil { + panic("BUG: popObject should never fail immediately after pushObject: " + err.Error()) + } + case '[': + if err = d.Tokens.pushArray(); err != nil { + break + } + if err = d.Tokens.popArray(); err != nil { + panic("BUG: popArray should never fail immediately after pushArray: " + err.Error()) + } + } + if err != nil { + return nil, wrapSyntacticError(d, err, pos-n, +1) // report position at start of value + } + d.prevEnd = pos + d.prevStart = pos - n + return d.buf[pos-n : pos : pos], nil +} + +// CheckNextValue checks whether the next value is syntactically valid, +// but does not advance the read offset. +func (d *decoderState) CheckNextValue() error { + d.PeekKind() // populates d.peekPos and d.peekErr + pos, err := d.peekPos, d.peekErr + d.peekPos, d.peekErr = 0, nil + if err != nil { + return err + } + + var flags jsonwire.ValueFlags + if pos, err := d.consumeValue(&flags, pos, d.Tokens.Depth()); err != nil { + return wrapSyntacticError(d, err, pos, +1) + } + return nil +} + +// CheckEOF verifies that the input has no more data. +func (d *decoderState) CheckEOF() error { + switch pos, err := d.consumeWhitespace(d.prevEnd); err { + case nil: + err := jsonwire.NewInvalidCharacterError(d.buf[pos:], "after top-level value") + return wrapSyntacticError(d, err, pos, 0) + case io.ErrUnexpectedEOF: + return nil + default: + return err + } +} + +// consumeWhitespace consumes all whitespace starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the last whitespace. +// If it returns nil, there is guaranteed to at least be one unread byte. +// +// The following pattern is common in this implementation: +// +// pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) +// if d.needMore(pos) { +// if pos, err = d.consumeWhitespace(pos); err != nil { +// return ... +// } +// } +// +// It is difficult to simplify this without sacrificing performance since +// consumeWhitespace must be inlined. The body of the if statement is +// executed only in rare situations where we need to fetch more data. +// Since fetching may return an error, we also need to check the error. +func (d *decoderState) consumeWhitespace(pos int) (newPos int, err error) { + for { + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + absPos := d.baseOffset + int64(pos) + err = d.fetch() // will mutate d.buf and invalidate pos + pos = int(absPos - d.baseOffset) + if err != nil { + return pos, err + } + continue + } + return pos, nil + } +} + +// consumeValue consumes a single JSON value starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the value. +func (d *decoderState) consumeValue(flags *jsonwire.ValueFlags, pos, depth int) (newPos int, err error) { + for { + var n int + var err error + switch next := Kind(d.buf[pos]).normalize(); next { + case 'n': + if n = jsonwire.ConsumeNull(d.buf[pos:]); n == 0 { + n, err = jsonwire.ConsumeLiteral(d.buf[pos:], "null") + } + case 'f': + if n = jsonwire.ConsumeFalse(d.buf[pos:]); n == 0 { + n, err = jsonwire.ConsumeLiteral(d.buf[pos:], "false") + } + case 't': + if n = jsonwire.ConsumeTrue(d.buf[pos:]); n == 0 { + n, err = jsonwire.ConsumeLiteral(d.buf[pos:], "true") + } + case '"': + if n = jsonwire.ConsumeSimpleString(d.buf[pos:]); n == 0 { + return d.consumeString(flags, pos) + } + case '0': + // NOTE: Since JSON numbers are not self-terminating, + // we need to make sure that the next byte is not part of a number. + if n = jsonwire.ConsumeSimpleNumber(d.buf[pos:]); n == 0 || d.needMore(pos+n) { + return d.consumeNumber(pos) + } + case '{': + return d.consumeObject(flags, pos, depth) + case '[': + return d.consumeArray(flags, pos, depth) + default: + if (d.Tokens.Last.isObject() && next == ']') || (d.Tokens.Last.isArray() && next == '}') { + return pos, errMismatchDelim + } + return pos, jsonwire.NewInvalidCharacterError(d.buf[pos:], "at start of value") + } + if err == io.ErrUnexpectedEOF { + absPos := d.baseOffset + int64(pos) + err = d.fetch() // will mutate d.buf and invalidate pos + pos = int(absPos - d.baseOffset) + if err != nil { + return pos + n, err + } + continue + } + return pos + n, err + } +} + +// consumeLiteral consumes a single JSON literal starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the literal. +func (d *decoderState) consumeLiteral(pos int, lit string) (newPos int, err error) { + for { + n, err := jsonwire.ConsumeLiteral(d.buf[pos:], lit) + if err == io.ErrUnexpectedEOF { + absPos := d.baseOffset + int64(pos) + err = d.fetch() // will mutate d.buf and invalidate pos + pos = int(absPos - d.baseOffset) + if err != nil { + return pos + n, err + } + continue + } + return pos + n, err + } +} + +// consumeString consumes a single JSON string starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the string. +func (d *decoderState) consumeString(flags *jsonwire.ValueFlags, pos int) (newPos int, err error) { + var n int + for { + n, err = jsonwire.ConsumeStringResumable(flags, d.buf[pos:], n, !d.Flags.Get(jsonflags.AllowInvalidUTF8)) + if err == io.ErrUnexpectedEOF { + absPos := d.baseOffset + int64(pos) + err = d.fetch() // will mutate d.buf and invalidate pos + pos = int(absPos - d.baseOffset) + if err != nil { + return pos + n, err + } + continue + } + return pos + n, err + } +} + +// consumeNumber consumes a single JSON number starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the number. +func (d *decoderState) consumeNumber(pos int) (newPos int, err error) { + var n int + var state jsonwire.ConsumeNumberState + for { + n, state, err = jsonwire.ConsumeNumberResumable(d.buf[pos:], n, state) + // NOTE: Since JSON numbers are not self-terminating, + // we need to make sure that the next byte is not part of a number. + if err == io.ErrUnexpectedEOF || d.needMore(pos+n) { + mayTerminate := err == nil + absPos := d.baseOffset + int64(pos) + err = d.fetch() // will mutate d.buf and invalidate pos + pos = int(absPos - d.baseOffset) + if err != nil { + if mayTerminate && err == io.ErrUnexpectedEOF { + return pos + n, nil + } + return pos, err + } + continue + } + return pos + n, err + } +} + +// consumeObject consumes a single JSON object starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the object. +func (d *decoderState) consumeObject(flags *jsonwire.ValueFlags, pos, depth int) (newPos int, err error) { + var n int + var names *objectNamespace + if !d.Flags.Get(jsonflags.AllowDuplicateNames) { + d.Namespaces.push() + defer d.Namespaces.pop() + names = d.Namespaces.Last() + } + + // Handle before start. + if uint(pos) >= uint(len(d.buf)) || d.buf[pos] != '{' { + panic("BUG: consumeObject must be called with a buffer that starts with '{'") + } else if depth == maxNestingDepth+1 { + return pos, errMaxDepth + } + pos++ + + // Handle after start. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, err + } + } + if d.buf[pos] == '}' { + pos++ + return pos, nil + } + + depth++ + for { + // Handle before name. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, err + } + } + var flags2 jsonwire.ValueFlags + if n = jsonwire.ConsumeSimpleString(d.buf[pos:]); n == 0 { + oldAbsPos := d.baseOffset + int64(pos) + pos, err = d.consumeString(&flags2, pos) + newAbsPos := d.baseOffset + int64(pos) + n = int(newAbsPos - oldAbsPos) + flags.Join(flags2) + if err != nil { + return pos, err + } + } else { + pos += n + } + quotedName := d.buf[pos-n : pos] + if !d.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(quotedName, flags2.IsVerbatim()) { + return pos - n, wrapWithObjectName(ErrDuplicateName, quotedName) + } + + // Handle after name. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, wrapWithObjectName(err, quotedName) + } + } + if d.buf[pos] != ':' { + err := jsonwire.NewInvalidCharacterError(d.buf[pos:], "after object name (expecting ':')") + return pos, wrapWithObjectName(err, quotedName) + } + pos++ + + // Handle before value. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, wrapWithObjectName(err, quotedName) + } + } + pos, err = d.consumeValue(flags, pos, depth) + if err != nil { + return pos, wrapWithObjectName(err, quotedName) + } + + // Handle after value. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, err + } + } + switch d.buf[pos] { + case ',': + pos++ + continue + case '}': + pos++ + return pos, nil + default: + return pos, jsonwire.NewInvalidCharacterError(d.buf[pos:], "after object value (expecting ',' or '}')") + } + } +} + +// consumeArray consumes a single JSON array starting at d.buf[pos:]. +// It returns the new position in d.buf immediately after the array. +func (d *decoderState) consumeArray(flags *jsonwire.ValueFlags, pos, depth int) (newPos int, err error) { + // Handle before start. + if uint(pos) >= uint(len(d.buf)) || d.buf[pos] != '[' { + panic("BUG: consumeArray must be called with a buffer that starts with '['") + } else if depth == maxNestingDepth+1 { + return pos, errMaxDepth + } + pos++ + + // Handle after start. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, err + } + } + if d.buf[pos] == ']' { + pos++ + return pos, nil + } + + var idx int64 + depth++ + for { + // Handle before value. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, err + } + } + pos, err = d.consumeValue(flags, pos, depth) + if err != nil { + return pos, wrapWithArrayIndex(err, idx) + } + + // Handle after value. + pos += jsonwire.ConsumeWhitespace(d.buf[pos:]) + if d.needMore(pos) { + if pos, err = d.consumeWhitespace(pos); err != nil { + return pos, err + } + } + switch d.buf[pos] { + case ',': + pos++ + idx++ + continue + case ']': + pos++ + return pos, nil + default: + return pos, jsonwire.NewInvalidCharacterError(d.buf[pos:], "after array element (expecting ',' or ']')") + } + } +} + +// 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. +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. +// 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. +// Each level on the stack represents a nested JSON object or array. +// It is incremented whenever an [BeginObject] or [BeginArray] token is encountered +// and decremented whenever an [EndObject] or [EndArray] token is encountered. +// The depth is zero-indexed, where zero represents the top-level JSON value. +func (d *Decoder) StackDepth() int { + // NOTE: Keep in sync with Encoder.StackDepth. + return d.s.Tokens.Depth() - 1 +} + +// StackIndex returns information about the specified stack level. +// 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. +// +// It also reports the length of that JSON object or array. +// Each name and value in a JSON object is counted separately, +// so the effective number of members would be half the length. +// A complete JSON object must have an even length. +func (d *Decoder) StackIndex(i int) (Kind, int64) { + // NOTE: Keep in sync with Encoder.StackIndex. + switch s := d.s.Tokens.index(i); { + case i > 0 && s.isObject(): + return '{', s.Length() + case i > 0 && s.isArray(): + return '[', s.Length() + default: + return 0, s.Length() + } +} + +// StackPointer returns a JSON Pointer (RFC 6901) to the most recently read value. +func (d *Decoder) StackPointer() Pointer { + return Pointer(d.s.AppendStackPointer(nil, -1)) +} + +func (d *decoderState) AppendStackPointer(b []byte, where int) []byte { + d.Names.copyQuotedBuffer(d.buf) + return d.state.appendStackPointer(b, where) +} diff --git a/src/encoding/json/jsontext/decode_test.go b/src/encoding/json/jsontext/decode_test.go new file mode 100644 index 0000000000..67580e6f4f --- /dev/null +++ b/src/encoding/json/jsontext/decode_test.go @@ -0,0 +1,1267 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "errors" + "fmt" + "io" + "net" + "path" + "reflect" + "slices" + "strings" + "testing" + "testing/iotest" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsontest" + "encoding/json/internal/jsonwire" +) + +// equalTokens reports whether to sequences of tokens formats the same way. +func equalTokens(xs, ys []Token) bool { + if len(xs) != len(ys) { + return false + } + for i := range xs { + if !(reflect.DeepEqual(xs[i], ys[i]) || xs[i].String() == ys[i].String()) { + return false + } + } + return true +} + +// TestDecoder tests whether we can parse JSON with either tokens or raw values. +func TestDecoder(t *testing.T) { + for _, td := range coderTestdata { + for _, typeName := range []string{"Token", "Value", "TokenDelims"} { + t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) { + testDecoder(t, td.name.Where, typeName, td) + }) + } + } +} +func testDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) { + dec := NewDecoder(bytes.NewBufferString(td.in)) + switch typeName { + case "Token": + var tokens []Token + var pointers []Pointer + for { + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + t.Fatalf("%s: Decoder.ReadToken error: %v", where, err) + } + tokens = append(tokens, tok.Clone()) + if td.pointers != nil { + pointers = append(pointers, dec.StackPointer()) + } + } + if !equalTokens(tokens, td.tokens) { + t.Fatalf("%s: tokens mismatch:\ngot %v\nwant %v", where, tokens, td.tokens) + } + if !slices.Equal(pointers, td.pointers) { + t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers) + } + case "Value": + val, err := dec.ReadValue() + if err != nil { + t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) + } + got := string(val) + want := strings.TrimSpace(td.in) + if got != want { + t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want) + } + case "TokenDelims": + // Use ReadToken for object/array delimiters, ReadValue otherwise. + var tokens []Token + loop: + for { + switch dec.PeekKind() { + case '{', '}', '[', ']': + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break loop + } + t.Fatalf("%s: Decoder.ReadToken error: %v", where, err) + } + tokens = append(tokens, tok.Clone()) + default: + val, err := dec.ReadValue() + if err != nil { + if err == io.EOF { + break loop + } + t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) + } + tokens = append(tokens, rawToken(string(val))) + } + } + if !equalTokens(tokens, td.tokens) { + t.Fatalf("%s: tokens mismatch:\ngot %v\nwant %v", where, tokens, td.tokens) + } + } +} + +// TestFaultyDecoder tests that temporary I/O errors are not fatal. +func TestFaultyDecoder(t *testing.T) { + for _, td := range coderTestdata { + for _, typeName := range []string{"Token", "Value"} { + t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) { + testFaultyDecoder(t, td.name.Where, typeName, td) + }) + } + } +} +func testFaultyDecoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) { + b := &FaultyBuffer{ + B: []byte(td.in), + MaxBytes: 1, + MayError: io.ErrNoProgress, + } + + // Read all the tokens. + // If the underlying io.Reader is faulty, then Read may return + // an error without changing the internal state machine. + // In other words, I/O errors occur before syntactic errors. + dec := NewDecoder(b) + switch typeName { + case "Token": + var tokens []Token + for { + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + if !errors.Is(err, io.ErrNoProgress) { + t.Fatalf("%s: %d: Decoder.ReadToken error: %v", where, len(tokens), err) + } + continue + } + tokens = append(tokens, tok.Clone()) + } + if !equalTokens(tokens, td.tokens) { + t.Fatalf("%s: tokens mismatch:\ngot %s\nwant %s", where, tokens, td.tokens) + } + case "Value": + for { + val, err := dec.ReadValue() + if err != nil { + if err == io.EOF { + break + } + if !errors.Is(err, io.ErrNoProgress) { + t.Fatalf("%s: Decoder.ReadValue error: %v", where, err) + } + continue + } + got := string(val) + want := strings.TrimSpace(td.in) + if got != want { + t.Fatalf("%s: Decoder.ReadValue = %s, want %s", where, got, want) + } + } + } +} + +type decoderMethodCall struct { + wantKind Kind + wantOut tokOrVal + wantErr error + wantPointer Pointer +} + +var decoderErrorTestdata = []struct { + name jsontest.CaseName + opts []Options + in string + calls []decoderMethodCall + wantOffset int +}{{ + name: jsontest.Name("InvalidStart"), + in: ` #`, + calls: []decoderMethodCall{ + {'#', zeroToken, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""}, + {'#', zeroValue, newInvalidCharacterError("#", "at start of value").withPos(" ", ""), ""}, + }, +}, { + name: jsontest.Name("StreamN0"), + in: ` `, + calls: []decoderMethodCall{ + {0, zeroToken, io.EOF, ""}, + {0, zeroValue, io.EOF, ""}, + }, +}, { + name: jsontest.Name("StreamN1"), + in: ` null `, + calls: []decoderMethodCall{ + {'n', Null, nil, ""}, + {0, zeroToken, io.EOF, ""}, + {0, zeroValue, io.EOF, ""}, + }, + wantOffset: len(` null`), +}, { + name: jsontest.Name("StreamN2"), + in: ` nullnull `, + calls: []decoderMethodCall{ + {'n', Null, nil, ""}, + {'n', Null, nil, ""}, + {0, zeroToken, io.EOF, ""}, + {0, zeroValue, io.EOF, ""}, + }, + wantOffset: len(` nullnull`), +}, { + name: jsontest.Name("StreamN2/ExtraComma"), // stream is whitespace delimited, not comma delimited + in: ` null , null `, + calls: []decoderMethodCall{ + {'n', Null, nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""}, + {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` null `, ""), ""}, + }, + wantOffset: len(` null`), +}, { + name: jsontest.Name("TruncatedNull"), + in: `nul`, + calls: []decoderMethodCall{ + {'n', zeroToken, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""}, + {'n', zeroValue, E(io.ErrUnexpectedEOF).withPos(`nul`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidNull"), + in: `nulL`, + calls: []decoderMethodCall{ + {'n', zeroToken, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""}, + {'n', zeroValue, newInvalidCharacterError("L", `in literal null (expecting 'l')`).withPos(`nul`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedFalse"), + in: `fals`, + calls: []decoderMethodCall{ + {'f', zeroToken, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""}, + {'f', zeroValue, E(io.ErrUnexpectedEOF).withPos(`fals`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidFalse"), + in: `falsE`, + calls: []decoderMethodCall{ + {'f', zeroToken, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""}, + {'f', zeroValue, newInvalidCharacterError("E", `in literal false (expecting 'e')`).withPos(`fals`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedTrue"), + in: `tru`, + calls: []decoderMethodCall{ + {'t', zeroToken, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""}, + {'t', zeroValue, E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidTrue"), + in: `truE`, + calls: []decoderMethodCall{ + {'t', zeroToken, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""}, + {'t', zeroValue, newInvalidCharacterError("E", `in literal true (expecting 'e')`).withPos(`tru`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedString"), + in: `"start`, + calls: []decoderMethodCall{ + {'"', zeroToken, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""}, + {'"', zeroValue, E(io.ErrUnexpectedEOF).withPos(`"start`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidString"), + in: `"ok` + "\x00", + calls: []decoderMethodCall{ + {'"', zeroToken, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""}, + {'"', zeroValue, newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""}, + }, +}, { + name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"), + opts: []Options{AllowInvalidUTF8(true)}, + in: "\"living\xde\xad\xbe\xef\"", + calls: []decoderMethodCall{ + {'"', rawToken("\"living\xde\xad\xbe\xef\""), nil, ""}, + }, + wantOffset: len("\"living\xde\xad\xbe\xef\""), +}, { + name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"), + opts: []Options{AllowInvalidUTF8(true)}, + in: "\"living\xde\xad\xbe\xef\"", + calls: []decoderMethodCall{ + {'"', Value("\"living\xde\xad\xbe\xef\""), nil, ""}, + }, + wantOffset: len("\"living\xde\xad\xbe\xef\""), +}, { + name: jsontest.Name("InvalidString/RejectInvalidUTF8"), + opts: []Options{AllowInvalidUTF8(false)}, + in: "\"living\xde\xad\xbe\xef\"", + calls: []decoderMethodCall{ + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedNumber"), + in: `0.`, + calls: []decoderMethodCall{ + {'0', zeroToken, E(io.ErrUnexpectedEOF), ""}, + {'0', zeroValue, E(io.ErrUnexpectedEOF), ""}, + }, +}, { + name: jsontest.Name("InvalidNumber"), + in: `0.e`, + calls: []decoderMethodCall{ + {'0', zeroToken, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""}, + {'0', zeroValue, newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedObject/AfterStart"), + in: `{`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, + {'{', BeginObject, nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, + }, + wantOffset: len(`{`), +}, { + name: jsontest.Name("TruncatedObject/AfterName"), + in: `{"0"`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("0"), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0"`, "/0"), ""}, + }, + wantOffset: len(`{"0"`), +}, { + name: jsontest.Name("TruncatedObject/AfterColon"), + in: `{"0":`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("0"), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":`, "/0"), ""}, + }, + wantOffset: len(`{"0"`), +}, { + name: jsontest.Name("TruncatedObject/AfterValue"), + in: `{"0":0`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("0"), nil, ""}, + {'0', Uint(0), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, + }, + wantOffset: len(`{"0":0`), +}, { + name: jsontest.Name("TruncatedObject/AfterComma"), + in: `{"0":0,`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("0"), nil, ""}, + {'0', Uint(0), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, + }, + wantOffset: len(`{"0":0`), +}, { + name: jsontest.Name("InvalidObject/MissingColon"), + in: ` { "fizz" "buzz" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {0, zeroValue, newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + }, + wantOffset: len(` { "fizz"`), +}, { + name: jsontest.Name("InvalidObject/MissingColon/GotComma"), + in: ` { "fizz" , "buzz" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + }, + wantOffset: len(` { "fizz"`), +}, { + name: jsontest.Name("InvalidObject/MissingColon/GotHash"), + in: ` { "fizz" # "buzz" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {0, zeroValue, newInvalidCharacterError("#", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + }, + wantOffset: len(` { "fizz"`), +}, { + name: jsontest.Name("InvalidObject/MissingComma"), + in: ` { "fizz" : "buzz" "gazz" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {'"', String("buzz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {0, zeroValue, newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + }, + wantOffset: len(` { "fizz" : "buzz"`), +}, { + name: jsontest.Name("InvalidObject/MissingComma/GotColon"), + in: ` { "fizz" : "buzz" : "gazz" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {'"', String("buzz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + }, + wantOffset: len(` { "fizz" : "buzz"`), +}, { + name: jsontest.Name("InvalidObject/MissingComma/GotHash"), + in: ` { "fizz" : "buzz" # "gazz" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {'"', String("buzz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {0, zeroValue, newInvalidCharacterError("#", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + }, + wantOffset: len(` { "fizz" : "buzz"`), +}, { + name: jsontest.Name("InvalidObject/ExtraComma/AfterStart"), + in: ` { , } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, + {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/ExtraComma/AfterValue"), + in: ` { "fizz" : "buzz" , } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("fizz"), nil, ""}, + {'"', String("buzz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""}, + {0, zeroValue, newInvalidCharacterError(",", `at start of value`).withPos(` { "fizz" : "buzz" `, ""), ""}, + }, + wantOffset: len(` { "fizz" : "buzz"`), +}, { + name: jsontest.Name("InvalidObject/InvalidName/GotNull"), + in: ` { null : null } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("n", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'n', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, + {'n', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/InvalidName/GotFalse"), + in: ` { false : false } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("f", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'f', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, + {'f', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/InvalidName/GotTrue"), + in: ` { true : true } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("t", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'t', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, + {'t', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/InvalidName/GotNumber"), + in: ` { 0 : 0 } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("0", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'0', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, + {'0', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/InvalidName/GotObject"), + in: ` { {} : {} } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("{", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'{', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, + {'{', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/InvalidName/GotArray"), + in: ` { [] : [] } `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("[", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {'[', zeroToken, E(ErrNonStringName).withPos(` { `, ""), ""}, + {'[', zeroValue, E(ErrNonStringName).withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("InvalidObject/MismatchingDelim"), + in: ` { ] `, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError("]", "at start of string (expecting '\"')").withPos(` { `, ""), ""}, + {'{', BeginObject, nil, ""}, + {']', zeroToken, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""}, + {']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(` { `, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("ValidObject/InvalidValue"), + in: ` { } `, + calls: []decoderMethodCall{ + {'{', BeginObject, nil, ""}, + {'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(" { ", ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("ValidObject/UniqueNames"), + in: `{"0":0,"1":1} `, + calls: []decoderMethodCall{ + {'{', BeginObject, nil, ""}, + {'"', String("0"), nil, ""}, + {'0', Uint(0), nil, ""}, + {'"', String("1"), nil, ""}, + {'0', Uint(1), nil, ""}, + {'}', EndObject, nil, ""}, + }, + wantOffset: len(`{"0":0,"1":1}`), +}, { + name: jsontest.Name("ValidObject/DuplicateNames"), + opts: []Options{AllowDuplicateNames(true)}, + in: `{"0":0,"0":0} `, + calls: []decoderMethodCall{ + {'{', BeginObject, nil, ""}, + {'"', String("0"), nil, ""}, + {'0', Uint(0), nil, ""}, + {'"', String("0"), nil, ""}, + {'0', Uint(0), nil, ""}, + {'}', EndObject, nil, ""}, + }, + wantOffset: len(`{"0":0,"0":0}`), +}, { + name: jsontest.Name("InvalidObject/DuplicateNames"), + in: `{"X":{},"Y":{},"X":{}} `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("X"), nil, ""}, + {'{', BeginObject, nil, ""}, + {'}', EndObject, nil, ""}, + {'"', String("Y"), nil, ""}, + {'{', BeginObject, nil, ""}, + {'}', EndObject, nil, ""}, + {'"', zeroToken, E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"}, + {'"', zeroValue, E(ErrDuplicateName).withPos(`{"0":{},"Y":{},`, "/X"), "/Y"}, + }, + wantOffset: len(`{"0":{},"1":{}`), +}, { + name: jsontest.Name("TruncatedArray/AfterStart"), + in: `[`, + calls: []decoderMethodCall{ + {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, + {'[', BeginArray, nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[", ""), ""}, + }, + wantOffset: len(`[`), +}, { + name: jsontest.Name("TruncatedArray/AfterValue"), + in: `[0`, + calls: []decoderMethodCall{ + {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, + {'[', BeginArray, nil, ""}, + {'0', Uint(0), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0", ""), ""}, + }, + wantOffset: len(`[0`), +}, { + name: jsontest.Name("TruncatedArray/AfterComma"), + in: `[0,`, + calls: []decoderMethodCall{ + {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, + {'[', BeginArray, nil, ""}, + {'0', Uint(0), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos("[0,", ""), ""}, + }, + wantOffset: len(`[0`), +}, { + name: jsontest.Name("InvalidArray/MissingComma"), + in: ` [ "fizz" "buzz" ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, + {'[', BeginArray, nil, ""}, + {'"', String("fizz"), nil, ""}, + {0, zeroToken, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, + {0, zeroValue, newInvalidCharacterError("\"", "after array element (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, + }, + wantOffset: len(` [ "fizz"`), +}, { + name: jsontest.Name("InvalidArray/MismatchingDelim"), + in: ` [ } `, + calls: []decoderMethodCall{ + {'[', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""}, + {'[', BeginArray, nil, ""}, + {'}', zeroToken, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""}, + {'}', zeroValue, newInvalidCharacterError("}", "at start of value").withPos(` [ `, "/0"), ""}, + }, + wantOffset: len(` [`), +}, { + name: jsontest.Name("ValidArray/InvalidValue"), + in: ` [ ] `, + calls: []decoderMethodCall{ + {'[', BeginArray, nil, ""}, + {']', zeroValue, newInvalidCharacterError("]", "at start of value").withPos(" [ ", "/0"), ""}, + }, + wantOffset: len(` [`), +}, { + name: jsontest.Name("InvalidDelim/AfterTopLevel"), + in: `"",`, + calls: []decoderMethodCall{ + {'"', String(""), nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, + {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`""`, ""), ""}, + }, + wantOffset: len(`""`), +}, { + name: jsontest.Name("InvalidDelim/AfterBeginObject"), + in: `{:`, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError(":", `at start of string (expecting '"')`).withPos(`{`, ""), ""}, + {'{', BeginObject, nil, ""}, + {0, zeroToken, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, + {0, zeroValue, newInvalidCharacterError(":", "at start of value").withPos(`{`, ""), ""}, + }, + wantOffset: len(`{`), +}, { + name: jsontest.Name("InvalidDelim/AfterObjectName"), + in: `{"",`, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String(""), nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, + {0, zeroValue, newInvalidCharacterError(",", "after object name (expecting ':')").withPos(`{""`, "/"), ""}, + }, + wantOffset: len(`{""`), +}, { + name: jsontest.Name("ValidDelim/AfterObjectName"), + in: `{"":`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String(""), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":`, "/"), ""}, + }, + wantOffset: len(`{""`), +}, { + name: jsontest.Name("InvalidDelim/AfterObjectValue"), + in: `{"":"":`, + calls: []decoderMethodCall{ + {'{', zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String(""), nil, ""}, + {'"', String(""), nil, ""}, + {0, zeroToken, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, + {0, zeroValue, newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(`{"":""`, ""), ""}, + }, + wantOffset: len(`{"":""`), +}, { + name: jsontest.Name("ValidDelim/AfterObjectValue"), + in: `{"":"",`, + calls: []decoderMethodCall{ + {'{', zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String(""), nil, ""}, + {'"', String(""), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`{"":"",`, ""), ""}, + }, + wantOffset: len(`{"":""`), +}, { + name: jsontest.Name("InvalidDelim/AfterBeginArray"), + in: `[,`, + calls: []decoderMethodCall{ + {'[', zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, "/0"), ""}, + {'[', BeginArray, nil, ""}, + {0, zeroToken, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, + {0, zeroValue, newInvalidCharacterError(",", "at start of value").withPos(`[`, ""), ""}, + }, + wantOffset: len(`[`), +}, { + name: jsontest.Name("InvalidDelim/AfterArrayValue"), + in: `["":`, + calls: []decoderMethodCall{ + {'[', zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, + {'[', BeginArray, nil, ""}, + {'"', String(""), nil, ""}, + {0, zeroToken, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, + {0, zeroValue, newInvalidCharacterError(":", "after array element (expecting ',' or ']')").withPos(`[""`, ""), ""}, + }, + wantOffset: len(`[""`), +}, { + name: jsontest.Name("ValidDelim/AfterArrayValue"), + in: `["",`, + calls: []decoderMethodCall{ + {'[', zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, + {'[', BeginArray, nil, ""}, + {'"', String(""), nil, ""}, + {0, zeroToken, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, + {0, zeroValue, E(io.ErrUnexpectedEOF).withPos(`["",`, ""), ""}, + }, + wantOffset: len(`[""`), +}, { + name: jsontest.Name("ErrorPosition"), + in: ` "a` + "\xff" + `0" `, + calls: []decoderMethodCall{ + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""}, + }, +}, { + name: jsontest.Name("ErrorPosition/0"), + in: ` [ "a` + "\xff" + `1" ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, + {'[', BeginArray, nil, ""}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, + }, + wantOffset: len(` [`), +}, { + name: jsontest.Name("ErrorPosition/1"), + in: ` [ "a1" , "b` + "\xff" + `1" ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, + {'[', BeginArray, nil, ""}, + {'"', String("a1"), nil, ""}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, + }, + wantOffset: len(` [ "a1"`), +}, { + name: jsontest.Name("ErrorPosition/0/0"), + in: ` [ [ "a` + "\xff" + `2" ] ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, + {'[', BeginArray, nil, ""}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, + {'[', BeginArray, nil, "/0"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, + }, + wantOffset: len(` [ [`), +}, { + name: jsontest.Name("ErrorPosition/1/0"), + in: ` [ "a1" , [ "a` + "\xff" + `2" ] ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""}, + {'[', BeginArray, nil, ""}, + {'"', String("a1"), nil, "/0"}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/0"}, + {'[', BeginArray, nil, "/1"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), "/1"}, + }, + wantOffset: len(` [ "a1" , [`), +}, { + name: jsontest.Name("ErrorPosition/0/1"), + in: ` [ [ "a2" , "b` + "\xff" + `2" ] ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""}, + {'[', BeginArray, nil, ""}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""}, + {'[', BeginArray, nil, "/0"}, + {'"', String("a2"), nil, "/0/0"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), "/0/0"}, + }, + wantOffset: len(` [ [ "a2"`), +}, { + name: jsontest.Name("ErrorPosition/1/1"), + in: ` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""}, + {'[', BeginArray, nil, ""}, + {'"', String("a1"), nil, "/0"}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""}, + {'[', BeginArray, nil, "/1"}, + {'"', String("a2"), nil, "/1/0"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), "/1/0"}, + }, + wantOffset: len(` [ "a1" , [ "a2"`), +}, { + name: jsontest.Name("ErrorPosition/a1-"), + in: ` { "a` + "\xff" + `1" : "b1" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, + }, + wantOffset: len(` {`), +}, { + name: jsontest.Name("ErrorPosition/a1"), + in: ` { "a1" : "b` + "\xff" + `1" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, + }, + wantOffset: len(` { "a1"`), +}, { + name: jsontest.Name("ErrorPosition/c1-"), + in: ` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'"', String("b1"), nil, "/a1"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c`, ""), "/a1"}, + }, + wantOffset: len(` { "a1" : "b1"`), +}, { + name: jsontest.Name("ErrorPosition/c1"), + in: ` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'"', String("b1"), nil, "/a1"}, + {'"', String("c1"), nil, "/c1"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" : "c1" : "d`, "/c1"), "/c1"}, + }, + wantOffset: len(` { "a1" : "b1" , "c1"`), +}, { + name: jsontest.Name("ErrorPosition/a1/a2-"), + in: ` { "a1" : { "a` + "\xff" + `2" : "b2" } } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""}, + {'{', BeginObject, nil, "/a1"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), "/a1"}, + }, + wantOffset: len(` { "a1" : {`), +}, { + name: jsontest.Name("ErrorPosition/a1/a2"), + in: ` { "a1" : { "a2" : "b` + "\xff" + `2" } } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""}, + {'{', BeginObject, nil, "/a1"}, + {'"', String("a2"), nil, "/a1/a2"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), "/a1/a2"}, + }, + wantOffset: len(` { "a1" : { "a2"`), +}, { + name: jsontest.Name("ErrorPosition/a1/c2-"), + in: ` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'{', BeginObject, nil, "/a1"}, + {'"', String("a2"), nil, "/a1/a2"}, + {'"', String("b2"), nil, "/a1/a2"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), "/a1/a2"}, + }, + wantOffset: len(` { "a1" : { "a2" : "b2"`), +}, { + name: jsontest.Name("ErrorPosition/a1/c2"), + in: ` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a2"), nil, "/a1/a2"}, + {'"', String("b2"), nil, "/a1/a2"}, + {'"', String("c2"), nil, "/a1/c2"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), "/a1/c2"}, + }, + wantOffset: len(` { "a1" : { "a2" : "b2" , "c2"`), +}, { + name: jsontest.Name("ErrorPosition/1/a2"), + in: ` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""}, + {'[', BeginArray, nil, ""}, + {'"', String("a1"), nil, "/0"}, + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""}, + {'{', BeginObject, nil, "/1"}, + {'"', String("a2"), nil, "/1/a2"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), "/1/a2"}, + }, + wantOffset: len(` [ "a1" , { "a2"`), +}, { + name: jsontest.Name("ErrorPosition/c1/1"), + in: ` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `, + calls: []decoderMethodCall{ + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""}, + {'{', BeginObject, nil, ""}, + {'"', String("a1"), nil, "/a1"}, + {'"', String("b1"), nil, "/a1"}, + {'"', String("c1"), nil, "/c1"}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""}, + {'[', BeginArray, nil, "/c1"}, + {'"', String("a2"), nil, "/c1/0"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), "/c1/0"}, + }, + wantOffset: len(` { "a1" : "b1" , "c1" : [ "a2"`), +}, { + name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"), + in: ` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `, + calls: []decoderMethodCall{ + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {'[', BeginArray, nil, ""}, + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {'{', BeginObject, nil, "/0"}, + {'"', String("a1"), nil, "/0/a1"}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {'[', BeginArray, nil, ""}, + {'"', String("a2"), nil, "/0/a1/0"}, + {'{', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {'{', BeginObject, nil, "/0/a1/1"}, + {'"', String("a3"), nil, "/0/a1/1/a3"}, + {'"', String("b3"), nil, "/0/a1/1/a3"}, + {'"', String("c3"), nil, "/0/a1/1/c3"}, + {'[', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {'[', BeginArray, nil, "/0/a1/1/c3"}, + {'"', String("a4"), nil, "/0/a1/1/c3/0"}, + {'"', zeroValue, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, + {'"', zeroToken, E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, + }, + wantOffset: len(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4"`), +}} + +// TestDecoderErrors test that Decoder errors occur when we expect and +// leaves the Decoder in a consistent state. +func TestDecoderErrors(t *testing.T) { + for _, td := range decoderErrorTestdata { + t.Run(path.Join(td.name.Name), func(t *testing.T) { + testDecoderErrors(t, td.name.Where, td.opts, td.in, td.calls, td.wantOffset) + }) + } +} +func testDecoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, in string, calls []decoderMethodCall, wantOffset int) { + src := bytes.NewBufferString(in) + dec := NewDecoder(src, opts...) + for i, call := range calls { + gotKind := dec.PeekKind() + if gotKind != call.wantKind { + t.Fatalf("%s: %d: Decoder.PeekKind = %v, want %v", where, i, gotKind, call.wantKind) + } + + var gotErr error + switch wantOut := call.wantOut.(type) { + case Token: + var gotOut Token + gotOut, gotErr = dec.ReadToken() + if gotOut.String() != wantOut.String() { + t.Fatalf("%s: %d: Decoder.ReadToken = %v, want %v", where, i, gotOut, wantOut) + } + case Value: + var gotOut Value + gotOut, gotErr = dec.ReadValue() + if string(gotOut) != string(wantOut) { + t.Fatalf("%s: %d: Decoder.ReadValue = %s, want %s", where, i, gotOut, wantOut) + } + } + if !equalError(gotErr, call.wantErr) { + t.Fatalf("%s: %d: error mismatch:\ngot %v\nwant %v", where, i, gotErr, call.wantErr) + } + if call.wantPointer != "" { + gotPointer := dec.StackPointer() + if gotPointer != call.wantPointer { + t.Fatalf("%s: %d: Decoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer) + } + } + } + gotOffset := int(dec.InputOffset()) + if gotOffset != wantOffset { + t.Fatalf("%s: Decoder.InputOffset = %v, want %v", where, gotOffset, wantOffset) + } + gotUnread := string(dec.s.unreadBuffer()) // should be a prefix of wantUnread + wantUnread := in[wantOffset:] + if !strings.HasPrefix(wantUnread, gotUnread) { + t.Fatalf("%s: Decoder.UnreadBuffer = %v, want %v", where, gotUnread, wantUnread) + } +} + +// TestBufferDecoder tests that we detect misuses of bytes.Buffer with Decoder. +func TestBufferDecoder(t *testing.T) { + bb := bytes.NewBufferString("[null, false, true]") + dec := NewDecoder(bb) + var err error + for { + if _, err = dec.ReadToken(); err != nil { + break + } + bb.WriteByte(' ') // not allowed to write to the buffer while reading + } + want := &ioError{action: "read", err: errBufferWriteAfterNext} + if !equalError(err, want) { + t.Fatalf("error mismatch: got %v, want %v", err, want) + } +} + +var resumableDecoderTestdata = []string{ + `0`, + `123456789`, + `0.0`, + `0.123456789`, + `0e0`, + `0e+0`, + `0e123456789`, + `0e+123456789`, + `123456789.123456789e+123456789`, + `-0`, + `-123456789`, + `-0.0`, + `-0.123456789`, + `-0e0`, + `-0e-0`, + `-0e123456789`, + `-0e-123456789`, + `-123456789.123456789e-123456789`, + + `""`, + `"a"`, + `"ab"`, + `"abc"`, + `"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"`, + `"\"\\\/\b\f\n\r\t"`, + `"\u0022\u005c\u002f\u0008\u000c\u000a\u000d\u0009"`, + `"\ud800\udead"`, + "\"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\U0001f602\"", + `"\u0080\u00f6\u20ac\ud799\ue000\ufb33\ufffd\ud83d\ude02"`, +} + +// TestResumableDecoder tests that resume logic for parsing a +// JSON string and number properly works across every possible split point. +func TestResumableDecoder(t *testing.T) { + for _, want := range resumableDecoderTestdata { + t.Run("", func(t *testing.T) { + dec := NewDecoder(iotest.OneByteReader(strings.NewReader(want))) + got, err := dec.ReadValue() + if err != nil { + t.Fatalf("Decoder.ReadValue error: %v", err) + } + if string(got) != want { + t.Fatalf("Decoder.ReadValue = %s, want %s", got, want) + } + }) + } +} + +// TestBlockingDecoder verifies that JSON values except numbers can be +// synchronously sent and received on a blocking pipe without a deadlock. +// Numbers are the exception since termination cannot be determined until +// either the pipe ends or a non-numeric character is encountered. +func TestBlockingDecoder(t *testing.T) { + values := []string{"null", "false", "true", `""`, `{}`, `[]`} + + r, w := net.Pipe() + defer r.Close() + defer w.Close() + + enc := NewEncoder(w, jsonflags.OmitTopLevelNewline|1) + dec := NewDecoder(r) + + errCh := make(chan error) + + // Test synchronous ReadToken calls. + for _, want := range values { + go func() { + errCh <- enc.WriteValue(Value(want)) + }() + + tok, err := dec.ReadToken() + if err != nil { + t.Fatalf("Decoder.ReadToken error: %v", err) + } + got := tok.String() + switch tok.Kind() { + case '"': + got = `"` + got + `"` + case '{', '[': + tok, err := dec.ReadToken() + if err != nil { + t.Fatalf("Decoder.ReadToken error: %v", err) + } + got += tok.String() + } + if got != want { + t.Fatalf("ReadTokens = %s, want %s", got, want) + } + + if err := <-errCh; err != nil { + t.Fatalf("Encoder.WriteValue error: %v", err) + } + } + + // Test synchronous ReadValue calls. + for _, want := range values { + go func() { + errCh <- enc.WriteValue(Value(want)) + }() + + got, err := dec.ReadValue() + if err != nil { + t.Fatalf("Decoder.ReadValue error: %v", err) + } + if string(got) != want { + t.Fatalf("ReadValue = %s, want %s", got, want) + } + + if err := <-errCh; err != nil { + t.Fatalf("Encoder.WriteValue error: %v", err) + } + } +} + +func TestPeekableDecoder(t *testing.T) { + type operation any // PeekKind | ReadToken | ReadValue | BufferWrite + type PeekKind struct { + want Kind + } + type ReadToken struct { + wantKind Kind + wantErr error + } + type ReadValue struct { + wantKind Kind + wantErr error + } + type WriteString struct { + in string + } + ops := []operation{ + PeekKind{0}, + WriteString{"[ "}, + ReadToken{0, io.EOF}, // previous error from PeekKind is cached once + ReadToken{'[', nil}, + + PeekKind{0}, + WriteString{"] "}, + ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ", "")}, // previous error from PeekKind is cached once + ReadValue{0, newInvalidCharacterError("]", "at start of value").withPos("[ ", "/0")}, + ReadToken{']', nil}, + + WriteString{"[ "}, + ReadToken{'[', nil}, + + WriteString{" null "}, + PeekKind{'n'}, + PeekKind{'n'}, + ReadToken{'n', nil}, + + WriteString{", "}, + PeekKind{0}, + WriteString{"fal"}, + PeekKind{'f'}, + ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , fal", "/1")}, + WriteString{"se "}, + ReadValue{'f', nil}, + + PeekKind{0}, + WriteString{" , "}, + PeekKind{0}, + WriteString{` "" `}, + ReadValue{0, E(io.ErrUnexpectedEOF).withPos("[ ] [ null , false , ", "")}, // previous error from PeekKind is cached once + ReadValue{'"', nil}, + + WriteString{" , 0"}, + PeekKind{'0'}, + ReadToken{'0', nil}, + + WriteString{" , {} , []"}, + PeekKind{'{'}, + ReadValue{'{', nil}, + ReadValue{'[', nil}, + + WriteString{"]"}, + ReadToken{']', nil}, + } + + bb := struct{ *bytes.Buffer }{new(bytes.Buffer)} + d := NewDecoder(bb) + for i, op := range ops { + switch op := op.(type) { + case PeekKind: + if got := d.PeekKind(); got != op.want { + t.Fatalf("%d: Decoder.PeekKind() = %v, want %v", i, got, op.want) + } + case ReadToken: + gotTok, gotErr := d.ReadToken() + gotKind := gotTok.Kind() + if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) { + t.Fatalf("%d: Decoder.ReadToken() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr) + } + case ReadValue: + gotVal, gotErr := d.ReadValue() + gotKind := gotVal.Kind() + if gotKind != op.wantKind || !equalError(gotErr, op.wantErr) { + t.Fatalf("%d: Decoder.ReadValue() = (%v, %v), want (%v, %v)", i, gotKind, gotErr, op.wantKind, op.wantErr) + } + case WriteString: + bb.WriteString(op.in) + default: + panic(fmt.Sprintf("unknown operation: %T", op)) + } + } +} diff --git a/src/encoding/json/jsontext/doc.go b/src/encoding/json/jsontext/doc.go new file mode 100644 index 0000000000..755305151f --- /dev/null +++ b/src/encoding/json/jsontext/doc.go @@ -0,0 +1,107 @@ +// 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 + +// Package jsontext implements syntactic processing of JSON +// as specified in RFC 4627, RFC 7159, RFC 7493, RFC 8259, and RFC 8785. +// JSON is a simple data interchange format that can represent +// primitive data types such as booleans, strings, and numbers, +// in addition to structured data types such as objects and arrays. +// +// The [Encoder] and [Decoder] types are used to encode or decode +// a stream of JSON tokens or values. +// +// # Tokens and Values +// +// A JSON token refers to the basic structural elements of JSON: +// +// - a JSON literal (i.e., null, true, or false) +// - a JSON string (e.g., "hello, world!") +// - a JSON number (e.g., 123.456) +// - a start or end delimiter for a JSON object (i.e., '{' or '}') +// - a start or end delimiter for a JSON array (i.e., '[' or ']') +// +// A JSON token is represented by the [Token] type in Go. Technically, +// there are two additional structural characters (i.e., ':' and ','), +// but there is no [Token] representation for them since their presence +// can be inferred by the structure of the JSON grammar itself. +// For example, there must always be an implicit colon between +// the name and value of a JSON object member. +// +// A JSON value refers to a complete unit of JSON data: +// +// - a JSON literal, string, or number +// - a JSON object (e.g., `{"name":"value"}`) +// - 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 +// between tokens and values as both contain literals, strings, and numbers. +// However, only a value can represent the entirety of a JSON object or array. +// +// 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. +// [Options] may be passed to the [NewEncoder] or [NewDecoder] constructors +// to configure the syntactic 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. +// 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 +// needing to marshal a concrete Go value representing them. +// Similarly, one can decode a stream of JSON tokens without +// needing to unmarshal them into a concrete Go value. +// +// This package uses JSON terminology when discussing JSON, which may differ +// from related concepts in Go or elsewhere in computing literature. +// +// - a JSON "object" refers to an unordered collection of name/value members. +// - a JSON "array" refers to an ordered sequence of elements. +// - a JSON "value" refers to either a literal (i.e., null, false, or true), +// string, number, object, or array. +// +// See RFC 8259 for more information. +// +// # Specifications +// +// Relevant specifications include RFC 4627, RFC 7159, RFC 7493, RFC 8259, +// and RFC 8785. Each RFC is generally a stricter subset of another RFC. +// In increasing order of strictness: +// +// - RFC 4627 and RFC 7159 do not require (but recommend) the use of UTF-8 +// and also do not require (but recommend) that object names be unique. +// - RFC 8259 requires the use of UTF-8, +// but does not require (but recommends) that object names be unique. +// - RFC 7493 requires the use of UTF-8 +// and also requires that object names be unique. +// - RFC 8785 defines a canonical representation. It requires the use of UTF-8 +// and also requires that object names be unique and in a specific ordering. +// It specifies exactly how strings and numbers must be formatted. +// +// The primary difference between RFC 4627 and RFC 7159 is that the former +// restricted top-level values to only JSON objects and arrays, while +// RFC 7159 and subsequent RFCs permit top-level values to additionally be +// JSON nulls, booleans, strings, or numbers. +// +// By default, this package operates on RFC 7493, but can be configured +// to operate according to the other RFC specifications. +// RFC 7493 is a stricter subset of RFC 8259 and fully compliant with it. +// In particular, it makes specific choices about behavior that RFC 8259 +// leaves as undefined in order to ensure greater interoperability. +package jsontext + +// requireKeyedLiterals can be embedded in a struct to require keyed literals. +type requireKeyedLiterals struct{} + +// nonComparable can be embedded in a struct to prevent comparability. +type nonComparable [0]func() diff --git a/src/encoding/json/jsontext/encode.go b/src/encoding/json/jsontext/encode.go new file mode 100644 index 0000000000..a1e6307adc --- /dev/null +++ b/src/encoding/json/jsontext/encode.go @@ -0,0 +1,972 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "io" + "math/bits" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" +) + +// Encoder is a streaming encoder from raw JSON tokens and values. +// It is used to write a stream of top-level JSON values, +// each terminated with a newline character. +// +// [Encoder.WriteToken] and [Encoder.WriteValue] calls may be interleaved. +// For example, the following JSON value: +// +// {"name":"value","array":[null,false,true,3.14159],"object":{"k":"v"}} +// +// can be composed with the following calls (ignoring errors for brevity): +// +// e.WriteToken(BeginObject) // { +// e.WriteToken(String("name")) // "name" +// e.WriteToken(String("value")) // "value" +// e.WriteValue(Value(`"array"`)) // "array" +// e.WriteToken(BeginArray) // [ +// e.WriteToken(Null) // null +// e.WriteToken(False) // false +// e.WriteValue(Value("true")) // true +// e.WriteToken(Float(3.14159)) // 3.14159 +// e.WriteToken(EndArray) // ] +// e.WriteValue(Value(`"object"`)) // "object" +// e.WriteValue(Value(`{"k":"v"}`)) // {"k":"v"} +// e.WriteToken(EndObject) // } +// +// The above is one of many possible sequence of calls and +// may not represent the most sensible method to call for any given token/value. +// For example, it is probably more common to call [Encoder.WriteToken] with a string +// for object names. +type Encoder struct { + s encoderState +} + +// encoderState is the low-level state of Encoder. +// It has exported fields and method for use by the "json" package. +type encoderState struct { + state + encodeBuffer + jsonopts.Struct + + SeenPointers map[any]struct{} // only used when marshaling; identical to json.seenPointers +} + +// encodeBuffer is a buffer split into 2 segments: +// +// - buf[0:len(buf)] // written (but unflushed) portion of the buffer +// - buf[len(buf):cap(buf)] // unused portion of the buffer +type encodeBuffer struct { + Buf []byte // may alias wr if it is a bytes.Buffer + + // baseOffset is added to len(buf) to obtain the absolute offset + // relative to the start of io.Writer stream. + baseOffset int64 + + wr io.Writer + + // maxValue is the approximate maximum Value size passed to WriteValue. + maxValue int + // unusedCache is the buffer returned by the UnusedBuffer method. + unusedCache []byte + // bufStats is statistics about buffer utilization. + // It is only used with pooled encoders in pools.go. + bufStats bufferStatistics +} + +// NewEncoder constructs a new streaming encoder writing to w +// configured with the provided options. +// It flushes the internal buffer when the buffer is sufficiently full or +// when a top-level value has been written. +// +// If w is a [bytes.Buffer], then the encoder appends directly into the buffer +// without copying the contents from an intermediate buffer. +func NewEncoder(w io.Writer, opts ...Options) *Encoder { + e := new(Encoder) + e.Reset(w, opts...) + return e +} + +// 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 +// or the [encoding/json/v2.MarshalToFunc] function. +func (e *Encoder) Reset(w io.Writer, opts ...Options) { + switch { + case e == nil: + panic("jsontext: invalid nil Encoder") + case w == nil: + panic("jsontext: invalid nil io.Writer") + case e.s.Flags.Get(jsonflags.WithinArshalCall): + panic("jsontext: cannot reset Encoder passed to json.MarshalerTo") + } + e.s.reset(nil, w, opts...) +} + +func (e *encoderState) reset(b []byte, w io.Writer, opts ...Options) { + e.state.reset() + e.encodeBuffer = encodeBuffer{Buf: b, wr: w, bufStats: e.bufStats} + if bb, ok := w.(*bytes.Buffer); ok && bb != nil { + e.Buf = bb.Bytes()[bb.Len():] // alias the unused buffer of bb + } + opts2 := jsonopts.Struct{} // avoid mutating e.Struct in case it is part of opts + opts2.Join(opts...) + e.Struct = opts2 + if e.Flags.Get(jsonflags.Multiline) { + if !e.Flags.Has(jsonflags.SpaceAfterColon) { + e.Flags.Set(jsonflags.SpaceAfterColon | 1) + } + if !e.Flags.Has(jsonflags.SpaceAfterComma) { + e.Flags.Set(jsonflags.SpaceAfterComma | 0) + } + if !e.Flags.Has(jsonflags.Indent) { + e.Flags.Set(jsonflags.Indent | 1) + e.Indent = "\t" + } + } +} + +// Options returns the options used to construct the decoder and +// may additionally contain semantic options passed to a +// [encoding/json/v2.MarshalEncode] call. +// +// If operating within +// a [encoding/json/v2.MarshalerTo.MarshalJSONTo] method call or +// a [encoding/json/v2.MarshalToFunc] function call, +// then the returned options are only valid within the call. +func (e *Encoder) Options() Options { + return &e.s.Struct +} + +// NeedFlush determines whether to flush at this point. +func (e *encoderState) NeedFlush() bool { + // NOTE: This function is carefully written to be inlinable. + + // Avoid flushing if e.wr is nil since there is no underlying writer. + // Flush if less than 25% of the capacity remains. + // Flushing at some constant fraction ensures that the buffer stops growing + // so long as the largest Token or Value fits within that unused capacity. + return e.wr != nil && (e.Tokens.Depth() == 1 || len(e.Buf) > 3*cap(e.Buf)/4) +} + +// Flush flushes the buffer to the underlying io.Writer. +// It may append a trailing newline after the top-level value. +func (e *encoderState) Flush() error { + if e.wr == nil || e.avoidFlush() { + return nil + } + + // In streaming mode, always emit a newline after the top-level value. + if e.Tokens.Depth() == 1 && !e.Flags.Get(jsonflags.OmitTopLevelNewline) { + e.Buf = append(e.Buf, '\n') + } + + // Inform objectNameStack that we are about to flush the buffer content. + e.Names.copyQuotedBuffer(e.Buf) + + // Specialize bytes.Buffer for better performance. + if bb, ok := e.wr.(*bytes.Buffer); ok { + // 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. + n, _ := bb.Write(e.Buf) // never fails unless bb is nil + e.baseOffset += int64(n) + + // If the internal buffer of bytes.Buffer is too small, + // append operations elsewhere in the Encoder may grow the buffer. + // This would be semantically correct, but hurts performance. + // As such, ensure 25% of the current length is always available + // to reduce the probability that other appends must allocate. + if avail := bb.Available(); avail < bb.Len()/4 { + bb.Grow(avail + 1) + } + + e.Buf = bb.AvailableBuffer() + return nil + } + + // Flush the internal buffer to the underlying io.Writer. + n, err := e.wr.Write(e.Buf) + e.baseOffset += int64(n) + if err != nil { + // In the event of an error, preserve the unflushed portion. + // Thus, write errors aren't fatal so long as the io.Writer + // maintains consistent state after errors. + if n > 0 { + e.Buf = e.Buf[:copy(e.Buf, e.Buf[n:])] + } + return &ioError{action: "write", err: err} + } + e.Buf = e.Buf[:0] + + // Check whether to grow the buffer. + // 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 + const growthRateFactor = 2 // higher value is slower + // By default, grow if below the maximum buffer size. + grow := cap(e.Buf) <= maxBufferSize/growthSizeFactor + // Growing can be expensive, so only grow + // if a sufficient number of bytes have been processed. + grow = grow && int64(cap(e.Buf)) < e.previousOffsetEnd()/growthRateFactor + if grow { + e.Buf = make([]byte, 0, cap(e.Buf)*growthSizeFactor) + } + + return nil +} +func (d *encodeBuffer) offsetAt(pos int) int64 { return d.baseOffset + int64(pos) } +func (e *encodeBuffer) previousOffsetEnd() int64 { return e.baseOffset + int64(len(e.Buf)) } +func (e *encodeBuffer) unflushedBuffer() []byte { return e.Buf } + +// avoidFlush indicates whether to avoid flushing to ensure there is always +// enough in the buffer to unwrite the last object member if it were empty. +func (e *encoderState) avoidFlush() bool { + switch { + case e.Tokens.Last.Length() == 0: + // Never flush after BeginObject or BeginArray since we don't know yet + // if the object or array will end up being empty. + return true + case e.Tokens.Last.needObjectValue(): + // Never flush before the object value since we don't know yet + // if the object value will end up being empty. + return true + case e.Tokens.Last.NeedObjectName() && len(e.Buf) >= 2: + // Never flush after the object value if it does turn out to be empty. + switch string(e.Buf[len(e.Buf)-2:]) { + case `ll`, `""`, `{}`, `[]`: // last two bytes of every empty value + return true + } + } + return false +} + +// UnwriteEmptyObjectMember unwrites the last object member if it is empty +// and reports whether it performed an unwrite operation. +func (e *encoderState) UnwriteEmptyObjectMember(prevName *string) bool { + if last := e.Tokens.Last; !last.isObject() || !last.NeedObjectName() || last.Length() == 0 { + panic("BUG: must be called on an object after writing a value") + } + + // The flushing logic is modified to never flush a trailing empty value. + // The encoder never writes trailing whitespace eagerly. + b := e.unflushedBuffer() + + // Detect whether the last value was empty. + var n int + if len(b) >= 3 { + switch string(b[len(b)-2:]) { + case "ll": // last two bytes of `null` + n = len(`null`) + case `""`: + // It is possible for a non-empty string to have `""` as a suffix + // if the second to the last quote was escaped. + if b[len(b)-3] == '\\' { + return false // e.g., `"\""` is not empty + } + n = len(`""`) + case `{}`: + n = len(`{}`) + case `[]`: + n = len(`[]`) + } + } + if n == 0 { + return false + } + + // Unwrite the value, whitespace, colon, name, whitespace, and comma. + b = b[:len(b)-n] + b = jsonwire.TrimSuffixWhitespace(b) + b = jsonwire.TrimSuffixByte(b, ':') + b = jsonwire.TrimSuffixString(b) + b = jsonwire.TrimSuffixWhitespace(b) + b = jsonwire.TrimSuffixByte(b, ',') + e.Buf = b // store back truncated unflushed buffer + + // Undo state changes. + e.Tokens.Last.decrement() // for object member value + e.Tokens.Last.decrement() // for object member name + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + if e.Tokens.Last.isActiveNamespace() { + e.Namespaces.Last().removeLast() + } + } + e.Names.clearLast() + if prevName != nil { + e.Names.copyQuotedBuffer(e.Buf) // required by objectNameStack.replaceLastUnquotedName + e.Names.replaceLastUnquotedName(*prevName) + } + return true +} + +// UnwriteOnlyObjectMemberName unwrites the only object member name +// and returns the unquoted name. +func (e *encoderState) UnwriteOnlyObjectMemberName() string { + if last := e.Tokens.Last; !last.isObject() || last.Length() != 1 { + panic("BUG: must be called on an object after writing first name") + } + + // Unwrite the name and whitespace. + b := jsonwire.TrimSuffixString(e.Buf) + isVerbatim := bytes.IndexByte(e.Buf[len(b):], '\\') < 0 + name := string(jsonwire.UnquoteMayCopy(e.Buf[len(b):], isVerbatim)) + e.Buf = jsonwire.TrimSuffixWhitespace(b) + + // Undo state changes. + e.Tokens.Last.decrement() + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + if e.Tokens.Last.isActiveNamespace() { + e.Namespaces.Last().removeLast() + } + } + e.Names.clearLast() + return name +} + +// WriteToken writes the next token and advances the internal write offset. +// +// The provided token kind must be consistent with the JSON grammar. +// 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 +// the internal state remains unchanged. The offset reported +// in [SyntacticError] will be relative to the [Encoder.OutputOffset]. +func (e *Encoder) WriteToken(t Token) error { + return e.s.WriteToken(t) +} +func (e *encoderState) WriteToken(t Token) error { + k := t.Kind() + b := e.Buf // use local variable to avoid mutating e in case of error + + // Append any delimiters or optional whitespace. + b = e.Tokens.MayAppendDelim(b, k) + if e.Flags.Get(jsonflags.AnyWhitespace) { + b = e.appendWhitespace(b, k) + } + pos := len(b) // offset before the token + + // Append the token to the output and to the state machine. + var err error + switch k { + case 'n': + b = append(b, "null"...) + err = e.Tokens.appendLiteral() + case 'f': + b = append(b, "false"...) + err = e.Tokens.appendLiteral() + case 't': + b = append(b, "true"...) + err = e.Tokens.appendLiteral() + case '"': + if b, err = t.appendString(b, &e.Flags); err != nil { + break + } + if e.Tokens.Last.NeedObjectName() { + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + if !e.Tokens.Last.isValidNamespace() { + err = errInvalidNamespace + break + } + if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) { + err = wrapWithObjectName(ErrDuplicateName, b[pos:]) + break + } + } + e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds + } + err = e.Tokens.appendString() + case '0': + if b, err = t.appendNumber(b, &e.Flags); err != nil { + break + } + err = e.Tokens.appendNumber() + case '{': + b = append(b, '{') + if err = e.Tokens.pushObject(); err != nil { + break + } + e.Names.push() + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + e.Namespaces.push() + } + case '}': + b = append(b, '}') + if err = e.Tokens.popObject(); err != nil { + break + } + e.Names.pop() + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + e.Namespaces.pop() + } + case '[': + b = append(b, '[') + err = e.Tokens.pushArray() + case ']': + b = append(b, ']') + err = e.Tokens.popArray() + default: + err = errInvalidToken + } + if err != nil { + return wrapSyntacticError(e, err, pos, +1) + } + + // Finish off the buffer and store it back into e. + e.Buf = b + if e.NeedFlush() { + return e.Flush() + } + return nil +} + +// AppendRaw appends either a raw string (without double quotes) or number. +// Specify safeASCII if the string output is guaranteed to be ASCII +// without any characters (including '<', '>', and '&') that need escaping, +// otherwise this will validate whether the string needs escaping. +// The appended bytes for a JSON number must be valid. +// +// This is a specialized implementation of Encoder.WriteValue +// that allows appending directly into the buffer. +// It is only called from marshal logic in the "json" package. +func (e *encoderState) AppendRaw(k Kind, safeASCII bool, appendFn func([]byte) ([]byte, error)) error { + b := e.Buf // use local variable to avoid mutating e in case of error + + // Append any delimiters or optional whitespace. + b = e.Tokens.MayAppendDelim(b, k) + if e.Flags.Get(jsonflags.AnyWhitespace) { + b = e.appendWhitespace(b, k) + } + pos := len(b) // offset before the token + + var err error + switch k { + case '"': + // Append directly into the encoder buffer by assuming that + // most of the time none of the characters need escaping. + b = append(b, '"') + if b, err = appendFn(b); err != nil { + return err + } + b = append(b, '"') + + // Check whether we need to escape the string and if necessary + // copy it to a scratch buffer and then escape it back. + isVerbatim := safeASCII || !jsonwire.NeedEscape(b[pos+len(`"`):len(b)-len(`"`)]) + if !isVerbatim { + var err error + b2 := append(e.unusedCache, b[pos+len(`"`):len(b)-len(`"`)]...) + b, err = jsonwire.AppendQuote(b[:pos], string(b2), &e.Flags) + e.unusedCache = b2[:0] + if err != nil { + return wrapSyntacticError(e, err, pos, +1) + } + } + + // Update the state machine. + if e.Tokens.Last.NeedObjectName() { + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + if !e.Tokens.Last.isValidNamespace() { + return wrapSyntacticError(e, err, pos, +1) + } + if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], isVerbatim) { + err = wrapWithObjectName(ErrDuplicateName, b[pos:]) + return wrapSyntacticError(e, err, pos, +1) + } + } + e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds + } + if err := e.Tokens.appendString(); err != nil { + return wrapSyntacticError(e, err, pos, +1) + } + case '0': + if b, err = appendFn(b); err != nil { + return err + } + if err := e.Tokens.appendNumber(); err != nil { + return wrapSyntacticError(e, err, pos, +1) + } + default: + panic("BUG: invalid kind") + } + + // Finish off the buffer and store it back into e. + e.Buf = b + if e.NeedFlush() { + return e.Flush() + } + return nil +} + +// WriteValue writes the next raw value and advances the internal write offset. +// The Encoder does not simply copy the provided value verbatim, but +// parses it to ensure that it is syntactically valid and reformats it +// according to how the Encoder is configured to format whitespace and strings. +// If [AllowInvalidUTF8] is specified, then any invalid UTF-8 is mangled +// as the Unicode replacement character, U+FFFD. +// +// 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. +func (e *Encoder) WriteValue(v Value) error { + return e.s.WriteValue(v) +} +func (e *encoderState) WriteValue(v Value) error { + e.maxValue |= len(v) // bitwise OR is a fast approximation of max + + k := v.Kind() + b := e.Buf // use local variable to avoid mutating e in case of error + + // Append any delimiters or optional whitespace. + b = e.Tokens.MayAppendDelim(b, k) + if e.Flags.Get(jsonflags.AnyWhitespace) { + b = e.appendWhitespace(b, k) + } + pos := len(b) // offset before the value + + // Append the value the output. + var n int + n += jsonwire.ConsumeWhitespace(v[n:]) + b, m, err := e.reformatValue(b, v[n:], e.Tokens.Depth()) + if err != nil { + return wrapSyntacticError(e, err, pos+n+m, +1) + } + n += m + n += jsonwire.ConsumeWhitespace(v[n:]) + if len(v) > n { + err = jsonwire.NewInvalidCharacterError(v[n:], "after top-level value") + return wrapSyntacticError(e, err, pos+n, 0) + } + + // Append the kind to the state machine. + switch k { + case 'n', 'f', 't': + err = e.Tokens.appendLiteral() + case '"': + if e.Tokens.Last.NeedObjectName() { + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + if !e.Tokens.Last.isValidNamespace() { + err = errInvalidNamespace + break + } + if e.Tokens.Last.isActiveNamespace() && !e.Namespaces.Last().insertQuoted(b[pos:], false) { + err = wrapWithObjectName(ErrDuplicateName, b[pos:]) + break + } + } + e.Names.ReplaceLastQuotedOffset(pos) // only replace if insertQuoted succeeds + } + err = e.Tokens.appendString() + case '0': + err = e.Tokens.appendNumber() + case '{': + if err = e.Tokens.pushObject(); err != nil { + break + } + if err = e.Tokens.popObject(); err != nil { + panic("BUG: popObject should never fail immediately after pushObject: " + err.Error()) + } + if e.Flags.Get(jsonflags.ReorderRawObjects) { + mustReorderObjects(b[pos:]) + } + case '[': + if err = e.Tokens.pushArray(); err != nil { + break + } + if err = e.Tokens.popArray(); err != nil { + panic("BUG: popArray should never fail immediately after pushArray: " + err.Error()) + } + if e.Flags.Get(jsonflags.ReorderRawObjects) { + mustReorderObjects(b[pos:]) + } + } + if err != nil { + return wrapSyntacticError(e, err, pos, +1) + } + + // Finish off the buffer and store it back into e. + e.Buf = b + if e.NeedFlush() { + return e.Flush() + } + return nil +} + +// CountNextDelimWhitespace counts the number of bytes of delimiter and +// whitespace bytes assuming the upcoming token is a JSON value. +// This method is used for error reporting at the semantic layer. +func (e *encoderState) CountNextDelimWhitespace() (n int) { + const next = Kind('"') // arbitrary kind as next JSON value + delim := e.Tokens.needDelim(next) + if delim > 0 { + n += len(",") | len(":") + } + if delim == ':' { + if e.Flags.Get(jsonflags.SpaceAfterColon) { + n += len(" ") + } + } else { + if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) { + n += len(" ") + } + if e.Flags.Get(jsonflags.Multiline) { + if m := e.Tokens.NeedIndent(next); m > 0 { + n += len("\n") + len(e.IndentPrefix) + (m-1)*len(e.Indent) + } + } + } + return n +} + +// appendWhitespace appends whitespace that immediately precedes the next token. +func (e *encoderState) appendWhitespace(b []byte, next Kind) []byte { + if delim := e.Tokens.needDelim(next); delim == ':' { + if e.Flags.Get(jsonflags.SpaceAfterColon) { + b = append(b, ' ') + } + } else { + if delim == ',' && e.Flags.Get(jsonflags.SpaceAfterComma) { + b = append(b, ' ') + } + if e.Flags.Get(jsonflags.Multiline) { + b = e.AppendIndent(b, e.Tokens.NeedIndent(next)) + } + } + return b +} + +// AppendIndent appends the appropriate number of indentation characters +// for the current nested level, n. +func (e *encoderState) AppendIndent(b []byte, n int) []byte { + if n == 0 { + return b + } + b = append(b, '\n') + b = append(b, e.IndentPrefix...) + for ; n > 1; n-- { + b = append(b, e.Indent...) + } + return b +} + +// reformatValue parses a JSON value from the start of src and +// 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) reformatValue(dst []byte, src Value, depth int) ([]byte, int, error) { + // TODO: Should this update ValueFlags as input? + if len(src) == 0 { + return dst, 0, io.ErrUnexpectedEOF + } + switch k := Kind(src[0]).normalize(); k { + case 'n': + if jsonwire.ConsumeNull(src) == 0 { + n, err := jsonwire.ConsumeLiteral(src, "null") + return dst, n, err + } + return append(dst, "null"...), len("null"), nil + case 'f': + if jsonwire.ConsumeFalse(src) == 0 { + n, err := jsonwire.ConsumeLiteral(src, "false") + return dst, n, err + } + return append(dst, "false"...), len("false"), nil + case 't': + if jsonwire.ConsumeTrue(src) == 0 { + n, err := jsonwire.ConsumeLiteral(src, "true") + return dst, n, err + } + return append(dst, "true"...), len("true"), nil + case '"': + if n := jsonwire.ConsumeSimpleString(src); n > 0 { + dst = append(dst, src[:n]...) // copy simple strings verbatim + return dst, n, nil + } + return jsonwire.ReformatString(dst, src, &e.Flags) + case '0': + if n := jsonwire.ConsumeSimpleNumber(src); n > 0 && !e.Flags.Get(jsonflags.CanonicalizeNumbers) { + dst = append(dst, src[:n]...) // copy simple numbers verbatim + return dst, n, nil + } + return jsonwire.ReformatNumber(dst, src, &e.Flags) + case '{': + return e.reformatObject(dst, src, depth) + case '[': + return e.reformatArray(dst, src, depth) + default: + return dst, 0, jsonwire.NewInvalidCharacterError(src, "at start of value") + } +} + +// reformatObject parses a JSON object from the start of src and +// appends it to the end of src, 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 start. + if len(src) == 0 || src[0] != '{' { + panic("BUG: reformatObject must be called with a buffer that starts with '{'") + } else if depth == maxNestingDepth+1 { + return dst, 0, errMaxDepth + } + dst = append(dst, '{') + n := len("{") + + // Append (possible) object end. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, io.ErrUnexpectedEOF + } + if src[n] == '}' { + dst = append(dst, '}') + n += len("}") + return dst, n, nil + } + + var err error + var names *objectNamespace + if !e.Flags.Get(jsonflags.AllowDuplicateNames) { + e.Namespaces.push() + defer e.Namespaces.pop() + names = e.Namespaces.Last() + } + depth++ + for { + // Append optional newline and indentation. + if e.Flags.Get(jsonflags.Multiline) { + dst = e.AppendIndent(dst, depth) + } + + // Append object name. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, io.ErrUnexpectedEOF + } + m := jsonwire.ConsumeSimpleString(src[n:]) + isVerbatim := m > 0 + if isVerbatim { + dst = append(dst, src[n:n+m]...) + } else { + dst, m, err = jsonwire.ReformatString(dst, src[n:], &e.Flags) + if err != nil { + return dst, n + m, err + } + } + quotedName := src[n : n+m] + if !e.Flags.Get(jsonflags.AllowDuplicateNames) && !names.insertQuoted(quotedName, isVerbatim) { + return dst, n, wrapWithObjectName(ErrDuplicateName, quotedName) + } + n += m + + // Append colon. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName) + } + if src[n] != ':' { + err = jsonwire.NewInvalidCharacterError(src[n:], "after object name (expecting ':')") + return dst, n, wrapWithObjectName(err, quotedName) + } + dst = append(dst, ':') + n += len(":") + if e.Flags.Get(jsonflags.SpaceAfterColon) { + dst = append(dst, ' ') + } + + // Append object value. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, wrapWithObjectName(io.ErrUnexpectedEOF, quotedName) + } + dst, m, err = e.reformatValue(dst, src[n:], depth) + if err != nil { + return dst, n + m, wrapWithObjectName(err, quotedName) + } + n += m + + // Append comma or object end. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, io.ErrUnexpectedEOF + } + switch src[n] { + case ',': + dst = append(dst, ',') + if e.Flags.Get(jsonflags.SpaceAfterComma) { + dst = append(dst, ' ') + } + n += len(",") + continue + case '}': + if e.Flags.Get(jsonflags.Multiline) { + dst = e.AppendIndent(dst, depth-1) + } + dst = append(dst, '}') + n += len("}") + return dst, n, nil + default: + return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "after object value (expecting ',' or '}')") + } + } +} + +// reformatArray parses a JSON array from the start of src and +// 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) reformatArray(dst []byte, src Value, depth int) ([]byte, int, error) { + // Append array start. + if len(src) == 0 || src[0] != '[' { + panic("BUG: reformatArray must be called with a buffer that starts with '['") + } else if depth == maxNestingDepth+1 { + return dst, 0, errMaxDepth + } + dst = append(dst, '[') + n := len("[") + + // Append (possible) array end. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, io.ErrUnexpectedEOF + } + if src[n] == ']' { + dst = append(dst, ']') + n += len("]") + return dst, n, nil + } + + var idx int64 + var err error + depth++ + for { + // Append optional newline and indentation. + if e.Flags.Get(jsonflags.Multiline) { + dst = e.AppendIndent(dst, depth) + } + + // Append array value. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, io.ErrUnexpectedEOF + } + var m int + dst, m, err = e.reformatValue(dst, src[n:], depth) + if err != nil { + return dst, n + m, wrapWithArrayIndex(err, idx) + } + n += m + + // Append comma or array end. + n += jsonwire.ConsumeWhitespace(src[n:]) + if uint(len(src)) <= uint(n) { + return dst, n, io.ErrUnexpectedEOF + } + switch src[n] { + case ',': + dst = append(dst, ',') + if e.Flags.Get(jsonflags.SpaceAfterComma) { + dst = append(dst, ' ') + } + n += len(",") + idx++ + continue + case ']': + if e.Flags.Get(jsonflags.Multiline) { + dst = e.AppendIndent(dst, depth-1) + } + dst = append(dst, ']') + n += len("]") + return dst, n, nil + default: + return dst, n, jsonwire.NewInvalidCharacterError(src[n:], "after array value (expecting ',' or ']')") + } + } +} + +// 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. +func (e *Encoder) OutputOffset() int64 { + return e.s.previousOffsetEnd() +} + +// UnusedBuffer returns a zero-length buffer with a possible non-zero capacity. +// This buffer is intended to be used to populate a [Value] +// being passed to an immediately succeeding [Encoder.WriteValue] call. +// +// Example usage: +// +// b := d.UnusedBuffer() +// b = append(b, '"') +// b = appendString(b, v) // append the string formatting of v +// b = append(b, '"') +// ... := d.WriteValue(b) +// +// It is the user's responsibility to ensure that the value is valid JSON. +func (e *Encoder) UnusedBuffer() []byte { + // 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 + // 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 + // 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.unusedCache) < n { + e.s.unusedCache = make([]byte, 0, n) + } + return e.s.unusedCache +} + +// 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 +// and decremented whenever an [EndObject] or [EndArray] token is encountered. +// The depth is zero-indexed, where zero represents the top-level JSON value. +func (e *Encoder) StackDepth() int { + // NOTE: Keep in sync with Decoder.StackDepth. + return e.s.Tokens.Depth() - 1 +} + +// StackIndex returns information about the specified stack level. +// 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. +// +// It also reports the length of that JSON object or array. +// Each name and value in a JSON object is counted separately, +// so the effective number of members would be half the length. +// A complete JSON object must have an even length. +func (e *Encoder) StackIndex(i int) (Kind, int64) { + // NOTE: Keep in sync with Decoder.StackIndex. + switch s := e.s.Tokens.index(i); { + case i > 0 && s.isObject(): + return '{', s.Length() + case i > 0 && s.isArray(): + return '[', s.Length() + default: + return 0, s.Length() + } +} + +// StackPointer returns a JSON Pointer (RFC 6901) to the most recently written value. +func (e *Encoder) StackPointer() Pointer { + return Pointer(e.s.AppendStackPointer(nil, -1)) +} + +func (e *encoderState) AppendStackPointer(b []byte, where int) []byte { + e.Names.copyQuotedBuffer(e.Buf) + return e.state.appendStackPointer(b, where) +} diff --git a/src/encoding/json/jsontext/encode_test.go b/src/encoding/json/jsontext/encode_test.go new file mode 100644 index 0000000000..206482263f --- /dev/null +++ b/src/encoding/json/jsontext/encode_test.go @@ -0,0 +1,737 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "errors" + "io" + "path" + "slices" + "testing" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsontest" + "encoding/json/internal/jsonwire" +) + +// TestEncoder tests whether we can produce JSON with either tokens or raw values. +func TestEncoder(t *testing.T) { + for _, td := range coderTestdata { + for _, formatName := range []string{"Compact", "Indented"} { + for _, typeName := range []string{"Token", "Value", "TokenDelims"} { + t.Run(path.Join(td.name.Name, typeName, formatName), func(t *testing.T) { + testEncoder(t, td.name.Where, formatName, typeName, td) + }) + } + } + } +} +func testEncoder(t *testing.T, where jsontest.CasePos, formatName, typeName string, td coderTestdataEntry) { + var want string + var opts []Options + dst := new(bytes.Buffer) + opts = append(opts, jsonflags.OmitTopLevelNewline|1) + want = td.outCompacted + switch formatName { + case "Indented": + opts = append(opts, Multiline(true)) + opts = append(opts, WithIndentPrefix("\t")) + opts = append(opts, WithIndent(" ")) + if td.outIndented != "" { + want = td.outIndented + } + } + enc := NewEncoder(dst, opts...) + + switch typeName { + case "Token": + var pointers []Pointer + for _, tok := range td.tokens { + if err := enc.WriteToken(tok); err != nil { + t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) + } + if td.pointers != nil { + pointers = append(pointers, enc.StackPointer()) + } + } + if !slices.Equal(pointers, td.pointers) { + t.Fatalf("%s: pointers mismatch:\ngot %q\nwant %q", where, pointers, td.pointers) + } + case "Value": + if err := enc.WriteValue(Value(td.in)); err != nil { + t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) + } + case "TokenDelims": + // Use WriteToken for object/array delimiters, WriteValue otherwise. + for _, tok := range td.tokens { + switch tok.Kind() { + case '{', '}', '[', ']': + if err := enc.WriteToken(tok); err != nil { + t.Fatalf("%s: Encoder.WriteToken error: %v", where, err) + } + default: + val := Value(tok.String()) + if tok.Kind() == '"' { + val, _ = jsonwire.AppendQuote(nil, tok.String(), &jsonflags.Flags{}) + } + if err := enc.WriteValue(val); err != nil { + t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) + } + } + } + } + + got := dst.String() + if got != want { + t.Errorf("%s: output mismatch:\ngot %q\nwant %q", where, got, want) + } +} + +// TestFaultyEncoder tests that temporary I/O errors are not fatal. +func TestFaultyEncoder(t *testing.T) { + for _, td := range coderTestdata { + for _, typeName := range []string{"Token", "Value"} { + t.Run(path.Join(td.name.Name, typeName), func(t *testing.T) { + testFaultyEncoder(t, td.name.Where, typeName, td) + }) + } + } +} +func testFaultyEncoder(t *testing.T, where jsontest.CasePos, typeName string, td coderTestdataEntry) { + b := &FaultyBuffer{ + MaxBytes: 1, + MayError: io.ErrShortWrite, + } + + // Write all the tokens. + // Even if the underlying io.Writer may be faulty, + // writing a valid token or value is guaranteed to at least + // be appended to the internal buffer. + // In other words, syntactic errors occur before I/O errors. + enc := NewEncoder(b) + switch typeName { + case "Token": + for i, tok := range td.tokens { + err := enc.WriteToken(tok) + if err != nil && !errors.Is(err, io.ErrShortWrite) { + t.Fatalf("%s: %d: Encoder.WriteToken error: %v", where, i, err) + } + } + case "Value": + err := enc.WriteValue(Value(td.in)) + if err != nil && !errors.Is(err, io.ErrShortWrite) { + t.Fatalf("%s: Encoder.WriteValue error: %v", where, err) + } + } + gotOutput := string(append(b.B, enc.s.unflushedBuffer()...)) + wantOutput := td.outCompacted + "\n" + if gotOutput != wantOutput { + t.Fatalf("%s: output mismatch:\ngot %s\nwant %s", where, gotOutput, wantOutput) + } +} + +type encoderMethodCall struct { + in tokOrVal + wantErr error + wantPointer Pointer +} + +var encoderErrorTestdata = []struct { + name jsontest.CaseName + opts []Options + calls []encoderMethodCall + wantOut string +}{{ + name: jsontest.Name("InvalidToken"), + calls: []encoderMethodCall{ + {zeroToken, E(errInvalidToken), ""}, + }, +}, { + name: jsontest.Name("InvalidValue"), + calls: []encoderMethodCall{ + {Value(`#`), newInvalidCharacterError("#", "at start of value"), ""}, + }, +}, { + name: jsontest.Name("InvalidValue/DoubleZero"), + calls: []encoderMethodCall{ + {Value(`00`), newInvalidCharacterError("0", "after top-level value").withPos(`0`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedValue"), + calls: []encoderMethodCall{ + {zeroValue, E(io.ErrUnexpectedEOF).withPos("", ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedNull"), + calls: []encoderMethodCall{ + {Value(`nul`), E(io.ErrUnexpectedEOF).withPos("nul", ""), ""}, + }, +}, { + name: jsontest.Name("InvalidNull"), + calls: []encoderMethodCall{ + {Value(`nulL`), newInvalidCharacterError("L", "in literal null (expecting 'l')").withPos(`nul`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedFalse"), + calls: []encoderMethodCall{ + {Value(`fals`), E(io.ErrUnexpectedEOF).withPos("fals", ""), ""}, + }, +}, { + name: jsontest.Name("InvalidFalse"), + calls: []encoderMethodCall{ + {Value(`falsE`), newInvalidCharacterError("E", "in literal false (expecting 'e')").withPos(`fals`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedTrue"), + calls: []encoderMethodCall{ + {Value(`tru`), E(io.ErrUnexpectedEOF).withPos(`tru`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidTrue"), + calls: []encoderMethodCall{ + {Value(`truE`), newInvalidCharacterError("E", "in literal true (expecting 'e')").withPos(`tru`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedString"), + calls: []encoderMethodCall{ + {Value(`"star`), E(io.ErrUnexpectedEOF).withPos(`"star`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidString"), + calls: []encoderMethodCall{ + {Value(`"ok` + "\x00"), newInvalidCharacterError("\x00", `in string (expecting non-control character)`).withPos(`"ok`, ""), ""}, + }, +}, { + name: jsontest.Name("ValidString/AllowInvalidUTF8/Token"), + opts: []Options{AllowInvalidUTF8(true)}, + calls: []encoderMethodCall{ + {String("living\xde\xad\xbe\xef"), nil, ""}, + }, + wantOut: "\"living\xde\xad\ufffd\ufffd\"\n", +}, { + name: jsontest.Name("ValidString/AllowInvalidUTF8/Value"), + opts: []Options{AllowInvalidUTF8(true)}, + calls: []encoderMethodCall{ + {Value("\"living\xde\xad\xbe\xef\""), nil, ""}, + }, + wantOut: "\"living\xde\xad\ufffd\ufffd\"\n", +}, { + name: jsontest.Name("InvalidString/RejectInvalidUTF8"), + opts: []Options{AllowInvalidUTF8(false)}, + calls: []encoderMethodCall{ + {String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8), ""}, + {Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("\"living\xde\xad", ""), ""}, + {BeginObject, nil, ""}, + {String("name"), nil, ""}, + {BeginArray, nil, ""}, + {String("living\xde\xad\xbe\xef"), E(jsonwire.ErrInvalidUTF8).withPos(`{"name":[`, "/name/0"), ""}, + {Value("\"living\xde\xad\xbe\xef\""), E(jsonwire.ErrInvalidUTF8).withPos("{\"name\":[\"living\xde\xad", "/name/0"), ""}, + }, + wantOut: `{"name":[`, +}, { + name: jsontest.Name("TruncatedNumber"), + calls: []encoderMethodCall{ + {Value(`0.`), E(io.ErrUnexpectedEOF).withPos("0", ""), ""}, + }, +}, { + name: jsontest.Name("InvalidNumber"), + calls: []encoderMethodCall{ + {Value(`0.e`), newInvalidCharacterError("e", "in number (expecting digit)").withPos(`0.`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedObject/AfterStart"), + calls: []encoderMethodCall{ + {Value(`{`), E(io.ErrUnexpectedEOF).withPos("{", ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedObject/AfterName"), + calls: []encoderMethodCall{ + {Value(`{"X"`), E(io.ErrUnexpectedEOF).withPos(`{"X"`, "/X"), ""}, + }, +}, { + name: jsontest.Name("TruncatedObject/AfterColon"), + calls: []encoderMethodCall{ + {Value(`{"X":`), E(io.ErrUnexpectedEOF).withPos(`{"X":`, "/X"), ""}, + }, +}, { + name: jsontest.Name("TruncatedObject/AfterValue"), + calls: []encoderMethodCall{ + {Value(`{"0":0`), E(io.ErrUnexpectedEOF).withPos(`{"0":0`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedObject/AfterComma"), + calls: []encoderMethodCall{ + {Value(`{"0":0,`), E(io.ErrUnexpectedEOF).withPos(`{"0":0,`, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidObject/MissingColon"), + calls: []encoderMethodCall{ + {Value(` { "fizz" "buzz" } `), newInvalidCharacterError("\"", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + {Value(` { "fizz" , "buzz" } `), newInvalidCharacterError(",", "after object name (expecting ':')").withPos(` { "fizz" `, "/fizz"), ""}, + }, +}, { + name: jsontest.Name("InvalidObject/MissingComma"), + calls: []encoderMethodCall{ + {Value(` { "fizz" : "buzz" "gazz" } `), newInvalidCharacterError("\"", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + {Value(` { "fizz" : "buzz" : "gazz" } `), newInvalidCharacterError(":", "after object value (expecting ',' or '}')").withPos(` { "fizz" : "buzz" `, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidObject/ExtraComma"), + calls: []encoderMethodCall{ + {Value(` { , } `), newInvalidCharacterError(",", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, + {Value(` { "fizz" : "buzz" , } `), newInvalidCharacterError("}", `at start of string (expecting '"')`).withPos(` { "fizz" : "buzz" , `, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidObject/InvalidName"), + calls: []encoderMethodCall{ + {Value(`{ null }`), newInvalidCharacterError("n", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, + {Value(`{ false }`), newInvalidCharacterError("f", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, + {Value(`{ true }`), newInvalidCharacterError("t", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, + {Value(`{ 0 }`), newInvalidCharacterError("0", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, + {Value(`{ {} }`), newInvalidCharacterError("{", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, + {Value(`{ [] }`), newInvalidCharacterError("[", `at start of string (expecting '"')`).withPos(`{ `, ""), ""}, + {BeginObject, nil, ""}, + {Null, E(ErrNonStringName).withPos(`{`, ""), ""}, + {Value(`null`), E(ErrNonStringName).withPos(`{`, ""), ""}, + {False, E(ErrNonStringName).withPos(`{`, ""), ""}, + {Value(`false`), E(ErrNonStringName).withPos(`{`, ""), ""}, + {True, E(ErrNonStringName).withPos(`{`, ""), ""}, + {Value(`true`), E(ErrNonStringName).withPos(`{`, ""), ""}, + {Uint(0), E(ErrNonStringName).withPos(`{`, ""), ""}, + {Value(`0`), E(ErrNonStringName).withPos(`{`, ""), ""}, + {BeginObject, E(ErrNonStringName).withPos(`{`, ""), ""}, + {Value(`{}`), E(ErrNonStringName).withPos(`{`, ""), ""}, + {BeginArray, E(ErrNonStringName).withPos(`{`, ""), ""}, + {Value(`[]`), E(ErrNonStringName).withPos(`{`, ""), ""}, + {EndObject, nil, ""}, + }, + wantOut: "{}\n", +}, { + name: jsontest.Name("InvalidObject/InvalidValue"), + calls: []encoderMethodCall{ + {Value(`{ "0": x }`), newInvalidCharacterError("x", `at start of value`).withPos(`{ "0": `, "/0"), ""}, + }, +}, { + name: jsontest.Name("InvalidObject/MismatchingDelim"), + calls: []encoderMethodCall{ + {Value(` { ] `), newInvalidCharacterError("]", `at start of string (expecting '"')`).withPos(` { `, ""), ""}, + {Value(` { "0":0 ] `), newInvalidCharacterError("]", `after object value (expecting ',' or '}')`).withPos(` { "0":0 `, ""), ""}, + {BeginObject, nil, ""}, + {EndArray, E(errMismatchDelim).withPos(`{`, ""), ""}, + {Value(`]`), newInvalidCharacterError("]", "at start of value").withPos(`{`, ""), ""}, + {EndObject, nil, ""}, + }, + wantOut: "{}\n", +}, { + name: jsontest.Name("ValidObject/UniqueNames"), + calls: []encoderMethodCall{ + {BeginObject, nil, ""}, + {String("0"), nil, ""}, + {Uint(0), nil, ""}, + {String("1"), nil, ""}, + {Uint(1), nil, ""}, + {EndObject, nil, ""}, + {Value(` { "0" : 0 , "1" : 1 } `), nil, ""}, + }, + wantOut: `{"0":0,"1":1}` + "\n" + `{"0":0,"1":1}` + "\n", +}, { + name: jsontest.Name("ValidObject/DuplicateNames"), + opts: []Options{AllowDuplicateNames(true)}, + calls: []encoderMethodCall{ + {BeginObject, nil, ""}, + {String("0"), nil, ""}, + {Uint(0), nil, ""}, + {String("0"), nil, ""}, + {Uint(0), nil, ""}, + {EndObject, nil, ""}, + {Value(` { "0" : 0 , "0" : 0 } `), nil, ""}, + }, + wantOut: `{"0":0,"0":0}` + "\n" + `{"0":0,"0":0}` + "\n", +}, { + name: jsontest.Name("InvalidObject/DuplicateNames"), + calls: []encoderMethodCall{ + {BeginObject, nil, ""}, + {String("X"), nil, ""}, + {BeginObject, nil, ""}, + {EndObject, nil, ""}, + {String("X"), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"}, + {Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},`, "/X"), "/X"}, + {String("Y"), nil, ""}, + {BeginObject, nil, ""}, + {EndObject, nil, ""}, + {String("X"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"}, + {Value(`"X"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/X"), "/Y"}, + {String("Y"), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"}, + {Value(`"Y"`), E(ErrDuplicateName).withPos(`{"X":{},"Y":{},`, "/Y"), "/Y"}, + {EndObject, nil, ""}, + {Value(` { "X" : 0 , "Y" : 1 , "X" : 0 } `), E(ErrDuplicateName).withPos(`{"X":{},"Y":{}}`+"\n"+` { "X" : 0 , "Y" : 1 , `, "/X"), ""}, + }, + wantOut: `{"X":{},"Y":{}}` + "\n", +}, { + name: jsontest.Name("TruncatedArray/AfterStart"), + calls: []encoderMethodCall{ + {Value(`[`), E(io.ErrUnexpectedEOF).withPos(`[`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedArray/AfterValue"), + calls: []encoderMethodCall{ + {Value(`[0`), E(io.ErrUnexpectedEOF).withPos(`[0`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedArray/AfterComma"), + calls: []encoderMethodCall{ + {Value(`[0,`), E(io.ErrUnexpectedEOF).withPos(`[0,`, ""), ""}, + }, +}, { + name: jsontest.Name("TruncatedArray/MissingComma"), + calls: []encoderMethodCall{ + {Value(` [ "fizz" "buzz" ] `), newInvalidCharacterError("\"", "after array value (expecting ',' or ']')").withPos(` [ "fizz" `, ""), ""}, + }, +}, { + name: jsontest.Name("InvalidArray/MismatchingDelim"), + calls: []encoderMethodCall{ + {Value(` [ } `), newInvalidCharacterError("}", `at start of value`).withPos(` [ `, "/0"), ""}, + {BeginArray, nil, ""}, + {EndObject, E(errMismatchDelim).withPos(`[`, "/0"), ""}, + {Value(`}`), newInvalidCharacterError("}", "at start of value").withPos(`[`, "/0"), ""}, + {EndArray, nil, ""}, + }, + wantOut: "[]\n", +}, { + name: jsontest.Name("Format/Object/SpaceAfterColon"), + opts: []Options{SpaceAfterColon(true)}, + calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, + wantOut: "{\"fizz\": \"buzz\",\"wizz\": \"wuzz\"}\n", +}, { + name: jsontest.Name("Format/Object/SpaceAfterComma"), + opts: []Options{SpaceAfterComma(true)}, + calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, + wantOut: "{\"fizz\":\"buzz\", \"wizz\":\"wuzz\"}\n", +}, { + name: jsontest.Name("Format/Object/SpaceAfterColonAndComma"), + opts: []Options{SpaceAfterColon(true), SpaceAfterComma(true)}, + calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, + wantOut: "{\"fizz\": \"buzz\", \"wizz\": \"wuzz\"}\n", +}, { + name: jsontest.Name("Format/Object/NoSpaceAfterColon+SpaceAfterComma+Multiline"), + opts: []Options{SpaceAfterColon(false), SpaceAfterComma(true), Multiline(true)}, + calls: []encoderMethodCall{{Value(`{"fizz":"buzz","wizz":"wuzz"}`), nil, ""}}, + wantOut: "{\n\t\"fizz\":\"buzz\", \n\t\"wizz\":\"wuzz\"\n}\n", +}, { + name: jsontest.Name("Format/Array/SpaceAfterComma"), + opts: []Options{SpaceAfterComma(true)}, + calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}}, + wantOut: "[\"fizz\", \"buzz\"]\n", +}, { + name: jsontest.Name("Format/Array/NoSpaceAfterComma+Multiline"), + opts: []Options{SpaceAfterComma(false), Multiline(true)}, + calls: []encoderMethodCall{{Value(`["fizz","buzz"]`), nil, ""}}, + wantOut: "[\n\t\"fizz\",\n\t\"buzz\"\n]\n", +}, { + name: jsontest.Name("Format/ReorderWithWhitespace"), + opts: []Options{ + AllowDuplicateNames(true), + AllowInvalidUTF8(true), + ReorderRawObjects(true), + SpaceAfterComma(true), + SpaceAfterColon(false), + Multiline(true), + WithIndentPrefix(" "), + WithIndent("\t"), + PreserveRawStrings(true), + }, + calls: []encoderMethodCall{ + {BeginArray, nil, ""}, + {BeginArray, nil, ""}, + {Value(` { "fizz" : "buzz" , + "zip" : { + "x` + "\xfd" + `x" : 123 , "x` + "\xff" + `x" : 123, "x` + "\xfe" + `x" : 123 + }, + "zap" : { + "xxx" : 333, "xxx": 1, "xxx": 22 + }, + "alpha" : "bravo" } `), nil, ""}, + {EndArray, nil, ""}, + {EndArray, nil, ""}, + }, + wantOut: "[\n \t[\n \t\t{\n \t\t\t\"alpha\":\"bravo\", \n \t\t\t\"fizz\":\"buzz\", \n \t\t\t\"zap\":{\n \t\t\t\t\"xxx\":1, \n \t\t\t\t\"xxx\":22, \n \t\t\t\t\"xxx\":333\n \t\t\t}, \n \t\t\t\"zip\":{\n \t\t\t\t\"x\xfdx\":123, \n \t\t\t\t\"x\xfex\":123, \n \t\t\t\t\"x\xffx\":123\n \t\t\t}\n \t\t}\n \t]\n ]\n", +}, { + name: jsontest.Name("Format/CanonicalizeRawInts"), + opts: []Options{CanonicalizeRawInts(true), SpaceAfterComma(true)}, + calls: []encoderMethodCall{ + {Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""}, + }, + wantOut: "[0.100, 5.0, 1E6, -9223372036854776000, -10, -1, 0, 0, 1, 10, 9223372036854776000]\n", +}, { + name: jsontest.Name("Format/CanonicalizeRawFloats"), + opts: []Options{CanonicalizeRawFloats(true), SpaceAfterComma(true)}, + calls: []encoderMethodCall{ + {Value(`[0.100,5.0,1E6,-9223372036854775808,-10,-1,-0,0,1,10,9223372036854775807]`), nil, ""}, + }, + wantOut: "[0.1, 5, 1000000, -9223372036854775808, -10, -1, 0, 0, 1, 10, 9223372036854775807]\n", +}, { + name: jsontest.Name("ErrorPosition"), + calls: []encoderMethodCall{ + {Value(` "a` + "\xff" + `0" `), E(jsonwire.ErrInvalidUTF8).withPos(` "a`, ""), ""}, + {String(`a` + "\xff" + `0`), E(jsonwire.ErrInvalidUTF8).withPos(``, ""), ""}, + }, +}, { + name: jsontest.Name("ErrorPosition/0"), + calls: []encoderMethodCall{ + {Value(` [ "a` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a`, "/0"), ""}, + {BeginArray, nil, ""}, + {Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`[ "a`, "/0"), ""}, + {String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`[`, "/0"), ""}, + }, + wantOut: `[`, +}, { + name: jsontest.Name("ErrorPosition/1"), + calls: []encoderMethodCall{ + {Value(` [ "a1" , "b` + "\xff" + `1" ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , "b`, "/1"), ""}, + {BeginArray, nil, ""}, + {String("a1"), nil, ""}, + {Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", "b`, "/1"), ""}, + {String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",`, "/1"), ""}, + }, + wantOut: `["a1"`, +}, { + name: jsontest.Name("ErrorPosition/0/0"), + calls: []encoderMethodCall{ + {Value(` [ [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a`, "/0/0"), ""}, + {BeginArray, nil, ""}, + {Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a`, "/0/0"), ""}, + {BeginArray, nil, "/0"}, + {Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[[ "a`, "/0/0"), "/0"}, + {String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[[`, "/0/0"), "/0"}, + }, + wantOut: `[[`, +}, { + name: jsontest.Name("ErrorPosition/1/0"), + calls: []encoderMethodCall{ + {Value(` [ "a1" , [ "a` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a`, "/1/0"), ""}, + {BeginArray, nil, ""}, + {String("a1"), nil, "/0"}, + {Value(` [ "a` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a`, "/1/0"), ""}, + {BeginArray, nil, "/1"}, + {Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[ "a`, "/1/0"), "/1"}, + {String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",[`, "/1/0"), "/1"}, + }, + wantOut: `["a1",[`, +}, { + name: jsontest.Name("ErrorPosition/0/1"), + calls: []encoderMethodCall{ + {Value(` [ [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ [ "a2" , "b`, "/0/1"), ""}, + {BeginArray, nil, ""}, + {Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[ [ "a2" , "b`, "/0/1"), ""}, + {BeginArray, nil, "/0"}, + {String("a2"), nil, "/0/0"}, + {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2", "b`, "/0/1"), "/0/0"}, + {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`[["a2",`, "/0/1"), "/0/0"}, + }, + wantOut: `[["a2"`, +}, { + name: jsontest.Name("ErrorPosition/1/1"), + calls: []encoderMethodCall{ + {Value(` [ "a1" , [ "a2" , "b` + "\xff" + `2" ] ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , [ "a2" , "b`, "/1/1"), ""}, + {BeginArray, nil, ""}, + {String("a1"), nil, "/0"}, + {Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", [ "a2" , "b`, "/1/1"), ""}, + {BeginArray, nil, "/1"}, + {String("a2"), nil, "/1/0"}, + {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2", "b`, "/1/1"), "/1/0"}, + {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",["a2",`, "/1/1"), "/1/0"}, + }, + wantOut: `["a1",["a2"`, +}, { + name: jsontest.Name("ErrorPosition/a1-"), + calls: []encoderMethodCall{ + {Value(` { "a` + "\xff" + `1" : "b1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a`, ""), ""}, + {BeginObject, nil, ""}, + {Value(` "a` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{ "a`, ""), ""}, + {String(`a` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{`, ""), ""}, + }, + wantOut: `{`, +}, { + name: jsontest.Name("ErrorPosition/a1"), + calls: []encoderMethodCall{ + {Value(` { "a1" : "b` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b`, "/a1"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {Value(` "b` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": "b`, "/a1"), ""}, + {String(`b` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":`, "/a1"), ""}, + }, + wantOut: `{"a1"`, +}, { + name: jsontest.Name("ErrorPosition/c1-"), + calls: []encoderMethodCall{ + {Value(` { "a1" : "b1" , "c` + "\xff" + `1" : "d1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c`, ""), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {String("b1"), nil, "/a1"}, + {Value(` "c` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1": "c`, ""), "/a1"}, + {String(`c` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":`, ""), "/a1"}, + }, + wantOut: `{"a1":"b1"`, +}, { + name: jsontest.Name("ErrorPosition/c1"), + calls: []encoderMethodCall{ + {Value(` { "a1" : "b1" , "c1" : "d` + "\xff" + `1" } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : "d`, "/c1"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {String("b1"), nil, "/a1"}, + {String("c1"), nil, "/c1"}, + {Value(` "d` + "\xff" + `1" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1": "d`, "/c1"), "/c1"}, + {String(`d` + "\xff" + `1`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1":"c1":`, "/c1"), "/c1"}, + }, + wantOut: `{"a1":"b1","c1"`, +}, { + name: jsontest.Name("ErrorPosition/a1/a2-"), + calls: []encoderMethodCall{ + {Value(` { "a1" : { "a` + "\xff" + `2" : "b2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a`, "/a1"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {Value(` { "a` + "\xff" + `2" : "b2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a`, "/a1"), ""}, + {BeginObject, nil, "/a1"}, + {Value(` "a` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{ "a`, "/a1"), "/a1"}, + {String(`a` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{`, "/a1"), "/a1"}, + }, + wantOut: `{"a1":{`, +}, { + name: jsontest.Name("ErrorPosition/a1/a2"), + calls: []encoderMethodCall{ + {Value(` { "a1" : { "a2" : "b` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b`, "/a1/a2"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b`, "/a1/a2"), ""}, + {BeginObject, nil, "/a1"}, + {String("a2"), nil, "/a1/a2"}, + {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2": "b`, "/a1/a2"), "/a1/a2"}, + {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":`, "/a1/a2"), "/a1/a2"}, + }, + wantOut: `{"a1":{"a2"`, +}, { + name: jsontest.Name("ErrorPosition/a1/c2-"), + calls: []encoderMethodCall{ + {Value(` { "a1" : { "a2" : "b2" , "c` + "\xff" + `2" : "d2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c`, "/a1"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {BeginObject, nil, "/a1"}, + {String("a2"), nil, "/a1/a2"}, + {String("b2"), nil, "/a1/a2"}, + {Value(` "c` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2", "c`, "/a1"), "/a1/a2"}, + {String(`c` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2",`, "/a1"), "/a1/a2"}, + }, + wantOut: `{"a1":{"a2":"b2"`, +}, { + name: jsontest.Name("ErrorPosition/a1/c2"), + calls: []encoderMethodCall{ + {Value(` { "a1" : { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {Value(` { "a2" : "b2" , "c2" : "d` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1": { "a2" : "b2" , "c2" : "d`, "/a1/c2"), ""}, + {BeginObject, nil, ""}, + {String("a2"), nil, "/a1/a2"}, + {String("b2"), nil, "/a1/a2"}, + {String("c2"), nil, "/a1/c2"}, + {Value(` "d` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2": "d`, "/a1/c2"), "/a1/c2"}, + {String(`d` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":{"a2":"b2","c2":`, "/a1/c2"), "/a1/c2"}, + }, + wantOut: `{"a1":{"a2":"b2","c2"`, +}, { + name: jsontest.Name("ErrorPosition/1/a2"), + calls: []encoderMethodCall{ + {Value(` [ "a1" , { "a2" : "b` + "\xff" + `2" } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ "a1" , { "a2" : "b`, "/1/a2"), ""}, + {BeginArray, nil, ""}, + {String("a1"), nil, "/0"}, + {Value(` { "a2" : "b` + "\xff" + `2" } `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1", { "a2" : "b`, "/1/a2"), ""}, + {BeginObject, nil, "/1"}, + {String("a2"), nil, "/1/a2"}, + {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2": "b`, "/1/a2"), "/1/a2"}, + {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`["a1",{"a2":`, "/1/a2"), "/1/a2"}, + }, + wantOut: `["a1",{"a2"`, +}, { + name: jsontest.Name("ErrorPosition/c1/1"), + calls: []encoderMethodCall{ + {Value(` { "a1" : "b1" , "c1" : [ "a2" , "b` + "\xff" + `2" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(` { "a1" : "b1" , "c1" : [ "a2" , "b`, "/c1/1"), ""}, + {BeginObject, nil, ""}, + {String("a1"), nil, "/a1"}, + {String("b1"), nil, "/a1"}, + {String("c1"), nil, "/c1"}, + {Value(` [ "a2" , "b` + "\xff" + `2" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1": [ "a2" , "b`, "/c1/1"), ""}, + {BeginArray, nil, "/c1"}, + {String("a2"), nil, "/c1/0"}, + {Value(` "b` + "\xff" + `2" `), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2", "b`, "/c1/1"), "/c1/0"}, + {String(`b` + "\xff" + `2`), E(jsonwire.ErrInvalidUTF8).withPos(`{"a1":"b1","c1":["a2",`, "/c1/1"), "/c1/0"}, + }, + wantOut: `{"a1":"b1","c1":["a2"`, +}, { + name: jsontest.Name("ErrorPosition/0/a1/1/c3/1"), + calls: []encoderMethodCall{ + {Value(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(` [ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {BeginArray, nil, ""}, + {Value(` { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[ { "a1" : [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {BeginObject, nil, "/0"}, + {String("a1"), nil, "/0/a1"}, + {Value(` [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1": [ "a2" , { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {BeginArray, nil, ""}, + {String("a2"), nil, "/0/a1/0"}, + {Value(` { "a3" : "b3" , "c3" : [ "a4" , "b` + "\xff" + `4" ] } `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2", { "a3" : "b3" , "c3" : [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {BeginObject, nil, "/0/a1/1"}, + {String("a3"), nil, "/0/a1/1/a3"}, + {String("b3"), nil, "/0/a1/1/a3"}, + {String("c3"), nil, "/0/a1/1/c3"}, + {Value(` [ "a4" , "b` + "\xff" + `4" ] `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3": [ "a4" , "b`, "/0/a1/1/c3/1"), ""}, + {BeginArray, nil, "/0/a1/1/c3"}, + {String("a4"), nil, "/0/a1/1/c3/0"}, + {Value(` "b` + "\xff" + `4" `), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4", "b`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, + {String(`b` + "\xff" + `4`), E(jsonwire.ErrInvalidUTF8).withPos(`[{"a1":["a2",{"a3":"b3","c3":["a4",`, "/0/a1/1/c3/1"), "/0/a1/1/c3/0"}, + }, + wantOut: `[{"a1":["a2",{"a3":"b3","c3":["a4"`, +}} + +// TestEncoderErrors test that Encoder errors occur when we expect and +// leaves the Encoder in a consistent state. +func TestEncoderErrors(t *testing.T) { + for _, td := range encoderErrorTestdata { + t.Run(path.Join(td.name.Name), func(t *testing.T) { + testEncoderErrors(t, td.name.Where, td.opts, td.calls, td.wantOut) + }) + } +} +func testEncoderErrors(t *testing.T, where jsontest.CasePos, opts []Options, calls []encoderMethodCall, wantOut string) { + dst := new(bytes.Buffer) + enc := NewEncoder(dst, opts...) + for i, call := range calls { + var gotErr error + switch tokVal := call.in.(type) { + case Token: + gotErr = enc.WriteToken(tokVal) + case Value: + gotErr = enc.WriteValue(tokVal) + } + if !equalError(gotErr, call.wantErr) { + t.Fatalf("%s: %d: error mismatch:\ngot %v\nwant %v", where, i, gotErr, call.wantErr) + } + if call.wantPointer != "" { + gotPointer := enc.StackPointer() + if gotPointer != call.wantPointer { + t.Fatalf("%s: %d: Encoder.StackPointer = %s, want %s", where, i, gotPointer, call.wantPointer) + } + } + } + gotOut := dst.String() + string(enc.s.unflushedBuffer()) + if gotOut != wantOut { + t.Fatalf("%s: output mismatch:\ngot %q\nwant %q", where, gotOut, wantOut) + } + gotOffset := int(enc.OutputOffset()) + wantOffset := len(wantOut) + if gotOffset != wantOffset { + t.Fatalf("%s: Encoder.OutputOffset = %v, want %v", where, gotOffset, wantOffset) + } +} diff --git a/src/encoding/json/jsontext/errors.go b/src/encoding/json/jsontext/errors.go new file mode 100644 index 0000000000..4b95d03f40 --- /dev/null +++ b/src/encoding/json/jsontext/errors.go @@ -0,0 +1,182 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "io" + "strconv" + + "encoding/json/internal/jsonwire" +) + +const errorPrefix = "jsontext: " + +type ioError struct { + action string // either "read" or "write" + err error +} + +func (e *ioError) Error() string { + return errorPrefix + e.action + " error: " + e.err.Error() +} +func (e *ioError) Unwrap() error { + return e.err +} + +// SyntacticError is a description of a syntactic 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. +type SyntacticError struct { + requireKeyedLiterals + nonComparable + + // ByteOffset indicates that an error occurred 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). + JSONPointer Pointer + + // Err is the underlying error. + Err error +} + +// wrapSyntacticError wraps an error and annotates it with a precise location +// using the provided [encoderState] or [decoderState]. +// If err is an [ioError] or [io.EOF], then it is not wrapped. +// +// It takes a relative offset pos that can be resolved into +// an absolute offset using state.offsetAt. +// +// It takes a where that specify how the JSON pointer is derived. +// If the underlying error is a [pointerSuffixError], +// then the suffix is appended to the derived pointer. +func wrapSyntacticError(state interface { + offsetAt(pos int) int64 + AppendStackPointer(b []byte, where int) []byte +}, err error, pos, where int) error { + if _, ok := err.(*ioError); err == io.EOF || ok { + return err + } + offset := state.offsetAt(pos) + ptr := state.AppendStackPointer(nil, where) + if serr, ok := err.(*pointerSuffixError); ok { + ptr = serr.appendPointer(ptr) + err = serr.error + } + if d, ok := state.(*decoderState); ok && err == errMismatchDelim { + where := "at start of value" + if len(d.Tokens.Stack) > 0 && d.Tokens.Last.Length() > 0 { + switch { + case d.Tokens.Last.isArray(): + where = "after array element (expecting ',' or ']')" + ptr = []byte(Pointer(ptr).Parent()) // problem is with parent array + case d.Tokens.Last.isObject(): + where = "after object value (expecting ',' or '}')" + ptr = []byte(Pointer(ptr).Parent()) // problem is with parent object + } + } + err = jsonwire.NewInvalidCharacterError(d.buf[pos:], where) + } + return &SyntacticError{ByteOffset: offset, JSONPointer: Pointer(ptr), Err: err} +} + +func (e *SyntacticError) Error() string { + pointer := e.JSONPointer + offset := e.ByteOffset + b := []byte(errorPrefix) + if e.Err != nil { + b = append(b, e.Err.Error()...) + if e.Err == ErrDuplicateName { + b = strconv.AppendQuote(append(b, ' '), pointer.LastToken()) + pointer = pointer.Parent() + offset = 0 // not useful to print offset for duplicate names + } + } else { + b = append(b, "syntactic error"...) + } + if pointer != "" { + b = strconv.AppendQuote(append(b, " within "...), jsonwire.TruncatePointer(string(pointer), 100)) + } + if offset > 0 { + b = strconv.AppendInt(append(b, " after offset "...), offset, 10) + } + return string(b) +} + +func (e *SyntacticError) Unwrap() error { + return e.Err +} + +// pointerSuffixError represents a JSON pointer suffix to be appended +// to [SyntacticError.JSONPointer]. It is an internal error type +// used within this package and does not appear in the public API. +// +// This type is primarily used to annotate errors in Encoder.WriteValue +// and Decoder.ReadValue with precise positions. +// At the time WriteValue or ReadValue is called, a JSON pointer to the +// upcoming value can be constructed using the Encoder/Decoder state. +// However, tracking pointers within values during normal operation +// would incur a performance penalty in the error-free case. +// +// To provide precise error locations without this overhead, +// the error is wrapped with object names or array indices +// as the call stack is popped when an error occurs. +// Since this happens in reverse order, pointerSuffixError holds +// the pointer in reverse and is only later reversed when appending to +// the pointer prefix. +// +// For example, if the encoder is at "/alpha/bravo/charlie" +// and an error occurs in WriteValue at "/xray/yankee/zulu", then +// the final pointer should be "/alpha/bravo/charlie/xray/yankee/zulu". +// +// As pointerSuffixError is populated during the error return path, +// it first contains "/zulu", then "/zulu/yankee", +// and finally "/zulu/yankee/xray". +// These tokens are reversed and concatenated to "/alpha/bravo/charlie" +// to form the full pointer. +type pointerSuffixError struct { + error + + // reversePointer is a JSON pointer, but with each token in reverse order. + reversePointer []byte +} + +// wrapWithObjectName wraps err with a JSON object name access, +// which must be a valid quoted JSON string. +func wrapWithObjectName(err error, quotedName []byte) error { + serr, _ := err.(*pointerSuffixError) + if serr == nil { + serr = &pointerSuffixError{error: err} + } + name := jsonwire.UnquoteMayCopy(quotedName, false) + serr.reversePointer = appendEscapePointerName(append(serr.reversePointer, '/'), name) + return serr +} + +// wrapWithArrayIndex wraps err with a JSON array index access. +func wrapWithArrayIndex(err error, index int64) error { + serr, _ := err.(*pointerSuffixError) + if serr == nil { + serr = &pointerSuffixError{error: err} + } + serr.reversePointer = strconv.AppendUint(append(serr.reversePointer, '/'), uint64(index), 10) + return serr +} + +// appendPointer appends the path encoded in e to the end of pointer. +func (e *pointerSuffixError) appendPointer(pointer []byte) []byte { + // Copy each token in reversePointer to the end of pointer in reverse order. + // Double reversal means that the appended suffix is now in forward order. + bi, bo := e.reversePointer, pointer + for len(bi) > 0 { + i := bytes.LastIndexByte(bi, '/') + bi, bo = bi[:i], append(bo, bi[i:]...) + } + return bo +} diff --git a/src/encoding/json/jsontext/example_test.go b/src/encoding/json/jsontext/example_test.go new file mode 100644 index 0000000000..4bf6a7ae5a --- /dev/null +++ b/src/encoding/json/jsontext/example_test.go @@ -0,0 +1,130 @@ +// 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 + +package jsontext_test + +import ( + "bytes" + "fmt" + "io" + "log" + "strings" + + "encoding/json/jsontext" + "encoding/json/v2" +) + +// This example demonstrates the use of the [Encoder] and [Decoder] to +// parse and modify JSON without unmarshaling it into a concrete Go type. +func Example_stringReplace() { + // Example input with non-idiomatic use of "Golang" instead of "Go". + const input = `{ + "title": "Golang version 1 is released", + "author": "Andrew Gerrand", + "date": "2012-03-28", + "text": "Today marks a major milestone in the development of the Golang programming language.", + "otherArticles": [ + "Twelve Years of Golang", + "The Laws of Reflection", + "Learn Golang from your browser" + ] + }` + + // Using a Decoder and Encoder, we can parse through every token, + // check and modify the token if necessary, and + // write the token to the output. + var replacements []jsontext.Pointer + in := strings.NewReader(input) + dec := jsontext.NewDecoder(in) + out := new(bytes.Buffer) + enc := jsontext.NewEncoder(out, jsontext.Multiline(true)) // expand for readability + for { + // Read a token from the input. + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + log.Fatal(err) + } + + // Check whether the token contains the string "Golang" and + // replace each occurrence with "Go" instead. + if tok.Kind() == '"' && strings.Contains(tok.String(), "Golang") { + replacements = append(replacements, dec.StackPointer()) + tok = jsontext.String(strings.ReplaceAll(tok.String(), "Golang", "Go")) + } + + // Write the (possibly modified) token to the output. + if err := enc.WriteToken(tok); err != nil { + log.Fatal(err) + } + } + + // Print the list of replacements and the adjusted JSON output. + if len(replacements) > 0 { + fmt.Println(`Replaced "Golang" with "Go" in:`) + for _, where := range replacements { + fmt.Println("\t" + where) + } + fmt.Println() + } + fmt.Println("Result:", out.String()) + + // Output: + // Replaced "Golang" with "Go" in: + // /title + // /text + // /otherArticles/0 + // /otherArticles/2 + // + // Result: { + // "title": "Go version 1 is released", + // "author": "Andrew Gerrand", + // "date": "2012-03-28", + // "text": "Today marks a major milestone in the development of the Go programming language.", + // "otherArticles": [ + // "Twelve Years of Go", + // "The Laws of Reflection", + // "Learn Go from your browser" + // ] + // } +} + +// Directly embedding JSON within HTML requires special handling for safety. +// Escape certain runes to prevent JSON directly treated as HTML +// from being able to perform `, + } + + b, err := json.Marshal(&page, + // Escape certain runes within a JSON string so that + // JSON will be safe to directly embed inside HTML. + jsontext.EscapeForHTML(true), + jsontext.EscapeForJS(true), + jsontext.Multiline(true)) // expand for readability + if err != nil { + log.Fatal(err) + } + fmt.Println(string(b)) + + // Output: + // { + // "Title": "Example Embedded Javascript", + // "Body": "\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e" + // } +} diff --git a/src/encoding/json/jsontext/export.go b/src/encoding/json/jsontext/export.go new file mode 100644 index 0000000000..0ecccad5b3 --- /dev/null +++ b/src/encoding/json/jsontext/export.go @@ -0,0 +1,77 @@ +// 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 + +package jsontext + +import ( + "io" + + "encoding/json/internal" +) + +// Internal is for internal use only. +// This is exempt from the Go compatibility agreement. +var Internal exporter + +type exporter struct{} + +// Export exposes internal functionality from "jsontext" to "json". +// This cannot be dynamically called by other packages since +// they cannot obtain a reference to the internal.AllowInternalUse value. +func (exporter) Export(p *internal.NotForPublicUse) export { + if p != &internal.AllowInternalUse { + panic("unauthorized call to Export") + } + return export{} +} + +// The export type exposes functionality to packages with visibility to +// the internal.AllowInternalUse variable. The "json" package uses this +// to modify low-level state in the Encoder and Decoder types. +// It mutates the state directly instead of calling ReadToken or WriteToken +// since this is more performant. The public APIs need to track state to ensure +// that users are constructing a valid JSON value, but the "json" implementation +// guarantees that it emits valid JSON by the structure of the code itself. +type export struct{} + +// Encoder returns a pointer to the underlying encoderState. +func (export) Encoder(e *Encoder) *encoderState { return &e.s } + +// Decoder returns a pointer to the underlying decoderState. +func (export) Decoder(d *Decoder) *decoderState { return &d.s } + +func (export) GetBufferedEncoder(o ...Options) *Encoder { + return getBufferedEncoder(o...) +} +func (export) PutBufferedEncoder(e *Encoder) { + putBufferedEncoder(e) +} + +func (export) GetStreamingEncoder(w io.Writer, o ...Options) *Encoder { + return getStreamingEncoder(w, o...) +} +func (export) PutStreamingEncoder(e *Encoder) { + putStreamingEncoder(e) +} + +func (export) GetBufferedDecoder(b []byte, o ...Options) *Decoder { + return getBufferedDecoder(b, o...) +} +func (export) PutBufferedDecoder(d *Decoder) { + putBufferedDecoder(d) +} + +func (export) GetStreamingDecoder(r io.Reader, o ...Options) *Decoder { + return getStreamingDecoder(r, o...) +} +func (export) PutStreamingDecoder(d *Decoder) { + putStreamingDecoder(d) +} + +func (export) IsIOError(err error) bool { + _, ok := err.(*ioError) + return ok +} diff --git a/src/encoding/json/jsontext/fuzz_test.go b/src/encoding/json/jsontext/fuzz_test.go new file mode 100644 index 0000000000..60d16b9e27 --- /dev/null +++ b/src/encoding/json/jsontext/fuzz_test.go @@ -0,0 +1,236 @@ +// 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 + +package jsontext + +import ( + "bytes" + "errors" + "io" + "math/rand" + "slices" + "testing" + + "encoding/json/internal/jsontest" +) + +func FuzzCoder(f *testing.F) { + // Add a number of inputs to the corpus including valid and invalid data. + for _, td := range coderTestdata { + f.Add(int64(0), []byte(td.in)) + } + for _, td := range decoderErrorTestdata { + f.Add(int64(0), []byte(td.in)) + } + for _, td := range encoderErrorTestdata { + f.Add(int64(0), []byte(td.wantOut)) + } + for _, td := range jsontest.Data { + f.Add(int64(0), td.Data()) + } + + f.Fuzz(func(t *testing.T, seed int64, b []byte) { + var tokVals []tokOrVal + rn := rand.NewSource(seed) + + // Read a sequence of tokens or values. Skip the test for any errors + // since we expect this with randomly generated fuzz inputs. + src := bytes.NewReader(b) + dec := NewDecoder(src) + for { + if rn.Int63()%8 > 0 { + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + t.Skipf("Decoder.ReadToken error: %v", err) + } + tokVals = append(tokVals, tok.Clone()) + } else { + val, err := dec.ReadValue() + if err != nil { + expectError := dec.PeekKind() == '}' || dec.PeekKind() == ']' + if expectError && errors.As(err, new(*SyntacticError)) { + continue + } + if err == io.EOF { + break + } + t.Skipf("Decoder.ReadValue error: %v", err) + } + tokVals = append(tokVals, append(zeroValue, val...)) + } + } + + // Write a sequence of tokens or values. Fail the test for any errors + // since the previous stage guarantees that the input is valid. + dst := new(bytes.Buffer) + enc := NewEncoder(dst) + for _, tokVal := range tokVals { + switch tokVal := tokVal.(type) { + case Token: + if err := enc.WriteToken(tokVal); err != nil { + t.Fatalf("Encoder.WriteToken error: %v", err) + } + case Value: + if err := enc.WriteValue(tokVal); err != nil { + t.Fatalf("Encoder.WriteValue error: %v", err) + } + } + } + + // Encoded output and original input must decode to the same thing. + var got, want []Token + for dec := NewDecoder(bytes.NewReader(b)); dec.PeekKind() > 0; { + tok, err := dec.ReadToken() + if err != nil { + t.Fatalf("Decoder.ReadToken error: %v", err) + } + got = append(got, tok.Clone()) + } + for dec := NewDecoder(dst); dec.PeekKind() > 0; { + tok, err := dec.ReadToken() + if err != nil { + t.Fatalf("Decoder.ReadToken error: %v", err) + } + want = append(want, tok.Clone()) + } + if !equalTokens(got, want) { + t.Fatalf("mismatching output:\ngot %v\nwant %v", got, want) + } + }) +} + +func FuzzResumableDecoder(f *testing.F) { + for _, td := range resumableDecoderTestdata { + f.Add(int64(0), []byte(td)) + } + + f.Fuzz(func(t *testing.T, seed int64, b []byte) { + rn := rand.NewSource(seed) + + // Regardless of how many bytes the underlying io.Reader produces, + // the provided tokens, values, and errors should always be identical. + t.Run("ReadToken", func(t *testing.T) { + decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn}) + decWant := NewDecoder(bytes.NewReader(b)) + gotTok, gotErr := decGot.ReadToken() + wantTok, wantErr := decWant.ReadToken() + if gotTok.String() != wantTok.String() || !equalError(gotErr, wantErr) { + t.Errorf("Decoder.ReadToken = (%v, %v), want (%v, %v)", gotTok, gotErr, wantTok, wantErr) + } + }) + t.Run("ReadValue", func(t *testing.T) { + decGot := NewDecoder(&FaultyBuffer{B: b, MaxBytes: 8, Rand: rn}) + decWant := NewDecoder(bytes.NewReader(b)) + gotVal, gotErr := decGot.ReadValue() + wantVal, wantErr := decWant.ReadValue() + if !slices.Equal(gotVal, wantVal) || !equalError(gotErr, wantErr) { + t.Errorf("Decoder.ReadValue = (%s, %v), want (%s, %v)", gotVal, gotErr, wantVal, wantErr) + } + }) + }) +} + +func FuzzValueFormat(f *testing.F) { + for _, td := range valueTestdata { + f.Add(int64(0), []byte(td.in)) + } + + // isValid reports whether b is valid according to the specified options. + isValid := func(b []byte, opts ...Options) bool { + d := NewDecoder(bytes.NewReader(b), opts...) + _, errVal := d.ReadValue() + _, errEOF := d.ReadToken() + return errVal == nil && errEOF == io.EOF + } + + // stripWhitespace removes all JSON whitespace characters from the input. + stripWhitespace := func(in []byte) (out []byte) { + out = make([]byte, 0, len(in)) + for _, c := range in { + switch c { + case ' ', '\n', '\r', '\t': + default: + out = append(out, c) + } + } + return out + } + + allOptions := []Options{ + AllowDuplicateNames(true), + AllowInvalidUTF8(true), + EscapeForHTML(true), + EscapeForJS(true), + PreserveRawStrings(true), + CanonicalizeRawInts(true), + CanonicalizeRawFloats(true), + ReorderRawObjects(true), + SpaceAfterColon(true), + SpaceAfterComma(true), + Multiline(true), + WithIndent("\t"), + WithIndentPrefix(" "), + } + + f.Fuzz(func(t *testing.T, seed int64, b []byte) { + validRFC7159 := isValid(b, AllowInvalidUTF8(true), AllowDuplicateNames(true)) + validRFC8259 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(true)) + validRFC7493 := isValid(b, AllowInvalidUTF8(false), AllowDuplicateNames(false)) + switch { + case !validRFC7159 && validRFC8259: + t.Errorf("invalid input per RFC 7159 implies invalid per RFC 8259") + case !validRFC8259 && validRFC7493: + t.Errorf("invalid input per RFC 8259 implies invalid per RFC 7493") + } + + gotValid := Value(b).IsValid() + wantValid := validRFC7493 + if gotValid != wantValid { + t.Errorf("Value.IsValid = %v, want %v", gotValid, wantValid) + } + + gotCompacted := Value(string(b)) + gotCompactOk := gotCompacted.Compact() == nil + wantCompactOk := validRFC7159 + if !bytes.Equal(stripWhitespace(gotCompacted), stripWhitespace(b)) { + t.Errorf("stripWhitespace(Value.Compact) = %s, want %s", stripWhitespace(gotCompacted), stripWhitespace(b)) + } + if gotCompactOk != wantCompactOk { + t.Errorf("Value.Compact success mismatch: got %v, want %v", gotCompactOk, wantCompactOk) + } + + gotIndented := Value(string(b)) + gotIndentOk := gotIndented.Indent() == nil + wantIndentOk := validRFC7159 + if !bytes.Equal(stripWhitespace(gotIndented), stripWhitespace(b)) { + t.Errorf("stripWhitespace(Value.Indent) = %s, want %s", stripWhitespace(gotIndented), stripWhitespace(b)) + } + if gotIndentOk != wantIndentOk { + t.Errorf("Value.Indent success mismatch: got %v, want %v", gotIndentOk, wantIndentOk) + } + + gotCanonicalized := Value(string(b)) + gotCanonicalizeOk := gotCanonicalized.Canonicalize() == nil + wantCanonicalizeOk := validRFC7493 + if gotCanonicalizeOk != wantCanonicalizeOk { + t.Errorf("Value.Canonicalize success mismatch: got %v, want %v", gotCanonicalizeOk, wantCanonicalizeOk) + } + + // Random options should not result in a panic. + var opts []Options + rn := rand.New(rand.NewSource(seed)) + for _, opt := range allOptions { + if rn.Intn(len(allOptions)/4) == 0 { + opts = append(opts, opt) + } + } + v := Value(b) + v.Format(opts...) // should not panic + }) +} diff --git a/src/encoding/json/jsontext/options.go b/src/encoding/json/jsontext/options.go new file mode 100644 index 0000000000..e07de21fcf --- /dev/null +++ b/src/encoding/json/jsontext/options.go @@ -0,0 +1,303 @@ +// 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 + +package jsontext + +import ( + "strings" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" +) + +// 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. +// +// There is a single Options type, which is used with both encoding and decoding. +// Some options affect both operations, while others only affect one operation: +// +// - [AllowDuplicateNames] affects encoding and decoding +// - [AllowInvalidUTF8] affects encoding and decoding +// - [EscapeForHTML] affects encoding only +// - [EscapeForJS] affects encoding only +// - [PreserveRawStrings] affects encoding only +// - [CanonicalizeRawInts] affects encoding only +// - [CanonicalizeRawFloats] affects encoding only +// - [ReorderRawObjects] affects encoding only +// - [SpaceAfterColon] affects encoding only +// - [SpaceAfterComma] affects encoding only +// - [Multiline] affects encoding only +// - [WithIndent] affects encoding only +// - [WithIndentPrefix] affects encoding only +// +// Options that do not affect a particular operation are ignored. +// +// The Options type is identical to [encoding/json.Options] and +// [encoding/json/v2.Options]. Options from the other packages may +// be passed to functionality in this package, but are ignored. +// Options from this package may be used with the other packages. +type Options = jsonopts.Options + +// AllowDuplicateNames specifies that JSON objects may contain +// duplicate member names. Disabling the duplicate name check may provide +// performance benefits, but breaks compliance with RFC 7493, section 2.3. +// The input or output will still be compliant with RFC 8259, +// which leaves the handling of duplicate names as unspecified behavior. +// +// This affects either encoding or decoding. +func AllowDuplicateNames(v bool) Options { + if v { + return jsonflags.AllowDuplicateNames | 1 + } else { + return jsonflags.AllowDuplicateNames | 0 + } +} + +// AllowInvalidUTF8 specifies that JSON strings may contain invalid UTF-8, +// which will be mangled as the Unicode replacement character, U+FFFD. +// This causes the encoder or decoder to break compliance with +// RFC 7493, section 2.1, and RFC 8259, section 8.1. +// +// This affects either encoding or decoding. +func AllowInvalidUTF8(v bool) Options { + if v { + return jsonflags.AllowInvalidUTF8 | 1 + } else { + return jsonflags.AllowInvalidUTF8 | 0 + } +} + +// EscapeForHTML specifies that '<', '>', and '&' characters within JSON strings +// should be escaped as a hexadecimal Unicode codepoint (e.g., \u003c) so that +// the output is safe to embed within HTML. +// +// This only affects encoding and is ignored when decoding. +func EscapeForHTML(v bool) Options { + if v { + return jsonflags.EscapeForHTML | 1 + } else { + return jsonflags.EscapeForHTML | 0 + } +} + +// EscapeForJS specifies that U+2028 and U+2029 characters within JSON strings +// should be escaped as a hexadecimal Unicode codepoint (e.g., \u2028) so that +// the output is valid to embed within JavaScript. See RFC 8259, section 12. +// +// This only affects encoding and is ignored when decoding. +func EscapeForJS(v bool) Options { + if v { + return jsonflags.EscapeForJS | 1 + } else { + return jsonflags.EscapeForJS | 0 + } +} + +// PreserveRawStrings specifies that when encoding a raw JSON string in a +// [Token] or [Value], pre-escaped sequences +// in a JSON string are preserved to the output. +// However, raw strings still respect [EscapeForHTML] and [EscapeForJS] +// such that the relevant characters are escaped. +// If [AllowInvalidUTF8] is enabled, bytes of invalid UTF-8 +// are preserved to the output. +// +// This only affects encoding and is ignored when decoding. +func PreserveRawStrings(v bool) Options { + if v { + return jsonflags.PreserveRawStrings | 1 + } else { + return jsonflags.PreserveRawStrings | 0 + } +} + +// CanonicalizeRawInts specifies that when encoding a raw JSON +// integer number (i.e., a number without a fraction and exponent) in a +// [Token] or [Value], the number is canonicalized +// according to RFC 8785, section 3.2.2.3. As a special case, +// the number -0 is canonicalized as 0. +// +// JSON numbers are treated as IEEE 754 double precision numbers. +// Any numbers with precision beyond what is representable by that form +// will lose their precision when canonicalized. For example, +// integer values beyond ±2⁵³ will lose their precision. +// For example, 1234567890123456789 is formatted as 1234567890123456800. +// +// This only affects encoding and is ignored when decoding. +func CanonicalizeRawInts(v bool) Options { + if v { + return jsonflags.CanonicalizeRawInts | 1 + } else { + return jsonflags.CanonicalizeRawInts | 0 + } +} + +// CanonicalizeRawFloats specifies that when encoding a raw JSON +// floating-point number (i.e., a number with a fraction or exponent) in a +// [Token] or [Value], the number is canonicalized +// according to RFC 8785, section 3.2.2.3. As a special case, +// the number -0 is canonicalized as 0. +// +// JSON numbers are treated as IEEE 754 double precision numbers. +// It is safe to canonicalize a serialized single precision number and +// parse it back as a single precision number and expect the same value. +// If a number exceeds ±1.7976931348623157e+308, which is the maximum +// finite number, then it saturated at that value and formatted as such. +// +// This only affects encoding and is ignored when decoding. +func CanonicalizeRawFloats(v bool) Options { + if v { + return jsonflags.CanonicalizeRawFloats | 1 + } else { + return jsonflags.CanonicalizeRawFloats | 0 + } +} + +// ReorderRawObjects specifies that when encoding a raw JSON object in a +// [Value], the object members are reordered according to +// RFC 8785, section 3.2.3. +// +// This only affects encoding and is ignored when decoding. +func ReorderRawObjects(v bool) Options { + if v { + return jsonflags.ReorderRawObjects | 1 + } else { + return jsonflags.ReorderRawObjects | 0 + } +} + +// SpaceAfterColon specifies that the JSON output should emit a space character +// after each colon separator following a JSON object name. +// If false, then no space character appears after the colon separator. +// +// This only affects encoding and is ignored when decoding. +func SpaceAfterColon(v bool) Options { + if v { + return jsonflags.SpaceAfterColon | 1 + } else { + return jsonflags.SpaceAfterColon | 0 + } +} + +// SpaceAfterComma specifies that the JSON output should emit a space character +// after each comma separator following a JSON object value or array element. +// If false, then no space character appears after the comma separator. +// +// This only affects encoding and is ignored when decoding. +func SpaceAfterComma(v bool) Options { + if v { + return jsonflags.SpaceAfterComma | 1 + } else { + return jsonflags.SpaceAfterComma | 0 + } +} + +// Multiline specifies that the JSON output should expand to multiple lines, +// where every JSON object member or JSON array element appears on +// a new, indented line according to the nesting depth. +// +// If [SpaceAfterColon] is not specified, then the default is true. +// If [SpaceAfterComma] is not specified, then the default is false. +// If [WithIndent] is not specified, then the default is "\t". +// +// If set to false, then the output is a single-line, +// where the only whitespace emitted is determined by the current +// values of [SpaceAfterColon] and [SpaceAfterComma]. +// +// This only affects encoding and is ignored when decoding. +func Multiline(v bool) Options { + if v { + return jsonflags.Multiline | 1 + } else { + return jsonflags.Multiline | 0 + } +} + +// WithIndent specifies that the encoder should emit multiline output +// 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. +// +// If the intent 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. +// Use of this option implies [Multiline] being set to true. +func WithIndent(indent string) Options { + // Fast-path: Return a constant for common indents, which avoids allocating. + // These are derived from analyzing the Go module proxy on 2023-07-01. + switch indent { + case "\t": + return jsonopts.Indent("\t") // ~14k usages + case " ": + return jsonopts.Indent(" ") // ~18k usages + case " ": + return jsonopts.Indent(" ") // ~1.7k usages + case " ": + return jsonopts.Indent(" ") // ~52k usages + case " ": + return jsonopts.Indent(" ") // ~12k usages + case "": + return jsonopts.Indent("") // ~1.5k usages + } + + // Otherwise, allocate for this unique value. + if s := strings.Trim(indent, " \t"); len(s) > 0 { + panic("json: invalid character " + jsonwire.QuoteRune(s) + " in indent") + } + return jsonopts.Indent(indent) +} + +// WithIndentPrefix specifies that the encoder should emit multiline output +// 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. +// +// 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") + } + return jsonopts.IndentPrefix(prefix) +} + +/* +// TODO(https://go.dev/issue/56733): Implement WithByteLimit and WithDepthLimit. + +// WithByteLimit sets a limit on the number of bytes of input or output bytes +// that may be consumed or produced for each top-level JSON value. +// If a [Decoder] or [Encoder] method call would need to consume/produce +// more than a total of n bytes to make progress on the top-level JSON value, +// then the call will report an error. +// Whitespace before and within the top-level value are counted against the limit. +// Whitespace after a top-level value are counted against the limit +// for the next top-level value. +// +// A non-positive limit is equivalent to no limit at all. +// If unspecified, the default limit is no limit at all. +// This affects either encoding or decoding. +func WithByteLimit(n int64) Options { + return jsonopts.ByteLimit(max(n, 0)) +} + +// WithDepthLimit sets a limit on the maximum depth of JSON nesting +// that may be consumed or produced for each top-level JSON value. +// If a [Decoder] or [Encoder] method call would need to consume or produce +// a depth greater than n to make progress on the top-level JSON value, +// then the call will report an error. +// +// A non-positive limit is equivalent to no limit at all. +// If unspecified, the default limit is 10000. +// This affects either encoding or decoding. +func WithDepthLimit(n int) Options { + return jsonopts.DepthLimit(max(n, 0)) +} +*/ diff --git a/src/encoding/json/jsontext/pools.go b/src/encoding/json/jsontext/pools.go new file mode 100644 index 0000000000..4f9e0ea410 --- /dev/null +++ b/src/encoding/json/jsontext/pools.go @@ -0,0 +1,152 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "io" + "math/bits" + "sync" +) + +// TODO(https://go.dev/issue/47657): Use sync.PoolOf. + +var ( + // This owns the internal buffer since there is no io.Writer to output to. + // Since the buffer can get arbitrarily large in normal usage, + // there is statistical tracking logic to determine whether to recycle + // the internal buffer or not based on a history of utilization. + bufferedEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }} + + // This owns the internal buffer, but it is only used to temporarily store + // buffered JSON before flushing it to the underlying io.Writer. + // In a sufficiently efficient streaming mode, we do not expect the buffer + // to grow arbitrarily large. Thus, we avoid recycling large buffers. + streamingEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }} + + // This does not own the internal buffer since + // it is taken directly from the provided bytes.Buffer. + bytesBufferEncoderPool = &sync.Pool{New: func() any { return new(Encoder) }} +) + +// bufferStatistics is statistics to track buffer utilization. +// It is used to determine whether to recycle a buffer or not +// to avoid https://go.dev/issue/23199. +type bufferStatistics struct { + strikes int // number of times the buffer was under-utilized + prevLen int // length of previous buffer +} + +func getBufferedEncoder(opts ...Options) *Encoder { + e := bufferedEncoderPool.Get().(*Encoder) + if e.s.Buf == nil { + // Round up to nearest 2ⁿ to make best use of malloc size classes. + // See runtime/sizeclasses.go on Go1.15. + // Logical OR with 63 to ensure 64 as the minimum buffer size. + n := 1 << bits.Len(uint(e.s.bufStats.prevLen|63)) + e.s.Buf = make([]byte, 0, n) + } + e.s.reset(e.s.Buf[:0], nil, opts...) + return e +} +func putBufferedEncoder(e *Encoder) { + // 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 + // won't be kept alive by a continuous stream of small usages. + // + // The worst case utilization is computed as: + // MIN_UTILIZATION_THRESHOLD / (1 + MAX_NUM_STRIKES) + // + // For the constants chosen below, this is (25%)/(1+4) ⇒ 5%. + // This may seem low, but it ensures a lower bound on + // the absolute worst-case utilization. Without this check, + // this would be theoretically 0%, which is infinitely worse. + // + // See https://go.dev/issue/27735. + switch { + case cap(e.s.Buf) <= 4<<10: // always recycle buffers smaller than 4KiB + e.s.bufStats.strikes = 0 + case cap(e.s.Buf)/4 <= len(e.s.Buf): // at least 25% utilization + e.s.bufStats.strikes = 0 + case e.s.bufStats.strikes < 4: // at most 4 strikes + e.s.bufStats.strikes++ + default: // discard the buffer; too large and too often under-utilized + e.s.bufStats.strikes = 0 + e.s.bufStats.prevLen = len(e.s.Buf) // heuristic for size to allocate next time + e.s.Buf = nil + } + bufferedEncoderPool.Put(e) +} + +func getStreamingEncoder(w io.Writer, opts ...Options) *Encoder { + if _, ok := w.(*bytes.Buffer); ok { + e := bytesBufferEncoderPool.Get().(*Encoder) + e.s.reset(nil, w, opts...) // buffer taken from bytes.Buffer + return e + } else { + e := streamingEncoderPool.Get().(*Encoder) + e.s.reset(e.s.Buf[:0], w, opts...) // preserve existing buffer + return e + } +} +func putStreamingEncoder(e *Encoder) { + if _, ok := e.s.wr.(*bytes.Buffer); ok { + bytesBufferEncoderPool.Put(e) + } else { + if cap(e.s.Buf) > 64<<10 { + e.s.Buf = nil // avoid pinning arbitrarily large amounts of memory + } + streamingEncoderPool.Put(e) + } +} + +var ( + // This does not own the internal buffer since it is externally provided. + bufferedDecoderPool = &sync.Pool{New: func() any { return new(Decoder) }} + + // This owns the internal buffer, but it is only used to temporarily store + // buffered JSON fetched from the underlying io.Reader. + // In a sufficiently efficient streaming mode, we do not expect the buffer + // to grow arbitrarily large. Thus, we avoid recycling large buffers. + streamingDecoderPool = &sync.Pool{New: func() any { return new(Decoder) }} + + // This does not own the internal buffer since + // it is taken directly from the provided bytes.Buffer. + bytesBufferDecoderPool = bufferedDecoderPool +) + +func getBufferedDecoder(b []byte, opts ...Options) *Decoder { + d := bufferedDecoderPool.Get().(*Decoder) + d.s.reset(b, nil, opts...) + return d +} +func putBufferedDecoder(d *Decoder) { + bufferedDecoderPool.Put(d) +} + +func getStreamingDecoder(r io.Reader, opts ...Options) *Decoder { + if _, ok := r.(*bytes.Buffer); ok { + d := bytesBufferDecoderPool.Get().(*Decoder) + d.s.reset(nil, r, opts...) // buffer taken from bytes.Buffer + return d + } else { + d := streamingDecoderPool.Get().(*Decoder) + d.s.reset(d.s.buf[:0], r, opts...) // preserve existing buffer + return d + } +} +func putStreamingDecoder(d *Decoder) { + if _, ok := d.s.rd.(*bytes.Buffer); ok { + bytesBufferDecoderPool.Put(d) + } else { + if cap(d.s.buf) > 64<<10 { + d.s.buf = nil // avoid pinning arbitrarily large amounts of memory + } + streamingDecoderPool.Put(d) + } +} diff --git a/src/encoding/json/jsontext/quote.go b/src/encoding/json/jsontext/quote.go new file mode 100644 index 0000000000..5ecfdbc211 --- /dev/null +++ b/src/encoding/json/jsontext/quote.go @@ -0,0 +1,41 @@ +// 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 + +package jsontext + +import ( + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonwire" +) + +// AppendQuote appends a double-quoted JSON string literal representing src +// to dst and returns the extended buffer. +// It uses the minimal string representation per RFC 8785, section 3.2.2.2. +// Invalid UTF-8 bytes are replaced with the Unicode replacement character +// and an error is returned at the end indicating the presence of invalid UTF-8. +// The dst must not overlap with the src. +func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) { + dst, err := jsonwire.AppendQuote(dst, src, &jsonflags.Flags{}) + if err != nil { + err = &SyntacticError{Err: err} + } + return dst, err +} + +// AppendUnquote appends the decoded interpretation of src as a +// double-quoted JSON string literal to dst and returns the extended buffer. +// The input src must be a JSON string without any surrounding whitespace. +// Invalid UTF-8 bytes are replaced with the Unicode replacement character +// and an error is returned at the end indicating the presence of invalid UTF-8. +// Any trailing bytes after the JSON string literal results in an error. +// The dst must not overlap with the src. +func AppendUnquote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error) { + dst, err := jsonwire.AppendUnquote(dst, src) + if err != nil { + err = &SyntacticError{Err: err} + } + return dst, err +} diff --git a/src/encoding/json/jsontext/state.go b/src/encoding/json/jsontext/state.go new file mode 100644 index 0000000000..1e8b4f22db --- /dev/null +++ b/src/encoding/json/jsontext/state.go @@ -0,0 +1,828 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "errors" + "iter" + "math" + "strconv" + "strings" + "unicode/utf8" + + "encoding/json/internal/jsonwire" +) + +// ErrDuplicateName indicates that a JSON token could not be +// encoded or decoded because it results in a duplicate JSON object name. +// This error is directly wrapped within a [SyntacticError] when produced. +// +// The name of a duplicate JSON object member can be extracted as: +// +// err := ... +// var serr jsontext.SyntacticError +// if errors.As(err, &serr) && serr.Err == jsontext.ErrDuplicateName { +// ptr := serr.JSONPointer // JSON pointer to duplicate name +// name := ptr.LastToken() // duplicate name itself +// ... +// } +// +// This error is only returned if [AllowDuplicateNames] is false. +var ErrDuplicateName = errors.New("duplicate object member name") + +// ErrNonStringName indicates that a JSON token could not be +// encoded or decoded because it is not a string, +// as required for JSON object names according to RFC 8259, section 4. +// This error is directly wrapped within a [SyntacticError] when produced. +var ErrNonStringName = errors.New("object member name must be a string") + +var ( + errMissingValue = errors.New("missing value after object name") + errMismatchDelim = errors.New("mismatching structural token for object or array") + errMaxDepth = errors.New("exceeded max depth") + + errInvalidNamespace = errors.New("object namespace is in an invalid state") +) + +// Per RFC 8259, section 9, implementations may enforce a maximum depth. +// Such a limit is necessary to prevent stack overflows. +const maxNestingDepth = 10000 + +type state struct { + // Tokens validates whether the next token kind is valid. + Tokens stateMachine + + // Names is a stack of object names. + Names objectNameStack + + // Namespaces is a stack of object namespaces. + // For performance reasons, Encoder or Decoder may not update this + // if Marshal or Unmarshal is able to track names in a more efficient way. + // See makeMapArshaler and makeStructArshaler. + // Not used if AllowDuplicateNames is true. + Namespaces objectNamespaceStack +} + +// needObjectValue reports whether the next token should be an object value. +// This method is used by [wrapSyntacticError]. +func (s *state) needObjectValue() bool { + return s.Tokens.Last.needObjectValue() +} + +func (s *state) reset() { + s.Tokens.reset() + s.Names.reset() + s.Namespaces.reset() +} + +// Pointer is a JSON Pointer (RFC 6901) that references a particular JSON value +// relative to the root of the top-level JSON value. +// +// A Pointer is a slash-separated list of tokens, where each token is +// either a JSON object name or an index to a JSON array element +// encoded as a base-10 integer value. +// It is impossible to distinguish between an array index and an object name +// (that happens to be an base-10 encoded integer) without also knowing +// the structure of the top-level JSON value that the pointer refers to. +// +// There is exactly one representation of a pointer to a particular value, +// so comparability of Pointer values is equivalent to checking whether +// they both point to the exact same value. +type Pointer string + +// IsValid reports whether p is a valid JSON Pointer according to RFC 6901. +// Note that the concatenation of two valid pointers produces a valid pointer. +func (p Pointer) IsValid() bool { + for i, r := range p { + switch { + case r == '~' && (i+1 == len(p) || (p[i+1] != '0' && p[i+1] != '1')): + return false // invalid escape + case r == '\ufffd' && !strings.HasPrefix(string(p[i:]), "\ufffd"): + return false // invalid UTF-8 + } + } + return len(p) == 0 || p[0] == '/' +} + +// Contains reports whether the JSON value that p points to +// is equal to or contains the JSON value that pc points to. +func (p Pointer) Contains(pc Pointer) bool { + // Invariant: len(p) <= len(pc) if p.Contains(pc) + suffix, ok := strings.CutPrefix(string(pc), string(p)) + return ok && (suffix == "" || suffix[0] == '/') +} + +// Parent strips off the last token and returns the remaining pointer. +// The parent of an empty p is an empty string. +func (p Pointer) Parent() Pointer { + return p[:max(strings.LastIndexByte(string(p), '/'), 0)] +} + +// LastToken returns the last token in the pointer. +// The last token of an empty p is an empty string. +func (p Pointer) LastToken() string { + last := p[max(strings.LastIndexByte(string(p), '/'), 0):] + return unescapePointerToken(strings.TrimPrefix(string(last), "/")) +} + +// AppendToken appends a token to the end of p and returns the full pointer. +func (p Pointer) AppendToken(tok string) Pointer { + return Pointer(appendEscapePointerName([]byte(p+"/"), tok)) +} + +// TODO: Add Pointer.AppendTokens, +// but should this take in a ...string or an iter.Seq[string]? + +// Tokens returns an iterator over the reference tokens in the JSON pointer, +// starting from the first token until the last token (unless stopped early). +func (p Pointer) Tokens() iter.Seq[string] { + return func(yield func(string) bool) { + for len(p) > 0 { + p = Pointer(strings.TrimPrefix(string(p), "/")) + i := min(uint(strings.IndexByte(string(p), '/')), uint(len(p))) + if !yield(unescapePointerToken(string(p)[:i])) { + return + } + p = p[i:] + } + } +} + +func unescapePointerToken(token string) string { + if strings.Contains(token, "~") { + // Per RFC 6901, section 3, unescape '~' and '/' characters. + token = strings.ReplaceAll(token, "~1", "/") + token = strings.ReplaceAll(token, "~0", "~") + } + return token +} + +// appendStackPointer appends a JSON Pointer (RFC 6901) to the current value. +// +// - If where is -1, then it points to the previously processed token. +// +// - If where is 0, then it points to the parent JSON object or array, +// or an object member if in-between an object member key and value. +// This is useful when the position is ambiguous whether +// we are interested in the previous or next token, or +// when we are uncertain whether the next token +// continues or terminates the current object or array. +// +// - If where is +1, then it points to the next expected value, +// assuming that it continues the current JSON object or array. +// As a special case, if the next token is a JSON object name, +// then it points to the parent JSON object. +// +// Invariant: Must call s.names.copyQuotedBuffer beforehand. +func (s state) appendStackPointer(b []byte, where int) []byte { + var objectDepth int + for i := 1; i < s.Tokens.Depth(); i++ { + e := s.Tokens.index(i) + arrayDelta := -1 // by default point to previous array element + if isLast := i == s.Tokens.Depth()-1; isLast { + switch { + case where < 0 && e.Length() == 0 || where == 0 && !e.needObjectValue() || where > 0 && e.NeedObjectName(): + return b + case where > 0 && e.isArray(): + arrayDelta = 0 // point to next array element + } + } + switch { + case e.isObject(): + b = appendEscapePointerName(append(b, '/'), s.Names.getUnquoted(objectDepth)) + objectDepth++ + case e.isArray(): + b = strconv.AppendUint(append(b, '/'), uint64(e.Length()+int64(arrayDelta)), 10) + } + } + return b +} + +func appendEscapePointerName[Bytes ~[]byte | ~string](b []byte, name Bytes) []byte { + for _, r := range string(name) { + // Per RFC 6901, section 3, escape '~' and '/' characters. + switch r { + case '~': + b = append(b, "~0"...) + case '/': + b = append(b, "~1"...) + default: + b = utf8.AppendRune(b, r) + } + } + return b +} + +// stateMachine is a push-down automaton that validates whether +// a sequence of tokens is valid or not according to the JSON grammar. +// It is useful for both encoding and decoding. +// +// It is a stack where each entry represents a nested JSON object or array. +// The stack has a minimum depth of 1 where the first level is a +// virtual JSON array to handle a stream of top-level JSON values. +// The top-level virtual JSON array is special in that it doesn't require commas +// between each JSON value. +// +// For performance, most methods are carefully written to be inlinable. +// The zero value is a valid state machine ready for use. +type stateMachine struct { + Stack []stateEntry + Last stateEntry +} + +// reset resets the state machine. +// The machine always starts with a minimum depth of 1. +func (m *stateMachine) reset() { + m.Stack = m.Stack[:0] + if cap(m.Stack) > 1<<10 { + m.Stack = nil + } + m.Last = stateTypeArray +} + +// Depth is the current nested depth of JSON objects and arrays. +// It is one-indexed (i.e., top-level values have a depth of 1). +func (m stateMachine) Depth() int { + return len(m.Stack) + 1 +} + +// index returns a reference to the ith entry. +// It is only valid until the next push method call. +func (m *stateMachine) index(i int) *stateEntry { + if i == len(m.Stack) { + return &m.Last + } + return &m.Stack[i] +} + +// DepthLength reports the current nested depth and +// the length of the last JSON object or array. +func (m stateMachine) DepthLength() (int, int64) { + return m.Depth(), m.Last.Length() +} + +// appendLiteral appends a JSON literal as the next token in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) appendLiteral() error { + switch { + case m.Last.NeedObjectName(): + return ErrNonStringName + case !m.Last.isValidNamespace(): + return errInvalidNamespace + default: + m.Last.Increment() + return nil + } +} + +// appendString appends a JSON string as the next token in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) appendString() error { + switch { + case !m.Last.isValidNamespace(): + return errInvalidNamespace + default: + m.Last.Increment() + return nil + } +} + +// appendNumber appends a JSON number as the next token in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) appendNumber() error { + return m.appendLiteral() +} + +// pushObject appends a JSON start object token as next in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) pushObject() error { + switch { + case m.Last.NeedObjectName(): + return ErrNonStringName + case !m.Last.isValidNamespace(): + return errInvalidNamespace + case len(m.Stack) == maxNestingDepth: + return errMaxDepth + default: + m.Last.Increment() + m.Stack = append(m.Stack, m.Last) + m.Last = stateTypeObject + return nil + } +} + +// popObject appends a JSON end object token as next in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) popObject() error { + switch { + case !m.Last.isObject(): + return errMismatchDelim + case m.Last.needObjectValue(): + return errMissingValue + case !m.Last.isValidNamespace(): + return errInvalidNamespace + default: + m.Last = m.Stack[len(m.Stack)-1] + m.Stack = m.Stack[:len(m.Stack)-1] + return nil + } +} + +// pushArray appends a JSON start array token as next in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) pushArray() error { + switch { + case m.Last.NeedObjectName(): + return ErrNonStringName + case !m.Last.isValidNamespace(): + return errInvalidNamespace + case len(m.Stack) == maxNestingDepth: + return errMaxDepth + default: + m.Last.Increment() + m.Stack = append(m.Stack, m.Last) + m.Last = stateTypeArray + return nil + } +} + +// popArray appends a JSON end array token as next in the sequence. +// If an error is returned, the state is not mutated. +func (m *stateMachine) popArray() error { + switch { + case !m.Last.isArray() || len(m.Stack) == 0: // forbid popping top-level virtual JSON array + return errMismatchDelim + case !m.Last.isValidNamespace(): + return errInvalidNamespace + default: + m.Last = m.Stack[len(m.Stack)-1] + m.Stack = m.Stack[:len(m.Stack)-1] + return nil + } +} + +// NeedIndent reports whether indent whitespace should be injected. +// A zero value means that no whitespace should be injected. +// A positive value means '\n', indentPrefix, and (n-1) copies of indentBody +// should be appended to the output immediately before the next token. +func (m stateMachine) NeedIndent(next Kind) (n int) { + willEnd := next == '}' || next == ']' + switch { + case m.Depth() == 1: + return 0 // top-level values are never indented + case m.Last.Length() == 0 && willEnd: + return 0 // an empty object or array is never indented + case m.Last.Length() == 0 || m.Last.needImplicitComma(next): + return m.Depth() + case willEnd: + return m.Depth() - 1 + default: + return 0 + } +} + +// MayAppendDelim appends a colon or comma that may precede the next token. +func (m stateMachine) MayAppendDelim(b []byte, next Kind) []byte { + switch { + case m.Last.needImplicitColon(): + return append(b, ':') + case m.Last.needImplicitComma(next) && len(m.Stack) != 0: // comma not needed for top-level values + return append(b, ',') + default: + return b + } +} + +// needDelim reports whether a colon or comma token should be implicitly emitted +// before the next token of the specified kind. +// A zero value means no delimiter should be emitted. +func (m stateMachine) needDelim(next Kind) (delim byte) { + switch { + case m.Last.needImplicitColon(): + return ':' + case m.Last.needImplicitComma(next) && len(m.Stack) != 0: // comma not needed for top-level values + return ',' + default: + return 0 + } +} + +// InvalidateDisabledNamespaces marks all disabled namespaces as invalid. +// +// For efficiency, Marshal and Unmarshal may disable namespaces since there are +// more efficient ways to track duplicate names. However, if an error occurs, +// the namespaces in Encoder or Decoder will be left in an inconsistent state. +// Mark the namespaces as invalid so that future method calls on +// Encoder or Decoder will return an error. +func (m *stateMachine) InvalidateDisabledNamespaces() { + for i := range m.Depth() { + e := m.index(i) + if !e.isActiveNamespace() { + e.invalidateNamespace() + } + } +} + +// stateEntry encodes several artifacts within a single unsigned integer: +// - whether this represents a JSON object or array, +// - whether this object should check for duplicate names, and +// - how many elements are in this JSON object or array. +type stateEntry uint64 + +const ( + // The type mask (1 bit) records whether this is a JSON object or array. + stateTypeMask stateEntry = 0x8000_0000_0000_0000 + stateTypeObject stateEntry = 0x8000_0000_0000_0000 + stateTypeArray stateEntry = 0x0000_0000_0000_0000 + + // The name check mask (2 bit) records whether to update + // the namespaces for the current JSON object and + // whether the namespace is valid. + stateNamespaceMask stateEntry = 0x6000_0000_0000_0000 + stateDisableNamespace stateEntry = 0x4000_0000_0000_0000 + stateInvalidNamespace stateEntry = 0x2000_0000_0000_0000 + + // The count mask (61 bits) records the number of elements. + stateCountMask stateEntry = 0x1fff_ffff_ffff_ffff + stateCountLSBMask stateEntry = 0x0000_0000_0000_0001 + stateCountOdd stateEntry = 0x0000_0000_0000_0001 + stateCountEven stateEntry = 0x0000_0000_0000_0000 +) + +// Length reports the number of elements in the JSON object or array. +// Each name and value in an object entry is treated as a separate element. +func (e stateEntry) Length() int64 { + return int64(e & stateCountMask) +} + +// isObject reports whether this is a JSON object. +func (e stateEntry) isObject() bool { + return e&stateTypeMask == stateTypeObject +} + +// isArray reports whether this is a JSON array. +func (e stateEntry) isArray() bool { + return e&stateTypeMask == stateTypeArray +} + +// NeedObjectName reports whether the next token must be a JSON string, +// which is necessary for JSON object names. +func (e stateEntry) NeedObjectName() bool { + return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountEven +} + +// needImplicitColon reports whether an colon should occur next, +// which always occurs after JSON object names. +func (e stateEntry) needImplicitColon() bool { + return e.needObjectValue() +} + +// needObjectValue reports whether the next token must be a JSON value, +// which is necessary after every JSON object name. +func (e stateEntry) needObjectValue() bool { + return e&(stateTypeMask|stateCountLSBMask) == stateTypeObject|stateCountOdd +} + +// needImplicitComma reports whether an 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 { + return !e.needObjectValue() && e.Length() > 0 && next != '}' && next != ']' +} + +// Increment increments the number of elements for the current object or array. +// This assumes that overflow won't practically be an issue since +// 1< 0. +func (e *stateEntry) decrement() { + (*e)-- +} + +// DisableNamespace disables the JSON object namespace such that the +// Encoder or Decoder no longer updates the namespace. +func (e *stateEntry) DisableNamespace() { + *e |= stateDisableNamespace +} + +// isActiveNamespace reports whether the JSON object namespace is actively +// being updated and used for duplicate name checks. +func (e stateEntry) isActiveNamespace() bool { + return e&(stateDisableNamespace) == 0 +} + +// invalidateNamespace marks the JSON object namespace as being invalid. +func (e *stateEntry) invalidateNamespace() { + *e |= stateInvalidNamespace +} + +// isValidNamespace reports whether the JSON object namespace is valid. +func (e stateEntry) isValidNamespace() bool { + return e&(stateInvalidNamespace) == 0 +} + +// objectNameStack is a stack of names when descending into a JSON object. +// In contrast to objectNamespaceStack, this only has to remember a single name +// per JSON object. +// +// This data structure may contain offsets to encodeBuffer or decodeBuffer. +// It violates clean abstraction of layers, but is significantly more efficient. +// This ensures that popping and pushing in the common case is a trivial +// push/pop of an offset integer. +// +// The zero value is an empty names stack ready for use. +type objectNameStack struct { + // offsets is a stack of offsets for each name. + // A non-negative offset is the ending offset into the local names buffer. + // A negative offset is the bit-wise inverse of a starting offset into + // a remote buffer (e.g., encodeBuffer or decodeBuffer). + // A math.MinInt offset at the end implies that the last object is empty. + // Invariant: Positive offsets always occur before negative offsets. + offsets []int + // unquotedNames is a back-to-back concatenation of names. + unquotedNames []byte +} + +func (ns *objectNameStack) reset() { + ns.offsets = ns.offsets[:0] + ns.unquotedNames = ns.unquotedNames[:0] + if cap(ns.offsets) > 1<<6 { + ns.offsets = nil // avoid pinning arbitrarily large amounts of memory + } + if cap(ns.unquotedNames) > 1<<10 { + ns.unquotedNames = nil // avoid pinning arbitrarily large amounts of memory + } +} + +func (ns *objectNameStack) length() int { + return len(ns.offsets) +} + +// getUnquoted retrieves the ith unquoted name in the stack. +// It returns an empty string if the last object is empty. +// +// Invariant: Must call copyQuotedBuffer beforehand. +func (ns *objectNameStack) getUnquoted(i int) []byte { + ns.ensureCopiedBuffer() + if i == 0 { + return ns.unquotedNames[:ns.offsets[0]] + } else { + return ns.unquotedNames[ns.offsets[i-1]:ns.offsets[i-0]] + } +} + +// invalidOffset indicates that the last JSON object currently has no name. +const invalidOffset = math.MinInt + +// push descends into a nested JSON object. +func (ns *objectNameStack) push() { + ns.offsets = append(ns.offsets, invalidOffset) +} + +// ReplaceLastQuotedOffset replaces the last name with the starting offset +// to the quoted name in some remote buffer. All offsets provided must be +// relative to the same buffer until copyQuotedBuffer is called. +func (ns *objectNameStack) ReplaceLastQuotedOffset(i int) { + // Use bit-wise inversion instead of naive multiplication by -1 to avoid + // ambiguity regarding zero (which is a valid offset into the names field). + // Bit-wise inversion is mathematically equivalent to -i-1, + // such that 0 becomes -1, 1 becomes -2, and so forth. + // This ensures that remote offsets are always negative. + ns.offsets[len(ns.offsets)-1] = ^i +} + +// replaceLastUnquotedName replaces the last name with the provided name. +// +// Invariant: Must call copyQuotedBuffer beforehand. +func (ns *objectNameStack) replaceLastUnquotedName(s string) { + ns.ensureCopiedBuffer() + var startOffset int + if len(ns.offsets) > 1 { + startOffset = ns.offsets[len(ns.offsets)-2] + } + ns.unquotedNames = append(ns.unquotedNames[:startOffset], s...) + ns.offsets[len(ns.offsets)-1] = len(ns.unquotedNames) +} + +// clearLast removes any name in the last JSON object. +// It is semantically equivalent to ns.push followed by ns.pop. +func (ns *objectNameStack) clearLast() { + ns.offsets[len(ns.offsets)-1] = invalidOffset +} + +// pop ascends out of a nested JSON object. +func (ns *objectNameStack) pop() { + ns.offsets = ns.offsets[:len(ns.offsets)-1] +} + +// copyQuotedBuffer copies names from the remote buffer into the local names +// buffer so that there are no more offset references into the remote buffer. +// This allows the remote buffer to change contents without affecting +// the names that this data structure is trying to remember. +func (ns *objectNameStack) copyQuotedBuffer(b []byte) { + // Find the first negative offset. + var i int + for i = len(ns.offsets) - 1; i >= 0 && ns.offsets[i] < 0; i-- { + continue + } + + // Copy each name from the remote buffer into the local buffer. + for i = i + 1; i < len(ns.offsets); i++ { + if i == len(ns.offsets)-1 && ns.offsets[i] == invalidOffset { + if i == 0 { + ns.offsets[i] = 0 + } else { + ns.offsets[i] = ns.offsets[i-1] + } + break // last JSON object had a push without any names + } + + // As a form of Hyrum proofing, we write an invalid character into the + // buffer to make misuse of Decoder.ReadToken more obvious. + // We need to undo that mutation here. + quotedName := b[^ns.offsets[i]:] + if quotedName[0] == invalidateBufferByte { + quotedName[0] = '"' + } + + // Append the unquoted name to the local buffer. + var startOffset int + if i > 0 { + startOffset = ns.offsets[i-1] + } + if n := jsonwire.ConsumeSimpleString(quotedName); n > 0 { + ns.unquotedNames = append(ns.unquotedNames[:startOffset], quotedName[len(`"`):n-len(`"`)]...) + } else { + ns.unquotedNames, _ = jsonwire.AppendUnquote(ns.unquotedNames[:startOffset], quotedName) + } + ns.offsets[i] = len(ns.unquotedNames) + } +} + +func (ns *objectNameStack) ensureCopiedBuffer() { + if len(ns.offsets) > 0 && ns.offsets[len(ns.offsets)-1] < 0 { + panic("BUG: copyQuotedBuffer not called beforehand") + } +} + +// objectNamespaceStack is a stack of object namespaces. +// This data structure assists in detecting duplicate names. +type objectNamespaceStack []objectNamespace + +// reset resets the object namespace stack. +func (nss *objectNamespaceStack) reset() { + if cap(*nss) > 1<<10 { + *nss = nil + } + *nss = (*nss)[:0] +} + +// push starts a new namespace for a nested JSON object. +func (nss *objectNamespaceStack) push() { + if cap(*nss) > len(*nss) { + *nss = (*nss)[:len(*nss)+1] + nss.Last().reset() + } else { + *nss = append(*nss, objectNamespace{}) + } +} + +// Last returns a pointer to the last JSON object namespace. +func (nss objectNamespaceStack) Last() *objectNamespace { + return &nss[len(nss)-1] +} + +// pop terminates the namespace for a nested JSON object. +func (nss *objectNamespaceStack) pop() { + *nss = (*nss)[:len(*nss)-1] +} + +// objectNamespace is the namespace for a JSON object. +// In contrast to objectNameStack, this needs to remember a all names +// per JSON object. +// +// The zero value is an empty namespace ready for use. +type objectNamespace struct { + // It relies on a linear search over all the names before switching + // to use a Go map for direct lookup. + + // endOffsets is a list of offsets to the end of each name in buffers. + // The length of offsets is the number of names in the namespace. + endOffsets []uint + // allUnquotedNames is a back-to-back concatenation of every name in the namespace. + allUnquotedNames []byte + // mapNames is a Go map containing every name in the namespace. + // Only valid if non-nil. + mapNames map[string]struct{} +} + +// reset resets the namespace to be empty. +func (ns *objectNamespace) reset() { + ns.endOffsets = ns.endOffsets[:0] + ns.allUnquotedNames = ns.allUnquotedNames[:0] + ns.mapNames = nil + if cap(ns.endOffsets) > 1<<6 { + ns.endOffsets = nil // avoid pinning arbitrarily large amounts of memory + } + if cap(ns.allUnquotedNames) > 1<<10 { + ns.allUnquotedNames = nil // avoid pinning arbitrarily large amounts of memory + } +} + +// length reports the number of names in the namespace. +func (ns *objectNamespace) length() int { + return len(ns.endOffsets) +} + +// getUnquoted retrieves the ith unquoted name in the namespace. +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]] + } +} + +// lastUnquoted retrieves the last name in the namespace. +func (ns *objectNamespace) lastUnquoted() []byte { + return ns.getUnquoted(ns.length() - 1) +} + +// insertQuoted inserts a name and reports whether it was inserted, +// which only occurs if name is not already in the namespace. +// The provided name must be a valid JSON string. +func (ns *objectNamespace) insertQuoted(name []byte, isVerbatim bool) bool { + if isVerbatim { + name = name[len(`"`) : len(name)-len(`"`)] + } + return ns.insert(name, !isVerbatim) +} +func (ns *objectNamespace) InsertUnquoted(name []byte) bool { + return ns.insert(name, false) +} +func (ns *objectNamespace) insert(name []byte, quoted bool) bool { + var allNames []byte + if quoted { + allNames, _ = jsonwire.AppendUnquote(ns.allUnquotedNames, name) + } else { + allNames = append(ns.allUnquotedNames, name...) + } + name = allNames[len(ns.allUnquotedNames):] + + // Switch to a map if the buffer is too large for linear search. + // This does not add the current name to the map. + if ns.mapNames == nil && (ns.length() > 64 || len(ns.allUnquotedNames) > 1024) { + ns.mapNames = make(map[string]struct{}) + var startOffset uint + for _, endOffset := range ns.endOffsets { + name := ns.allUnquotedNames[startOffset:endOffset] + ns.mapNames[string(name)] = struct{}{} // allocates a new string + startOffset = endOffset + } + } + + if ns.mapNames == nil { + // Perform linear search over the buffer to find matching names. + // It provides O(n) lookup, but does not require any allocations. + var startOffset uint + for _, endOffset := range ns.endOffsets { + if string(ns.allUnquotedNames[startOffset:endOffset]) == string(name) { + return false + } + startOffset = endOffset + } + } else { + // Use the map if it is populated. + // It provides O(1) lookup, but requires a string allocation per name. + if _, ok := ns.mapNames[string(name)]; ok { + return false + } + ns.mapNames[string(name)] = struct{}{} // allocates a new string + } + + ns.allUnquotedNames = allNames + ns.endOffsets = append(ns.endOffsets, uint(len(ns.allUnquotedNames))) + return true +} + +// removeLast removes the last name in the namespace. +func (ns *objectNamespace) removeLast() { + if ns.mapNames != nil { + delete(ns.mapNames, string(ns.lastUnquoted())) + } + if ns.length()-1 == 0 { + ns.endOffsets = ns.endOffsets[:0] + ns.allUnquotedNames = ns.allUnquotedNames[:0] + } else { + ns.endOffsets = ns.endOffsets[:ns.length()-1] + ns.allUnquotedNames = ns.allUnquotedNames[:ns.endOffsets[ns.length()-1]] + } +} diff --git a/src/encoding/json/jsontext/state_test.go b/src/encoding/json/jsontext/state_test.go new file mode 100644 index 0000000000..c227600945 --- /dev/null +++ b/src/encoding/json/jsontext/state_test.go @@ -0,0 +1,396 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "fmt" + "slices" + "strings" + "testing" + "unicode/utf8" +) + +func TestPointer(t *testing.T) { + tests := []struct { + in Pointer + wantParent Pointer + wantLast string + wantTokens []string + wantValid bool + }{ + {"", "", "", nil, true}, + {"a", "", "a", []string{"a"}, false}, + {"~", "", "~", []string{"~"}, false}, + {"/a", "", "a", []string{"a"}, true}, + {"/foo/bar", "/foo", "bar", []string{"foo", "bar"}, true}, + {"///", "//", "", []string{"", "", ""}, true}, + {"/~0~1", "", "~/", []string{"~/"}, true}, + {"/\xde\xad\xbe\xef", "", "\xde\xad\xbe\xef", []string{"\xde\xad\xbe\xef"}, false}, + } + for _, tt := range tests { + if got := tt.in.Parent(); got != tt.wantParent { + t.Errorf("Pointer(%q).Parent = %q, want %q", tt.in, got, tt.wantParent) + } + if got := tt.in.LastToken(); got != tt.wantLast { + t.Errorf("Pointer(%q).Last = %q, want %q", tt.in, got, tt.wantLast) + } + if strings.HasPrefix(string(tt.in), "/") { + wantRoundtrip := tt.in + if !utf8.ValidString(string(wantRoundtrip)) { + // Replace bytes of invalid UTF-8 with Unicode replacement character. + wantRoundtrip = Pointer([]rune(wantRoundtrip)) + } + if got := tt.in.Parent().AppendToken(tt.in.LastToken()); got != wantRoundtrip { + t.Errorf("Pointer(%q).Parent().AppendToken(LastToken()) = %q, want %q", tt.in, got, tt.in) + } + in := tt.in + for { + if (in + "x").Contains(tt.in) { + t.Errorf("Pointer(%q).Contains(%q) = true, want false", in+"x", tt.in) + } + if !in.Contains(tt.in) { + t.Errorf("Pointer(%q).Contains(%q) = false, want true", in, tt.in) + } + if in == in.Parent() { + break + } + in = in.Parent() + } + } + if got := slices.Collect(tt.in.Tokens()); !slices.Equal(got, tt.wantTokens) { + t.Errorf("Pointer(%q).Tokens = %q, want %q", tt.in, got, tt.wantTokens) + } + if got := tt.in.IsValid(); got != tt.wantValid { + t.Errorf("Pointer(%q).IsValid = %v, want %v", tt.in, got, tt.wantValid) + } + } +} + +func TestStateMachine(t *testing.T) { + // To test a state machine, we pass an ordered sequence of operations and + // check whether the current state is as expected. + // The operation type is a union type of various possible operations, + // which either call mutating methods on the state machine or + // call accessor methods on state machine and verify the results. + type operation any + type ( + // stackLengths checks the results of stateEntry.length accessors. + stackLengths []int64 + + // appendTokens is sequence of token kinds to append where + // none of them are expected to fail. + // + // For example: `[nft]` is equivalent to the following sequence: + // + // pushArray() + // appendLiteral() + // appendString() + // appendNumber() + // popArray() + // + appendTokens string + + // appendToken is a single token kind to append with the expected error. + appendToken struct { + kind Kind + want error + } + + // needDelim checks the result of the needDelim accessor. + needDelim struct { + next Kind + want byte + } + ) + + // Each entry is a sequence of tokens to pass to the state machine. + tests := []struct { + label string + ops []operation + }{{ + "TopLevelValues", + []operation{ + stackLengths{0}, + needDelim{'n', 0}, + appendTokens(`nft`), + stackLengths{3}, + needDelim{'"', 0}, + appendTokens(`"0[]{}`), + stackLengths{7}, + }, + }, { + "ArrayValues", + []operation{ + stackLengths{0}, + needDelim{'[', 0}, + appendTokens(`[`), + stackLengths{1, 0}, + needDelim{'n', 0}, + appendTokens(`nft`), + stackLengths{1, 3}, + needDelim{'"', ','}, + appendTokens(`"0[]{}`), + stackLengths{1, 7}, + needDelim{']', 0}, + appendTokens(`]`), + stackLengths{1}, + }, + }, { + "ObjectValues", + []operation{ + stackLengths{0}, + needDelim{'{', 0}, + appendTokens(`{`), + stackLengths{1, 0}, + needDelim{'"', 0}, + appendTokens(`"`), + stackLengths{1, 1}, + needDelim{'n', ':'}, + appendTokens(`n`), + stackLengths{1, 2}, + needDelim{'"', ','}, + appendTokens(`"f"t`), + stackLengths{1, 6}, + appendTokens(`"""0"[]"{}`), + stackLengths{1, 14}, + needDelim{'}', 0}, + appendTokens(`}`), + stackLengths{1}, + }, + }, { + "ObjectCardinality", + []operation{ + appendTokens(`{`), + + // Appending any kind other than string for object name is an error. + appendToken{'n', ErrNonStringName}, + appendToken{'f', ErrNonStringName}, + appendToken{'t', ErrNonStringName}, + appendToken{'0', ErrNonStringName}, + appendToken{'{', ErrNonStringName}, + appendToken{'[', ErrNonStringName}, + appendTokens(`"`), + + // Appending '}' without first appending any value is an error. + appendToken{'}', errMissingValue}, + appendTokens(`"`), + + appendTokens(`}`), + }, + }, { + "MismatchingDelims", + []operation{ + appendToken{'}', errMismatchDelim}, // appending '}' without preceding '{' + appendTokens(`[[{`), + appendToken{']', errMismatchDelim}, // appending ']' that mismatches preceding '{' + appendTokens(`}]`), + appendToken{'}', errMismatchDelim}, // appending '}' that mismatches preceding '[' + appendTokens(`]`), + appendToken{']', errMismatchDelim}, // appending ']' without preceding '[' + }, + }} + + for _, tt := range tests { + t.Run(tt.label, func(t *testing.T) { + // Flatten appendTokens to sequence of appendToken entries. + var ops []operation + for _, op := range tt.ops { + if toks, ok := op.(appendTokens); ok { + for _, k := range []byte(toks) { + ops = append(ops, appendToken{Kind(k), nil}) + } + continue + } + ops = append(ops, op) + } + + // Append each token to the state machine and check the output. + var state stateMachine + state.reset() + var sequence []Kind + for _, op := range ops { + switch op := op.(type) { + case stackLengths: + var got []int64 + for i := range state.Depth() { + e := state.index(i) + got = append(got, e.Length()) + } + want := []int64(op) + if !slices.Equal(got, want) { + t.Fatalf("%s: stack lengths mismatch:\ngot %v\nwant %v", sequence, got, want) + } + case appendToken: + got := state.append(op.kind) + if !equalError(got, op.want) { + t.Fatalf("%s: append('%c') = %v, want %v", sequence, op.kind, got, op.want) + } + if got == nil { + sequence = append(sequence, op.kind) + } + case needDelim: + if got := state.needDelim(op.next); got != op.want { + t.Fatalf("%s: needDelim('%c') = '%c', want '%c'", sequence, op.next, got, op.want) + } + default: + panic(fmt.Sprintf("unknown operation: %T", op)) + } + } + }) + } +} + +// append is a thin wrapper over the other append, pop, or push methods +// based on the token kind. +func (s *stateMachine) append(k Kind) error { + switch k { + case 'n', 'f', 't': + return s.appendLiteral() + case '"': + return s.appendString() + case '0': + return s.appendNumber() + case '{': + return s.pushObject() + case '}': + return s.popObject() + case '[': + return s.pushArray() + case ']': + return s.popArray() + default: + panic(fmt.Sprintf("invalid token kind: '%c'", k)) + } +} + +func TestObjectNamespace(t *testing.T) { + type operation any + type ( + insert struct { + name string + wantInserted bool + } + removeLast struct{} + ) + + // Sequence of insert operations to perform (order matters). + ops := []operation{ + insert{`""`, true}, + removeLast{}, + insert{`""`, true}, + insert{`""`, false}, + + // Test insertion of the same name with different formatting. + insert{`"alpha"`, true}, + insert{`"ALPHA"`, true}, // case-sensitive matching + insert{`"alpha"`, false}, + insert{`"\u0061\u006c\u0070\u0068\u0061"`, false}, // unescapes to "alpha" + removeLast{}, // removes "ALPHA" + insert{`"alpha"`, false}, + removeLast{}, // removes "alpha" + insert{`"alpha"`, true}, + removeLast{}, + + // Bulk insert simple names. + insert{`"alpha"`, true}, + insert{`"bravo"`, true}, + insert{`"charlie"`, true}, + insert{`"delta"`, true}, + insert{`"echo"`, true}, + insert{`"foxtrot"`, true}, + insert{`"golf"`, true}, + insert{`"hotel"`, true}, + insert{`"india"`, true}, + insert{`"juliet"`, true}, + insert{`"kilo"`, true}, + insert{`"lima"`, true}, + insert{`"mike"`, true}, + insert{`"november"`, true}, + insert{`"oscar"`, true}, + insert{`"papa"`, true}, + insert{`"quebec"`, true}, + insert{`"romeo"`, true}, + insert{`"sierra"`, true}, + insert{`"tango"`, true}, + insert{`"uniform"`, true}, + insert{`"victor"`, true}, + insert{`"whiskey"`, true}, + insert{`"xray"`, true}, + insert{`"yankee"`, true}, + insert{`"zulu"`, true}, + + // Test insertion of invalid UTF-8. + insert{`"` + "\ufffd" + `"`, true}, + insert{`"` + "\ufffd" + `"`, false}, + insert{`"\ufffd"`, false}, // unescapes to Unicode replacement character + insert{`"\uFFFD"`, false}, // unescapes to Unicode replacement character + insert{`"` + "\xff" + `"`, false}, // mangles as Unicode replacement character + removeLast{}, + insert{`"` + "\ufffd" + `"`, true}, + + // Test insertion of unicode characters. + insert{`"☺☻☹"`, true}, + insert{`"☺☻☹"`, false}, + removeLast{}, + insert{`"☺☻☹"`, true}, + } + + // Execute the sequence of operations twice: + // 1) on a fresh namespace and 2) on a namespace that has been reset. + var ns objectNamespace + wantNames := []string{} + for _, reset := range []bool{false, true} { + if reset { + ns.reset() + wantNames = nil + } + + // Execute the operations and ensure the state is consistent. + for i, op := range ops { + switch op := op.(type) { + case insert: + gotInserted := ns.insertQuoted([]byte(op.name), false) + if gotInserted != op.wantInserted { + t.Fatalf("%d: objectNamespace{%v}.insert(%v) = %v, want %v", i, strings.Join(wantNames, " "), op.name, gotInserted, op.wantInserted) + } + if gotInserted { + b, _ := AppendUnquote(nil, []byte(op.name)) + wantNames = append(wantNames, string(b)) + } + case removeLast: + ns.removeLast() + wantNames = wantNames[:len(wantNames)-1] + default: + panic(fmt.Sprintf("unknown operation: %T", op)) + } + + // Check that the namespace is consistent. + gotNames := []string{} + for i := range ns.length() { + gotNames = append(gotNames, string(ns.getUnquoted(i))) + } + if !slices.Equal(gotNames, wantNames) { + t.Fatalf("%d: objectNamespace = {%v}, want {%v}", i, strings.Join(gotNames, " "), strings.Join(wantNames, " ")) + } + } + + // Verify that we have not switched to using a Go map. + if ns.mapNames != nil { + t.Errorf("objectNamespace.mapNames = non-nil, want nil") + } + + // Insert a large number of names. + for i := range 64 { + ns.InsertUnquoted([]byte(fmt.Sprintf(`name%d`, i))) + } + + // Verify that we did switch to using a Go map. + if ns.mapNames == nil { + t.Errorf("objectNamespace.mapNames = nil, want non-nil") + } + } +} diff --git a/src/encoding/json/jsontext/token.go b/src/encoding/json/jsontext/token.go new file mode 100644 index 0000000000..22717b154a --- /dev/null +++ b/src/encoding/json/jsontext/token.go @@ -0,0 +1,527 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "errors" + "math" + "strconv" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonwire" +) + +// NOTE: Token is analogous to v1 json.Token. + +const ( + maxInt64 = math.MaxInt64 + minInt64 = math.MinInt64 + maxUint64 = math.MaxUint64 + minUint64 = 0 // for consistency and readability purposes + + invalidTokenPanic = "invalid jsontext.Token; it has been voided by a subsequent json.Decoder call" +) + +var errInvalidToken = errors.New("invalid jsontext.Token") + +// Token represents a lexical JSON token, which may be one of the following: +// - a JSON literal (i.e., null, true, or false) +// - a JSON string (e.g., "hello, world!") +// - a JSON number (e.g., 123.456) +// - a start or end delimiter for a JSON object (i.e., { or } ) +// - a start or end delimiter for a JSON array (i.e., [ or ] ) +// +// 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. +type Token struct { + nonComparable + + // Tokens can exist in either a "raw" or an "exact" form. + // Tokens produced by the Decoder are in the "raw" form. + // Tokens returned by constructors are usually in the "exact" form. + // 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 ║ + // ╚═════════════════╩════════════╧════════════╧════════════╝ + // + // Notes: + // - For tokens stored in "raw" form, the num field contains the + // absolute offset determined by raw.previousOffsetStart(). + // The buffer itself is stored in raw.previousBuffer(). + // - JSON literals and structural characters are always in the "raw" form. + // - JSON strings and numbers can be in either "raw" or "exact" forms. + // - The exact zero value of JSON strings and numbers in the "exact" forms + // have ambiguous representation. Thus, they are always represented + // in the "raw" form. + + // raw contains a reference to the raw decode buffer. + // If non-nil, then its value takes precedence over str and num. + // It is only valid if num == raw.previousOffsetStart(). + 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. + str string + + // num is a float64, int64, or uint64 stored as a uint64 value. + // It is non-zero for any JSON number in the "exact" form. + num uint64 +} + +// TODO: Does representing 1-byte delimiters as *decodeBuffer cause performance issues? + +var ( + Null Token = rawToken("null") + False Token = rawToken("false") + True Token = rawToken("true") + + BeginObject Token = rawToken("{") + EndObject Token = rawToken("}") + BeginArray Token = rawToken("[") + EndArray Token = rawToken("]") + + zeroString Token = rawToken(`""`) + zeroNumber Token = rawToken(`0`) + + nanString Token = String("NaN") + pinfString Token = String("Infinity") + ninfString Token = String("-Infinity") +) + +func rawToken(s string) Token { + return Token{raw: &decodeBuffer{buf: []byte(s), prevStart: 0, prevEnd: len(s)}} +} + +// Bool constructs a Token representing a JSON boolean. +func Bool(b bool) Token { + if b { + return True + } + return False +} + +// String constructs a Token representing a JSON string. +// The provided string should contain valid UTF-8, otherwise invalid characters +// may be mangled as the Unicode replacement character. +func String(s string) Token { + if len(s) == 0 { + return zeroString + } + return Token{str: s} +} + +// Float constructs a Token representing a JSON number. +// 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 { + switch { + case math.Float64bits(n) == 0: + return zeroNumber + case math.IsNaN(n): + return nanString + case math.IsInf(n, +1): + return pinfString + case math.IsInf(n, -1): + return ninfString + } + return Token{str: "f", num: math.Float64bits(n)} +} + +// Int constructs a Token representing a JSON number from an int64. +func Int(n int64) Token { + if n == 0 { + return zeroNumber + } + return Token{str: "i", num: uint64(n)} +} + +// Uint constructs a Token representing a JSON number from a uint64. +func Uint(n uint64) Token { + if n == 0 { + return zeroNumber + } + 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. +func (t Token) Clone() Token { + // TODO: Allow caller to avoid any allocations? + if raw := t.raw; raw != nil { + // Avoid copying globals. + if t.raw.prevStart == 0 { + switch t.raw { + case Null.raw: + return Null + case False.raw: + return False + case True.raw: + return True + case BeginObject.raw: + return BeginObject + case EndObject.raw: + return EndObject + case BeginArray.raw: + return BeginArray + case EndArray.raw: + return EndArray + } + } + + if uint64(raw.previousOffsetStart()) != t.num { + panic(invalidTokenPanic) + } + buf := bytes.Clone(raw.previousBuffer()) + return Token{raw: &decodeBuffer{buf: buf, prevStart: 0, prevEnd: len(buf)}} + } + return t +} + +// Bool returns the value for a JSON boolean. +// It panics if the token kind is not a JSON boolean. +func (t Token) Bool() bool { + switch t.raw { + case True.raw: + return true + case False.raw: + return false + default: + panic("invalid JSON token kind: " + t.Kind().String()) + } +} + +// appendString appends a JSON string to dst and returns it. +// It panics if t is not a JSON string. +func (t Token) appendString(dst []byte, flags *jsonflags.Flags) ([]byte, error) { + if raw := t.raw; raw != nil { + // Handle raw string value. + buf := raw.previousBuffer() + if Kind(buf[0]) == '"' { + if jsonwire.ConsumeSimpleString(buf) == len(buf) { + return append(dst, buf...), nil + } + dst, _, err := jsonwire.ReformatString(dst, buf, flags) + return dst, err + } + } else if len(t.str) != 0 && t.num == 0 { + // Handle exact string value. + return jsonwire.AppendQuote(dst, t.str, flags) + } + + panic("invalid JSON token kind: " + t.Kind().String()) +} + +// String returns the unescaped string value for a JSON string. +// For other JSON kinds, this returns the raw JSON representation. +func (t Token) String() string { + // This is inlinable to take advantage of "function outlining". + // This avoids an allocation for the string(b) conversion + // if the caller does not use the string in an escaping manner. + // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/ + s, b := t.string() + if len(b) > 0 { + return string(b) + } + return s +} +func (t Token) string() (string, []byte) { + if raw := t.raw; raw != nil { + if uint64(raw.previousOffsetStart()) != t.num { + panic(invalidTokenPanic) + } + buf := raw.previousBuffer() + if buf[0] == '"' { + // TODO: Preserve ValueFlags in Token? + isVerbatim := jsonwire.ConsumeSimpleString(buf) == len(buf) + return "", jsonwire.UnquoteMayCopy(buf, isVerbatim) + } + // Handle tokens that are not JSON strings for fmt.Stringer. + return "", buf + } + if len(t.str) != 0 && t.num == 0 { + return t.str, nil + } + // 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, math.Float64frombits(t.num), 64)), nil + case 'i': + return strconv.FormatInt(int64(t.num), 10), nil + case 'u': + return strconv.FormatUint(uint64(t.num), 10), nil + } + } + return "", nil +} + +// appendNumber appends a JSON number to dst and returns it. +// It panics if t is not a JSON number. +func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error) { + if raw := t.raw; raw != nil { + // Handle raw number value. + buf := raw.previousBuffer() + if Kind(buf[0]).normalize() == '0' { + dst, _, err := jsonwire.ReformatNumber(dst, buf, flags) + return dst, err + } + } else if t.num != 0 { + // Handle exact number value. + switch t.str[0] { + case 'f': + return jsonwire.AppendFloat(dst, math.Float64frombits(t.num), 64), nil + case 'i': + return strconv.AppendInt(dst, int64(t.num), 10), nil + case 'u': + return strconv.AppendUint(dst, uint64(t.num), 10), nil + } + } + + panic("invalid JSON token kind: " + t.Kind().String()) +} + +// Float returns the floating-point value for a JSON number. +// 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 { + if raw := t.raw; raw != nil { + // Handle raw 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 + } + } else if t.num != 0 { + // Handle exact number value. + switch t.str[0] { + case 'f': + return math.Float64frombits(t.num) + case 'i': + return float64(int64(t.num)) + case 'u': + return float64(uint64(t.num)) + } + } + + // Handle string values with "NaN", "Infinity", or "-Infinity". + if t.Kind() == '"' { + switch t.String() { + case "NaN": + return math.NaN() + case "Infinity": + return math.Inf(+1) + case "-Infinity": + return math.Inf(-1) + } + } + + panic("invalid JSON token kind: " + t.Kind().String()) +} + +// Int returns the signed integer value for a JSON number. +// 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 { + if raw := t.raw; raw != nil { + // Handle raw integer 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) + } + } + } else if t.num != 0 { + // Handle exact integer value. + switch t.str[0] { + case 'i': + return int64(t.num) + case 'u': + if t.num > maxInt64 { + return maxInt64 + } + 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()) +} + +// Uint returns the unsigned integer value for a JSON number. +// The fractional component of any number is ignored (truncation toward zero). +// Any number beyond the representation of an 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 { + // 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. + + if raw := t.raw; raw != nil { + // Handle raw integer 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 num, ok := jsonwire.ParseUint(buf); ok { + if neg { + return minUint64 + } + return num + } + } else if t.num != 0 { + // Handle exact integer value. + switch t.str[0] { + case 'u': + return t.num + case 'i': + if int64(t.num) < minUint64 { + return minUint64 + } + 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()) +} + +// Kind returns the token kind. +func (t Token) Kind() Kind { + switch { + case t.raw != nil: + raw := t.raw + if uint64(raw.previousOffsetStart()) != t.num { + panic(invalidTokenPanic) + } + return Kind(t.raw.buf[raw.prevStart]).normalize() + case t.num != 0: + return '0' + case len(t.str) != 0: + return '"' + default: + return invalidKind + } +} + +// 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': +// +// - 'n': null +// - 'f': false +// - 't': true +// - '"': string +// - '0': number +// - '{': object start +// - '}': object end +// - '[': array start +// - ']': array end +// +// An invalid kind is usually represented using 0, +// but may be non-zero due to invalid JSON data. +type Kind byte + +const invalidKind Kind = 0 + +// String prints the kind in a humanly readable fashion. +func (k Kind) String() string { + switch k { + case 'n': + return "null" + case 'f': + return "false" + case 't': + return "true" + case '"': + return "string" + case '0': + return "number" + case '{': + return "{" + case '}': + return "}" + case '[': + return "[" + case ']': + return "]" + default: + return "" + } +} + +// normalize coalesces all possible starting characters of a number as just '0'. +func (k Kind) normalize() Kind { + if k == '-' || ('0' <= k && k <= '9') { + return '0' + } + return k +} diff --git a/src/encoding/json/jsontext/token_test.go b/src/encoding/json/jsontext/token_test.go new file mode 100644 index 0000000000..ebe324e0db --- /dev/null +++ b/src/encoding/json/jsontext/token_test.go @@ -0,0 +1,168 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "math" + "reflect" + "testing" +) + +func TestTokenStringAllocations(t *testing.T) { + if testing.CoverMode() != "" { + t.Skip("coverage mode breaks the compiler optimization this depends on") + } + + tok := rawToken(`"hello"`) + var m map[string]bool + got := int(testing.AllocsPerRun(10, func() { + // This function uses tok.String() is a non-escaping manner + // (i.e., looking it up in a Go map). It should not allocate. + if m[tok.String()] { + panic("never executed") + } + })) + if got > 0 { + t.Errorf("Token.String allocated %d times, want 0", got) + } +} + +func TestTokenAccessors(t *testing.T) { + type token struct { + Bool bool + String string + Float float64 + Int int64 + Uint uint64 + Kind Kind + } + + tests := []struct { + in Token + want token + }{ + {Token{}, token{String: ""}}, + {Null, token{String: "null", Kind: 'n'}}, + {False, token{Bool: false, String: "false", Kind: 'f'}}, + {True, token{Bool: true, String: "true", Kind: 't'}}, + {Bool(false), token{Bool: false, String: "false", Kind: 'f'}}, + {Bool(true), token{Bool: true, String: "true", Kind: 't'}}, + {BeginObject, token{String: "{", Kind: '{'}}, + {EndObject, token{String: "}", Kind: '}'}}, + {BeginArray, token{String: "[", Kind: '['}}, + {EndArray, token{String: "]", Kind: ']'}}, + {String(""), token{String: "", Kind: '"'}}, + {String("hello, world!"), token{String: "hello, world!", Kind: '"'}}, + {rawToken(`"hello, world!"`), token{String: "hello, world!", Kind: '"'}}, + {Float(0), token{String: "0", Float: 0, Int: 0, Uint: 0, Kind: '0'}}, + {Float(math.Copysign(0, -1)), token{String: "-0", Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}}, + {Float(math.NaN()), token{String: "NaN", Float: math.NaN(), Int: 0, Uint: 0, Kind: '"'}}, + {Float(math.Inf(+1)), token{String: "Infinity", Float: math.Inf(+1), Kind: '"'}}, + {Float(math.Inf(-1)), token{String: "-Infinity", Float: math.Inf(-1), Kind: '"'}}, + {Int(minInt64), token{String: "-9223372036854775808", Float: minInt64, Int: minInt64, Uint: minUint64, Kind: '0'}}, + {Int(minInt64 + 1), token{String: "-9223372036854775807", Float: minInt64 + 1, Int: minInt64 + 1, Uint: minUint64, Kind: '0'}}, + {Int(-1), token{String: "-1", Float: -1, Int: -1, Uint: minUint64, Kind: '0'}}, + {Int(0), token{String: "0", Float: 0, Int: 0, Uint: 0, Kind: '0'}}, + {Int(+1), token{String: "1", Float: +1, Int: +1, Uint: +1, Kind: '0'}}, + {Int(maxInt64 - 1), token{String: "9223372036854775806", Float: maxInt64 - 1, Int: maxInt64 - 1, Uint: maxInt64 - 1, Kind: '0'}}, + {Int(maxInt64), token{String: "9223372036854775807", Float: maxInt64, Int: maxInt64, Uint: maxInt64, Kind: '0'}}, + {Uint(minUint64), token{String: "0", Kind: '0'}}, + {Uint(minUint64 + 1), token{String: "1", Float: minUint64 + 1, Int: minUint64 + 1, Uint: minUint64 + 1, Kind: '0'}}, + {Uint(maxUint64 - 1), token{String: "18446744073709551614", Float: maxUint64 - 1, Int: maxInt64, Uint: maxUint64 - 1, Kind: '0'}}, + {Uint(maxUint64), token{String: "18446744073709551615", Float: maxUint64, Int: maxInt64, Uint: maxUint64, Kind: '0'}}, + {rawToken(`-0`), token{String: "-0", Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`1e1000`), token{String: "1e1000", Float: math.MaxFloat64, Int: maxInt64, Uint: maxUint64, Kind: '0'}}, + {rawToken(`-1e1000`), token{String: "-1e1000", Float: -math.MaxFloat64, Int: minInt64, Uint: minUint64, Kind: '0'}}, + {rawToken(`0.1`), token{String: "0.1", Float: 0.1, Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`0.5`), token{String: "0.5", Float: 0.5, Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`0.9`), token{String: "0.9", Float: 0.9, Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`1.1`), token{String: "1.1", Float: 1.1, Int: 1, Uint: 1, Kind: '0'}}, + {rawToken(`-0.1`), token{String: "-0.1", Float: -0.1, Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`-0.5`), token{String: "-0.5", Float: -0.5, Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`-0.9`), token{String: "-0.9", Float: -0.9, Int: 0, Uint: 0, Kind: '0'}}, + {rawToken(`-1.1`), token{String: "-1.1", Float: -1.1, Int: -1, Uint: 0, Kind: '0'}}, + {rawToken(`99999999999999999999`), token{String: "99999999999999999999", Float: 1e20 - 1, Int: maxInt64, Uint: maxUint64, Kind: '0'}}, + {rawToken(`-99999999999999999999`), token{String: "-99999999999999999999", Float: -1e20 - 1, Int: minInt64, Uint: minUint64, Kind: '0'}}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + got := token{ + Bool: func() bool { + defer func() { recover() }() + return tt.in.Bool() + }(), + String: tt.in.String(), + Float: func() float64 { + defer func() { recover() }() + return tt.in.Float() + }(), + Int: func() int64 { + defer func() { recover() }() + return tt.in.Int() + }(), + Uint: func() uint64 { + defer func() { recover() }() + return tt.in.Uint() + }(), + Kind: tt.in.Kind(), + } + + if got.Bool != tt.want.Bool { + t.Errorf("Token(%s).Bool() = %v, want %v", tt.in, got.Bool, tt.want.Bool) + } + if got.String != tt.want.String { + t.Errorf("Token(%s).String() = %v, want %v", tt.in, got.String, tt.want.String) + } + if math.Float64bits(got.Float) != math.Float64bits(tt.want.Float) { + t.Errorf("Token(%s).Float() = %v, want %v", tt.in, got.Float, tt.want.Float) + } + if got.Int != tt.want.Int { + t.Errorf("Token(%s).Int() = %v, want %v", tt.in, got.Int, tt.want.Int) + } + if got.Uint != tt.want.Uint { + t.Errorf("Token(%s).Uint() = %v, want %v", tt.in, got.Uint, tt.want.Uint) + } + if got.Kind != tt.want.Kind { + t.Errorf("Token(%s).Kind() = %v, want %v", tt.in, got.Kind, tt.want.Kind) + } + }) + } +} + +func TestTokenClone(t *testing.T) { + tests := []struct { + in Token + wantExactRaw bool + }{ + {Token{}, true}, + {Null, true}, + {False, true}, + {True, true}, + {BeginObject, true}, + {EndObject, true}, + {BeginArray, true}, + {EndArray, true}, + {String("hello, world!"), true}, + {rawToken(`"hello, world!"`), false}, + {Float(3.14159), true}, + {rawToken(`3.14159`), false}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + got := tt.in.Clone() + if !reflect.DeepEqual(got, tt.in) { + t.Errorf("Token(%s) == Token(%s).Clone() = false, want true", tt.in, tt.in) + } + gotExactRaw := got.raw == tt.in.raw + if gotExactRaw != tt.wantExactRaw { + t.Errorf("Token(%s).raw == Token(%s).Clone().raw = %v, want %v", tt.in, tt.in, gotExactRaw, tt.wantExactRaw) + } + }) + } +} diff --git a/src/encoding/json/jsontext/value.go b/src/encoding/json/jsontext/value.go new file mode 100644 index 0000000000..a4b06b2a94 --- /dev/null +++ b/src/encoding/json/jsontext/value.go @@ -0,0 +1,395 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "bytes" + "errors" + "io" + "slices" + "sync" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonwire" +) + +// NOTE: Value is analogous to v1 json.RawMessage. + +// AppendFormat formats the JSON value in src and appends it to dst +// according to the specified options. +// See [Value.Format] for more details about the formatting behavior. +// +// The dst and src may overlap. +// If an error is reported, then the entirety of src is appended to dst. +func AppendFormat(dst, src []byte, opts ...Options) ([]byte, error) { + e := getBufferedEncoder(opts...) + defer putBufferedEncoder(e) + e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1) + if err := e.s.WriteValue(src); err != nil { + return append(dst, src...), err + } + return append(dst, e.s.Buf...), nil +} + +// Value represents a single raw JSON value, which may be one of the following: +// - a JSON literal (i.e., null, true, or false) +// - a JSON string (e.g., "hello, world!") +// - a JSON number (e.g., 123.456) +// - an entire JSON object (e.g., {"fizz":"buzz"} ) +// - an entire JSON array (e.g., [1,2,3] ) +// +// Value can represent entire array or object values, while [Token] cannot. +// Value may contain leading and/or trailing whitespace. +type Value []byte + +// Clone returns a copy of v. +func (v Value) Clone() Value { + return bytes.Clone(v) +} + +// String returns the string formatting of v. +func (v Value) String() string { + if v == nil { + return "null" + } + return string(v) +} + +// IsValid reports whether the raw JSON value is syntactically valid +// according to the specified options. +// +// By default (if no options are specified), it validates according to RFC 7493. +// It verifies whether the input is properly encoded as UTF-8, +// that escape sequences within strings decode to valid Unicode codepoints, and +// that all names in each object are unique. +// It does not verify whether numbers are representable within the limits +// of any common numeric type (e.g., float64, int64, or uint64). +// +// Relevant options include: +// - [AllowDuplicateNames] +// - [AllowInvalidUTF8] +// +// All other options are ignored. +func (v Value) IsValid(opts ...Options) bool { + // TODO: Document support for [WithByteLimit] and [WithDepthLimit]. + d := getBufferedDecoder(v, opts...) + defer putBufferedDecoder(d) + _, errVal := d.ReadValue() + _, errEOF := d.ReadToken() + return errVal == nil && errEOF == io.EOF +} + +// Format formats the raw JSON value in place. +// +// By default (if no options are specified), it validates according to RFC 7493 +// and produces the minimal JSON representation, where +// all whitespace is elided and JSON strings use the shortest encoding. +// +// Relevant options include: +// - [AllowDuplicateNames] +// - [AllowInvalidUTF8] +// - [EscapeForHTML] +// - [EscapeForJS] +// - [PreserveRawStrings] +// - [CanonicalizeRawInts] +// - [CanonicalizeRawFloats] +// - [ReorderRawObjects] +// - [SpaceAfterColon] +// - [SpaceAfterComma] +// - [Multiline] +// - [WithIndent] +// - [WithIndentPrefix] +// +// All other options are ignored. +// +// It is guaranteed to succeed if the value is valid according to the same options. +// If the value is already formatted, then the buffer is not mutated. +func (v *Value) Format(opts ...Options) error { + // TODO: Document support for [WithByteLimit] and [WithDepthLimit]. + return v.format(opts, nil) +} + +// format accepts two []Options to avoid the allocation appending them together. +// It is equivalent to v.Format(append(opts1, opts2...)...). +func (v *Value) format(opts1, opts2 []Options) error { + e := getBufferedEncoder(opts1...) + defer putBufferedEncoder(e) + e.s.Join(opts2...) + e.s.Flags.Set(jsonflags.OmitTopLevelNewline | 1) + if err := e.s.WriteValue(*v); err != nil { + return err + } + if !bytes.Equal(*v, e.s.Buf) { + *v = append((*v)[:0], e.s.Buf...) + } + return nil +} + +// Compact removes all whitespace from the raw JSON value. +// +// It does not reformat JSON strings or numbers to use any other representation. +// To maximize the set of JSON values that can be formatted, +// this permits values with duplicate names and invalid UTF-8. +// +// Compact is equivalent to calling [Value.Format] with the following options: +// - [AllowDuplicateNames](true) +// - [AllowInvalidUTF8](true) +// - [PreserveRawStrings](true) +// +// Any options specified by the caller are applied after the initial set +// and may deliberately override prior options. +func (v *Value) Compact(opts ...Options) error { + return v.format([]Options{ + AllowDuplicateNames(true), + AllowInvalidUTF8(true), + PreserveRawStrings(true), + }, opts) +} + +// Indent reformats the whitespace in the raw JSON value so that each element +// in a JSON object or array begins on a 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. +// +// Indent is equivalent to calling [Value.Format] with the following options: +// - [AllowDuplicateNames](true) +// - [AllowInvalidUTF8](true) +// - [PreserveRawStrings](true) +// - [Multiline](true) +// +// Any options specified by the caller are applied after the initial set +// and may deliberately override prior options. +func (v *Value) Indent(opts ...Options) error { + return v.format([]Options{ + AllowDuplicateNames(true), + AllowInvalidUTF8(true), + PreserveRawStrings(true), + Multiline(true), + }, opts) +} + +// Canonicalize canonicalizes the raw JSON value according to the +// JSON Canonicalization Scheme (JCS) as defined by RFC 8785 +// where it produces a stable representation of a JSON value. +// +// JSON strings are formatted to use their minimal representation, +// JSON numbers are formatted as double precision numbers according +// to some stable serialization algorithm. +// JSON object members are sorted in ascending order by name. +// All whitespace is removed. +// +// The output stability is dependent on the stability of the application data +// (see RFC 8785, Appendix E). It cannot produce stable output from +// fundamentally unstable input. For example, if the JSON value +// contains ephemeral data (e.g., a frequently changing timestamp), +// then the value is still unstable regardless of whether this is called. +// +// Canonicalize is equivalent to calling [Value.Format] with the following options: +// - [CanonicalizeRawInts](true) +// - [CanonicalizeRawFloats](true) +// - [ReorderRawObjects](true) +// +// Any options specified by the caller are applied after the initial set +// and may deliberately override prior options. +// +// Note that JCS treats all JSON numbers as IEEE 754 double precision numbers. +// Any numbers with precision beyond what is representable by that form +// will lose their precision when canonicalized. For example, integer values +// beyond ±2⁵³ will lose their precision. To preserve the original representation +// of JSON integers, additionally set [CanonicalizeRawInts] to false: +// +// v.Canonicalize(jsontext.CanonicalizeRawInts(false)) +func (v *Value) Canonicalize(opts ...Options) error { + return v.format([]Options{ + CanonicalizeRawInts(true), + CanonicalizeRawFloats(true), + ReorderRawObjects(true), + }, opts) +} + +// MarshalJSON returns v as the JSON encoding of v. +// It returns the stored value as the raw JSON output without any validation. +// If v is nil, then this returns a JSON null. +func (v Value) MarshalJSON() ([]byte, error) { + // NOTE: This matches the behavior of v1 json.RawMessage.MarshalJSON. + if v == nil { + return []byte("null"), nil + } + return v, nil +} + +// UnmarshalJSON sets v as the JSON encoding of b. +// It stores a copy of the provided raw JSON input without any validation. +func (v *Value) UnmarshalJSON(b []byte) error { + // NOTE: This matches the behavior of v1 json.RawMessage.UnmarshalJSON. + if v == nil { + return errors.New("jsontext.Value: UnmarshalJSON on nil pointer") + } + *v = append((*v)[:0], b...) + return nil +} + +// Kind returns the starting token kind. +// For a valid value, this will never include '}' or ']'. +func (v Value) Kind() Kind { + if v := v[jsonwire.ConsumeWhitespace(v):]; len(v) > 0 { + return Kind(v[0]).normalize() + } + return invalidKind +} + +const commaAndWhitespace = ", \n\r\t" + +type objectMember struct { + // name is the unquoted name. + name []byte // e.g., "name" + // buffer is the entirety of the raw JSON object member + // starting from right after the previous member (or opening '{') + // until right after the member value. + buffer []byte // e.g., `, \n\r\t"name": "value"` +} + +func (x objectMember) Compare(y objectMember) int { + if c := jsonwire.CompareUTF16(x.name, y.name); c != 0 { + return c + } + // With [AllowDuplicateNames] or [AllowInvalidUTF8], + // names could be identical, so also sort using the member value. + return jsonwire.CompareUTF16( + bytes.TrimLeft(x.buffer, commaAndWhitespace), + bytes.TrimLeft(y.buffer, commaAndWhitespace)) +} + +var objectMemberPool = sync.Pool{New: func() any { return new([]objectMember) }} + +func getObjectMembers() *[]objectMember { + ns := objectMemberPool.Get().(*[]objectMember) + *ns = (*ns)[:0] + return ns +} +func putObjectMembers(ns *[]objectMember) { + if cap(*ns) < 1<<10 { + clear(*ns) // avoid pinning name and buffer + objectMemberPool.Put(ns) + } +} + +// mustReorderObjects reorders in-place all object members in a JSON value, +// which must be valid otherwise it panics. +func mustReorderObjects(b []byte) { + // Obtain a buffered encoder just to use its internal buffer as + // a scratch buffer for reordering object members. + e2 := getBufferedEncoder() + defer putBufferedEncoder(e2) + + // Disable unnecessary checks to syntactically parse the JSON value. + d := getBufferedDecoder(b) + defer putBufferedDecoder(d) + d.s.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1) + mustReorderObjectsFromDecoder(d, &e2.s.Buf) // per RFC 8785, section 3.2.3 +} + +// mustReorderObjectsFromDecoder recursively reorders all object members in place +// according to the ordering specified in RFC 8785, section 3.2.3. +// +// Pre-conditions: +// - The value is valid (i.e., no decoder errors should ever occur). +// - Initial call is provided a Decoder reading from the start of v. +// +// Post-conditions: +// - Exactly one JSON value is read from the Decoder. +// - All fully-parsed JSON objects are reordered by directly moving +// the members in the value buffer. +// +// The runtime is approximately O(n·log(n)) + O(m·log(m)), +// where n is len(v) and m is the total number of object members. +func mustReorderObjectsFromDecoder(d *Decoder, scratch *[]byte) { + switch tok, err := d.ReadToken(); tok.Kind() { + case '{': + // Iterate and collect the name and offsets for every object member. + members := getObjectMembers() + defer putObjectMembers(members) + var prevMember objectMember + isSorted := true + + beforeBody := d.InputOffset() // offset after '{' + for d.PeekKind() != '}' { + beforeName := d.InputOffset() + var flags jsonwire.ValueFlags + name, _ := d.s.ReadValue(&flags) + name = jsonwire.UnquoteMayCopy(name, flags.IsVerbatim()) + mustReorderObjectsFromDecoder(d, scratch) + afterValue := d.InputOffset() + + currMember := objectMember{name, d.s.buf[beforeName:afterValue]} + if isSorted && len(*members) > 0 { + isSorted = objectMember.Compare(prevMember, currMember) < 0 + } + *members = append(*members, currMember) + prevMember = currMember + } + afterBody := d.InputOffset() // offset before '}' + d.ReadToken() + + // Sort the members; return early if it's already sorted. + if isSorted { + return + } + firstBufferBeforeSorting := (*members)[0].buffer + slices.SortFunc(*members, objectMember.Compare) + firstBufferAfterSorting := (*members)[0].buffer + + // Append the reordered members to a new buffer, + // then copy the reordered members back over the original members. + // Avoid swapping in place since each member may be a different size + // where moving a member over a smaller member may corrupt the data + // for subsequent members before they have been moved. + // + // The following invariant must hold: + // sum([m.after-m.before for m in members]) == afterBody-beforeBody + commaAndWhitespacePrefix := func(b []byte) []byte { + return b[:len(b)-len(bytes.TrimLeft(b, commaAndWhitespace))] + } + sorted := (*scratch)[:0] + for i, member := range *members { + switch { + case i == 0 && &member.buffer[0] != &firstBufferBeforeSorting[0]: + // First member after sorting is not the first member before sorting, + // so use the prefix of the first member before sorting. + sorted = append(sorted, commaAndWhitespacePrefix(firstBufferBeforeSorting)...) + sorted = append(sorted, bytes.TrimLeft(member.buffer, commaAndWhitespace)...) + case i != 0 && &member.buffer[0] == &firstBufferBeforeSorting[0]: + // Later member after sorting is the first member before sorting, + // so use the prefix of the first member after sorting. + sorted = append(sorted, commaAndWhitespacePrefix(firstBufferAfterSorting)...) + sorted = append(sorted, bytes.TrimLeft(member.buffer, commaAndWhitespace)...) + default: + sorted = append(sorted, member.buffer...) + } + } + if int(afterBody-beforeBody) != len(sorted) { + panic("BUG: length invariant violated") + } + copy(d.s.buf[beforeBody:afterBody], sorted) + + // Update scratch buffer to the largest amount ever used. + if len(sorted) > len(*scratch) { + *scratch = sorted + } + case '[': + for d.PeekKind() != ']' { + mustReorderObjectsFromDecoder(d, scratch) + } + d.ReadToken() + default: + if err != nil { + panic("BUG: " + err.Error()) + } + } +} diff --git a/src/encoding/json/jsontext/value_test.go b/src/encoding/json/jsontext/value_test.go new file mode 100644 index 0000000000..184a27d88e --- /dev/null +++ b/src/encoding/json/jsontext/value_test.go @@ -0,0 +1,200 @@ +// Copyright 2020 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 + +package jsontext + +import ( + "io" + "strings" + "testing" + + "encoding/json/internal/jsontest" + "encoding/json/internal/jsonwire" +) + +type valueTestdataEntry struct { + name jsontest.CaseName + in string + wantValid bool + wantCompacted string + wantCompactErr error // implies wantCompacted is in + wantIndented string // wantCompacted if empty; uses "\t" for indent prefix and " " for indent + wantIndentErr error // implies wantCompacted is in + wantCanonicalized string // wantCompacted if empty + wantCanonicalizeErr error // implies wantCompacted is in +} + +var valueTestdata = append(func() (out []valueTestdataEntry) { + // Initialize valueTestdata from coderTestdata. + for _, td := range coderTestdata { + // NOTE: The Compact method preserves the raw formatting of strings, + // while the Encoder (by default) does not. + if td.name.Name == "ComplicatedString" { + td.outCompacted = strings.TrimSpace(td.in) + } + out = append(out, valueTestdataEntry{ + name: td.name, + in: td.in, + wantValid: true, + wantCompacted: td.outCompacted, + wantIndented: td.outIndented, + wantCanonicalized: td.outCanonicalized, + }) + } + return out +}(), []valueTestdataEntry{{ + name: jsontest.Name("RFC8785/Primitives"), + in: `{ + "numbers": [333333333.33333329, 1E30, 4.50, + 2e-3, 0.000000000000000000000000001, -0], + "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/", + "literals": [null, true, false] + }`, + wantValid: true, + wantCompacted: `{"numbers":[333333333.33333329,1E30,4.50,2e-3,0.000000000000000000000000001,-0],"string":"\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/","literals":[null,true,false]}`, + wantIndented: `{ + "numbers": [ + 333333333.33333329, + 1E30, + 4.50, + 2e-3, + 0.000000000000000000000000001, + -0 + ], + "string": "\u20ac$\u000F\u000aA'\u0042\u0022\u005c\\\"\/", + "literals": [ + null, + true, + false + ] + }`, + wantCanonicalized: `{"literals":[null,true,false],"numbers":[333333333.3333333,1e+30,4.5,0.002,1e-27,0],"string":"€$\u000f\nA'B\"\\\\\"/"}`, +}, { + name: jsontest.Name("RFC8785/ObjectOrdering"), + in: `{ + "\u20ac": "Euro Sign", + "\r": "Carriage Return", + "\ufb33": "Hebrew Letter Dalet With Dagesh", + "1": "One", + "\ud83d\ude00": "Emoji: Grinning Face", + "\u0080": "Control", + "\u00f6": "Latin Small Letter O With Diaeresis" + }`, + wantValid: true, + wantCompacted: `{"\u20ac":"Euro Sign","\r":"Carriage Return","\ufb33":"Hebrew Letter Dalet With Dagesh","1":"One","\ud83d\ude00":"Emoji: Grinning Face","\u0080":"Control","\u00f6":"Latin Small Letter O With Diaeresis"}`, + wantIndented: `{ + "\u20ac": "Euro Sign", + "\r": "Carriage Return", + "\ufb33": "Hebrew Letter Dalet With Dagesh", + "1": "One", + "\ud83d\ude00": "Emoji: Grinning Face", + "\u0080": "Control", + "\u00f6": "Latin Small Letter O With Diaeresis" + }`, + wantCanonicalized: `{"\r":"Carriage Return","1":"One","€":"Control","ö":"Latin Small Letter O With Diaeresis","€":"Euro Sign","😀":"Emoji: Grinning Face","דּ":"Hebrew Letter Dalet With Dagesh"}`, +}, { + name: jsontest.Name("LargeIntegers"), + in: ` [ -9223372036854775808 , 9223372036854775807 ] `, + wantValid: true, + wantCompacted: `[-9223372036854775808,9223372036854775807]`, + wantIndented: `[ + -9223372036854775808, + 9223372036854775807 + ]`, + wantCanonicalized: `[-9223372036854776000,9223372036854776000]`, // NOTE: Loss of precision due to numbers being treated as floats. +}, { + name: jsontest.Name("InvalidUTF8"), + in: ` "living` + "\xde\xad\xbe\xef" + `\ufffd�" `, + wantValid: false, // uses RFC 7493 as the definition; which validates UTF-8 + wantCompacted: `"living` + "\xde\xad\xbe\xef" + `\ufffd�"`, + wantCanonicalizeErr: E(jsonwire.ErrInvalidUTF8).withPos(` "living`+"\xde\xad", ""), +}, { + name: jsontest.Name("InvalidUTF8/SurrogateHalf"), + in: `"\ud800"`, + wantValid: false, // uses RFC 7493 as the definition; which validates UTF-8 + wantCompacted: `"\ud800"`, + wantCanonicalizeErr: newInvalidEscapeSequenceError(`\ud800"`).withPos(`"`, ""), +}, { + name: jsontest.Name("UppercaseEscaped"), + in: `"\u000B"`, + wantValid: true, + wantCompacted: `"\u000B"`, + wantCanonicalized: `"\u000b"`, +}, { + name: jsontest.Name("DuplicateNames"), + in: ` { "0" : 0 , "1" : 1 , "0" : 0 }`, + wantValid: false, // uses RFC 7493 as the definition; which does check for object uniqueness + wantCompacted: `{"0":0,"1":1,"0":0}`, + wantIndented: `{ + "0": 0, + "1": 1, + "0": 0 + }`, + wantCanonicalizeErr: E(ErrDuplicateName).withPos(` { "0" : 0 , "1" : 1 , `, "/0"), +}, { + name: jsontest.Name("Whitespace"), + in: " \n\r\t", + wantValid: false, + wantCompacted: " \n\r\t", + wantCompactErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""), + wantIndentErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""), + wantCanonicalizeErr: E(io.ErrUnexpectedEOF).withPos(" \n\r\t", ""), +}}...) + +func TestValueMethods(t *testing.T) { + for _, td := range valueTestdata { + t.Run(td.name.Name, func(t *testing.T) { + if td.wantIndented == "" { + td.wantIndented = td.wantCompacted + } + if td.wantCanonicalized == "" { + td.wantCanonicalized = td.wantCompacted + } + if td.wantCompactErr != nil { + td.wantCompacted = td.in + } + if td.wantIndentErr != nil { + td.wantIndented = td.in + } + if td.wantCanonicalizeErr != nil { + td.wantCanonicalized = td.in + } + + v := Value(td.in) + gotValid := v.IsValid() + if gotValid != td.wantValid { + t.Errorf("%s: Value.IsValid = %v, want %v", td.name.Where, gotValid, td.wantValid) + } + + gotCompacted := Value(td.in) + gotCompactErr := gotCompacted.Compact() + if string(gotCompacted) != td.wantCompacted { + t.Errorf("%s: Value.Compact = %s, want %s", td.name.Where, gotCompacted, td.wantCompacted) + } + if !equalError(gotCompactErr, td.wantCompactErr) { + t.Errorf("%s: Value.Compact error mismatch:\ngot %v\nwant %v", td.name.Where, gotCompactErr, td.wantCompactErr) + } + + gotIndented := Value(td.in) + gotIndentErr := gotIndented.Indent(WithIndentPrefix("\t"), WithIndent(" ")) + if string(gotIndented) != td.wantIndented { + t.Errorf("%s: Value.Indent = %s, want %s", td.name.Where, gotIndented, td.wantIndented) + } + if !equalError(gotIndentErr, td.wantIndentErr) { + t.Errorf("%s: Value.Indent error mismatch:\ngot %v\nwant %v", td.name.Where, gotIndentErr, td.wantIndentErr) + } + + gotCanonicalized := Value(td.in) + gotCanonicalizeErr := gotCanonicalized.Canonicalize() + if string(gotCanonicalized) != td.wantCanonicalized { + t.Errorf("%s: Value.Canonicalize = %s, want %s", td.name.Where, gotCanonicalized, td.wantCanonicalized) + } + if !equalError(gotCanonicalizeErr, td.wantCanonicalizeErr) { + t.Errorf("%s: Value.Canonicalize error mismatch:\ngot %v\nwant %v", td.name.Where, gotCanonicalizeErr, td.wantCanonicalizeErr) + } + }) + } +} diff --git a/src/encoding/json/number_test.go b/src/encoding/json/number_test.go index c82e6deb83..69eccaaffd 100644 --- a/src/encoding/json/number_test.go +++ b/src/encoding/json/number_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/scanner.go b/src/encoding/json/scanner.go index 3445dbf2bb..f4086186e2 100644 --- a/src/encoding/json/scanner.go +++ b/src/encoding/json/scanner.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json // JSON value parser state machine. diff --git a/src/encoding/json/scanner_test.go b/src/encoding/json/scanner_test.go index 068439dcac..fb64463599 100644 --- a/src/encoding/json/scanner_test.go +++ b/src/encoding/json/scanner_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/stream.go b/src/encoding/json/stream.go index e2d9470bcc..fc480c9946 100644 --- a/src/encoding/json/stream.go +++ b/src/encoding/json/stream.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/stream_test.go b/src/encoding/json/stream_test.go index 46f9407c88..478ee18291 100644 --- a/src/encoding/json/stream_test.go +++ b/src/encoding/json/stream_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/tables.go b/src/encoding/json/tables.go index 10acdc18c6..e8841cfc68 100644 --- a/src/encoding/json/tables.go +++ b/src/encoding/json/tables.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import "unicode/utf8" diff --git a/src/encoding/json/tagkey_test.go b/src/encoding/json/tagkey_test.go index d432cd7d8b..8e4d360e94 100644 --- a/src/encoding/json/tagkey_test.go +++ b/src/encoding/json/tagkey_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/tags.go b/src/encoding/json/tags.go index b490328f4c..5ebd700fa6 100644 --- a/src/encoding/json/tags.go +++ b/src/encoding/json/tags.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import ( diff --git a/src/encoding/json/tags_test.go b/src/encoding/json/tags_test.go index eb43ff5530..6bb621c128 100644 --- a/src/encoding/json/tags_test.go +++ b/src/encoding/json/tags_test.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !goexperiment.jsonv2 + package json import "testing" diff --git a/src/encoding/json/v2/arshal.go b/src/encoding/json/v2/arshal.go new file mode 100644 index 0000000000..99fcc5bd46 --- /dev/null +++ b/src/encoding/json/v2/arshal.go @@ -0,0 +1,570 @@ +// Copyright 2020 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 + +package json + +import ( + "bytes" + "encoding" + "io" + "reflect" + "slices" + "strings" + "sync" + "time" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/jsontext" +) + +// Reference encoding and time packages to assist pkgsite +// in being able to hotlink references to those packages. +var ( + _ encoding.TextMarshaler + _ encoding.TextAppender + _ encoding.TextUnmarshaler + _ time.Time + _ time.Duration +) + +// export exposes internal functionality of the "jsontext" package. +var export = jsontext.Internal.Export(&internal.AllowInternalUse) + +// Marshal serializes a Go value as a []byte according to the provided +// marshal and encode options (while ignoring unmarshal or decode options). +// It does not terminate the output with a newline. +// +// Type-specific marshal functions and methods take precedence +// over the default representation of a value. +// 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 +// 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: +// +// - 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], +// 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 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, 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: +// +// - 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 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]. +// +// - 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. +// +// - A Go struct is encoded as a JSON object. +// See the “JSON Representation of Go structs” section +// in the package-level documentation for more details. +// +// - 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. +// +// - 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 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.Format] if non-empty. +// +// - A Go [time.Duration] is encoded as a JSON string containing the duration +// formatted according to [time.Duration.String]. +// If the format is "sec", "milli", "micro", or "nano", +// then the duration is encoded as a JSON number of the number of seconds +// (or milliseconds, microseconds, or nanoseconds) in the duration. +// If the format is "units", it uses [time.Duration.String]. +// +// - 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) { + enc := export.GetBufferedEncoder(opts...) + defer export.PutBufferedEncoder(enc) + xe := export.Encoder(enc) + xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1) + err = marshalEncode(enc, in, &xe.Struct) + if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return nil, internal.TransformMarshalError(in, err) + } + return bytes.Clone(xe.Buf), err +} + +// MarshalWrite serializes a Go value into an [io.Writer] according to the provided +// marshal and encode options (while ignoring unmarshal or decode options). +// 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) { + enc := export.GetStreamingEncoder(out, opts...) + defer export.PutStreamingEncoder(enc) + xe := export.Encoder(enc) + xe.Flags.Set(jsonflags.OmitTopLevelNewline | 1) + err = marshalEncode(enc, in, &xe.Struct) + if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.TransformMarshalError(in, err) + } + return err +} + +// MarshalEncode serializes a Go value into an [jsontext.Encoder] according to +// the provided marshal options (while ignoring unmarshal, encode, or decode options). +// Any marshal-relevant options already specified on the [jsontext.Encoder] +// take lower precedence than the set of options provided by the caller. +// Unlike [Marshal] and [MarshalWrite], encode options are ignored because +// they must have already been specified on the provided [jsontext.Encoder]. +// +// See [Marshal] for details about the conversion of a Go value into JSON. +func MarshalEncode(out *jsontext.Encoder, in any, opts ...Options) (err error) { + xe := export.Encoder(out) + if len(opts) > 0 { + optsOriginal := xe.Struct + defer func() { xe.Struct = optsOriginal }() + xe.Struct.JoinWithoutCoderOptions(opts...) + } + err = marshalEncode(out, in, &xe.Struct) + if err != nil && xe.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.TransformMarshalError(in, err) + } + return err +} + +func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err error) { + v := reflect.ValueOf(in) + if !v.IsValid() || (v.Kind() == reflect.Pointer && v.IsNil()) { + return out.WriteToken(jsontext.Null) + } + // Shallow copy non-pointer values to obtain an addressable value. + // It is beneficial to performance to always pass pointers to avoid this. + forceAddr := v.Kind() != reflect.Pointer + if forceAddr { + v2 := reflect.New(v.Type()) + v2.Elem().Set(v) + v = v2 + } + va := addressableValue{v.Elem(), forceAddr} // dereferenced pointer is always addressable + t := va.Type() + + // Lookup and call the marshal function for this type. + marshal := lookupArshaler(t).marshal + if mo.Marshalers != nil { + marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t) + } + if err := marshal(out, va, mo); err != nil { + if !mo.Flags.Get(jsonflags.AllowDuplicateNames) { + export.Encoder(out).Tokens.InvalidateDisabledNamespaces() + } + return err + } + return nil +} + +// Unmarshal decodes a []byte input into a Go value according to the provided +// unmarshal and decode options (while ignoring marshal or encode options). +// The input must be a single JSON value with optional whitespace interspersed. +// The output must be a non-nil pointer. +// +// Type-specific unmarshal functions and methods take precedence +// over the default representation of a value. +// 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 +// these functions and methods can be consistently called. +// +// The input is decoded into the output according 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], +// 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 value type implements [Unmarshaler], +// then the UnmarshalJSON method is called to decode the JSON value. +// +// - If the value type implements [encoding.TextUnmarshaler], +// then the input is decoded as a JSON string and +// the UnmarshalText method is called with the decoded string value. +// This fails with a [SemanticError] if the input is not a JSON string. +// +// - Otherwise, the JSON value is decoded 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). +// 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, +// then this fails with a [SemanticError]. Unless otherwise specified, +// the decoded value replaces any pre-existing value. +// +// 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. +// 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, +// otherwise it fails with a [SemanticError]. +// +// - A Go integer 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 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]. +// +// - A Go map is decoded from a JSON object, +// where each JSON object name and value pair is recursively decoded +// as the Go map key and value. Maps are not cleared. +// If the Go map is nil, then a new map is allocated to decode into. +// If the decoded key matches an existing Go map entry, the entry value +// is reused by decoding the JSON object value into it. +// 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 +// in the package-level documentation for more details. +// +// - A Go slice is decoded from a JSON array, where each JSON element +// 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. +// Otherwise, a nil interface value of an empty interface type is initialized +// with a zero Go bool, string, float64, map[string]any, or []any if the +// input is a JSON boolean, string, number, object, or array, respectively. +// If the interface value is still nil, then this fails with a [SemanticError] +// since decoding could not determine an appropriate Go type to decode into. +// 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. +// +// - 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 a 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] is decoded from a JSON string by +// passing the decoded string to [time.ParseDuration]. +// If the format is "sec", "milli", "micro", or "nano", +// then the duration is decoded from a JSON number of the number of seconds +// (or milliseconds, microseconds, or nanoseconds) in the duration. +// If the format is "units", it uses [time.ParseDuration]. +// +// - All other Go types (e.g., complex numbers, channels, and functions) +// have no default representation and result in a [SemanticError]. +// +// In general, unmarshaling follows merge semantics (similar to RFC 7396) +// where the decoded Go value replaces the destination value +// for any JSON kind other than an object. +// 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) { + dec := export.GetBufferedDecoder(in, opts...) + defer export.PutBufferedDecoder(dec) + xd := export.Decoder(dec) + err = unmarshalFull(dec, out, &xd.Struct) + if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.TransformUnmarshalError(out, err) + } + return err +} + +// UnmarshalRead deserializes a Go value from an [io.Reader] according to the +// provided unmarshal and decode options (while ignoring marshal or encode options). +// The input must be a single JSON value with optional whitespace interspersed. +// It consumes the entirety of [io.Reader] until [io.EOF] is encountered, +// 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) { + dec := export.GetStreamingDecoder(in, opts...) + defer export.PutStreamingDecoder(dec) + xd := export.Decoder(dec) + err = unmarshalFull(dec, out, &xd.Struct) + if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.TransformUnmarshalError(out, err) + } + return err +} + +func unmarshalFull(in *jsontext.Decoder, out any, uo *jsonopts.Struct) error { + switch err := unmarshalDecode(in, out, uo); err { + case nil: + return export.Decoder(in).CheckEOF() + case io.EOF: + return io.ErrUnexpectedEOF + default: + return err + } +} + +// UnmarshalDecode deserializes a Go value from a [jsontext.Decoder] according to +// the provided unmarshal options (while ignoring marshal, encode, or decode options). +// Any unmarshal options already specified on the [jsontext.Decoder] +// take lower precedence than the set of options provided by the caller. +// Unlike [Unmarshal] and [UnmarshalRead], decode options are ignored because +// they must have already been specified on the provided [jsontext.Decoder]. +// +// The input may be a stream of one or more JSON values, +// where this only unmarshals the next JSON value in the stream. +// The output must be a non-nil pointer. +// See [Unmarshal] for details about the conversion of JSON into a Go value. +func UnmarshalDecode(in *jsontext.Decoder, out any, opts ...Options) (err error) { + xd := export.Decoder(in) + if len(opts) > 0 { + optsOriginal := xd.Struct + defer func() { xd.Struct = optsOriginal }() + xd.Struct.JoinWithoutCoderOptions(opts...) + } + err = unmarshalDecode(in, out, &xd.Struct) + if err != nil && xd.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.TransformUnmarshalError(out, err) + } + return err +} + +func unmarshalDecode(in *jsontext.Decoder, out any, uo *jsonopts.Struct) (err error) { + v := reflect.ValueOf(out) + if v.Kind() != reflect.Pointer || v.IsNil() { + return &SemanticError{action: "unmarshal", GoType: reflect.TypeOf(out), Err: internal.ErrNonNilReference} + } + va := addressableValue{v.Elem(), false} // dereferenced pointer is always addressable + t := va.Type() + + // In legacy semantics, the entirety of the next JSON value + // was validated before attempting to unmarshal it. + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err := export.Decoder(in).CheckNextValue(); err != nil { + return err + } + } + + // Lookup and call the unmarshal function for this type. + unmarshal := lookupArshaler(t).unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t) + } + if err := unmarshal(in, va, uo); err != nil { + if !uo.Flags.Get(jsonflags.AllowDuplicateNames) { + export.Decoder(in).Tokens.InvalidateDisabledNamespaces() + } + return err + } + return nil +} + +// addressableValue is a reflect.Value that is guaranteed to be addressable +// such that calling the Addr and Set methods do not panic. +// +// There is no compile magic that enforces this property, +// but rather the need to construct this type makes it easier to examine each +// construction site to ensure that this property is upheld. +type addressableValue struct { + reflect.Value + + // forcedAddr reports whether this value is addressable + // only through the use of [newAddressableValue]. + // This is only used for [jsonflags.CallMethodsWithLegacySemantics]. + forcedAddr bool +} + +// newAddressableValue constructs a new addressable value of type t. +func newAddressableValue(t reflect.Type) addressableValue { + return addressableValue{reflect.New(t).Elem(), true} +} + +// TODO: Remove *jsonopts.Struct argument from [marshaler] and [unmarshaler]. +// This can be directly accessed on the encoder or decoder. + +// All marshal and unmarshal behavior is implemented using these signatures. +// The *jsonopts.Struct argument is guaranteed to identical to or at least +// a strict super-set of the options in Encoder.Struct or Decoder.Struct. +// It is identical for Marshal, Unmarshal, MarshalWrite, and UnmarshalRead. +// It is a super-set for MarshalEncode and UnmarshalDecode. +type ( + marshaler = func(*jsontext.Encoder, addressableValue, *jsonopts.Struct) error + unmarshaler = func(*jsontext.Decoder, addressableValue, *jsonopts.Struct) error +) + +type arshaler struct { + marshal marshaler + unmarshal unmarshaler + nonDefault bool +} + +var lookupArshalerCache sync.Map // map[reflect.Type]*arshaler + +func lookupArshaler(t reflect.Type) *arshaler { + if v, ok := lookupArshalerCache.Load(t); ok { + return v.(*arshaler) + } + + fncs := makeDefaultArshaler(t) + fncs = makeMethodArshaler(fncs, t) + fncs = makeTimeArshaler(fncs, t) + + // Use the last stored so that duplicate arshalers can be garbage collected. + v, _ := lookupArshalerCache.LoadOrStore(t, fncs) + return v.(*arshaler) +} + +var stringsPools = &sync.Pool{New: func() any { return new(stringSlice) }} + +type stringSlice []string + +// getStrings returns a non-nil pointer to a slice with length n. +func getStrings(n int) *stringSlice { + s := stringsPools.Get().(*stringSlice) + if cap(*s) < n { + *s = make([]string, n) + } + *s = (*s)[:n] + return s +} + +func putStrings(s *stringSlice) { + if cap(*s) > 1<<10 { + *s = nil // avoid pinning arbitrarily large amounts of memory + } + stringsPools.Put(s) +} + +func (ss *stringSlice) Sort() { + slices.SortFunc(*ss, func(x, y string) int { return strings.Compare(x, y) }) +} diff --git a/src/encoding/json/v2/arshal_any.go b/src/encoding/json/v2/arshal_any.go new file mode 100644 index 0000000000..3fb679d553 --- /dev/null +++ b/src/encoding/json/v2/arshal_any.go @@ -0,0 +1,283 @@ +// Copyright 2022 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 + +package json + +import ( + "cmp" + "reflect" + "strconv" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +// This file contains an optimized marshal and unmarshal implementation +// for the any type. This type is often used when the Go program has +// no knowledge of the JSON schema. This is a common enough occurrence +// to justify the complexity of adding logic for this. + +// marshalValueAny marshals a Go any as a JSON value. +// This assumes that there are no special formatting directives +// for any possible nested value. +func marshalValueAny(enc *jsontext.Encoder, val any, mo *jsonopts.Struct) error { + switch val := val.(type) { + case nil: + return enc.WriteToken(jsontext.Null) + case bool: + return enc.WriteToken(jsontext.Bool(val)) + case string: + return enc.WriteToken(jsontext.String(val)) + case float64: + 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) + } +} + +// unmarshalValueAny unmarshals a JSON value as a Go any. +// This assumes that there are no special formatting directives +// for any possible nested value. +// Duplicate names must be rejected since this does not implement merging. +func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error) { + switch k := dec.PeekKind(); k { + case '{': + return unmarshalObjectAny(dec, uo) + case '[': + return unmarshalArrayAny(dec, uo) + default: + xd := export.Decoder(dec) + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return nil, err + } + switch val.Kind() { + case 'n': + return nil, nil + case 'f': + return false, nil + case 't': + return true, nil + case '"': + val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if xd.StringCache == nil { + xd.StringCache = new(stringCache) + } + return makeString(xd.StringCache, val), nil + case '0': + if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) { + return internal.RawNumberOf(val), nil + } + fv, ok := jsonwire.ParseFloat(val, 64) + if !ok { + return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange) + } + return fv, nil + default: + panic("BUG: invalid kind: " + k.String()) + } + } +} + +// marshalObjectAny marshals a Go map[string]any as a JSON object +// (or as a JSON null if nil and [jsonflags.FormatNilMapAsNull]). +func marshalObjectAny(enc *jsontext.Encoder, obj map[string]any, mo *jsonopts.Struct) error { + // Check for cycles. + xe := export.Encoder(enc) + if xe.Tokens.Depth() > startDetectingCyclesAfter { + v := reflect.ValueOf(obj) + if err := visitPointer(&xe.SeenPointers, v); err != nil { + return newMarshalErrorBefore(enc, anyType, err) + } + defer leavePointer(&xe.SeenPointers, v) + } + + // Handle empty maps. + if len(obj) == 0 { + if mo.Flags.Get(jsonflags.FormatNilMapAsNull) && obj == nil { + return enc.WriteToken(jsontext.Null) + } + // Optimize for marshaling an empty map without any preceding whitespace. + if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '{'), "{}"...) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + } + + if err := enc.WriteToken(jsontext.BeginObject); err != nil { + return err + } + // A Go map guarantees that each entry has a unique key + // The only possibility of duplicates is due to invalid UTF-8. + if !mo.Flags.Get(jsonflags.AllowInvalidUTF8) { + xe.Tokens.Last.DisableNamespace() + } + if !mo.Flags.Get(jsonflags.Deterministic) || len(obj) <= 1 { + for name, val := range obj { + if err := enc.WriteToken(jsontext.String(name)); err != nil { + return err + } + if err := marshalValueAny(enc, val, mo); err != nil { + return err + } + } + } else { + names := getStrings(len(obj)) + var i int + for name := range obj { + (*names)[i] = name + i++ + } + names.Sort() + for _, name := range *names { + if err := enc.WriteToken(jsontext.String(name)); err != nil { + return err + } + if err := marshalValueAny(enc, obj[name], mo); err != nil { + return err + } + } + putStrings(names) + } + if err := enc.WriteToken(jsontext.EndObject); err != nil { + return err + } + return nil +} + +// unmarshalObjectAny unmarshals a JSON object as a Go map[string]any. +// It panics if not decoding a JSON object. +func unmarshalObjectAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (map[string]any, error) { + switch tok, err := dec.ReadToken(); { + case err != nil: + return nil, err + case tok.Kind() != '{': + panic("BUG: invalid kind: " + tok.Kind().String()) + } + obj := make(map[string]any) + // A Go map guarantees that each entry has a unique key + // The only possibility of duplicates is due to invalid UTF-8. + if !uo.Flags.Get(jsonflags.AllowInvalidUTF8) { + export.Decoder(dec).Tokens.Last.DisableNamespace() + } + var errUnmarshal error + for dec.PeekKind() != '}' { + tok, err := dec.ReadToken() + if err != nil { + return obj, err + } + name := tok.String() + + // Manually check for duplicate names. + if _, ok := obj[name]; ok { + // TODO: Unread the object name. + name := export.Decoder(dec).PreviousTokenOrValue() + err := newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(name)) + return obj, err + } + + val, err := unmarshalValueAny(dec, uo) + obj[name] = val + if err != nil { + if isFatalError(err, uo.Flags) { + return obj, err + } + errUnmarshal = cmp.Or(err, errUnmarshal) + } + } + if _, err := dec.ReadToken(); err != nil { + return obj, err + } + return obj, errUnmarshal +} + +// marshalArrayAny marshals a Go []any as a JSON array +// (or as a JSON null if nil and [jsonflags.FormatNilSliceAsNull]). +func marshalArrayAny(enc *jsontext.Encoder, arr []any, mo *jsonopts.Struct) error { + // Check for cycles. + xe := export.Encoder(enc) + if xe.Tokens.Depth() > startDetectingCyclesAfter { + v := reflect.ValueOf(arr) + if err := visitPointer(&xe.SeenPointers, v); err != nil { + return newMarshalErrorBefore(enc, sliceAnyType, err) + } + defer leavePointer(&xe.SeenPointers, v) + } + + // Handle empty slices. + if len(arr) == 0 { + if mo.Flags.Get(jsonflags.FormatNilSliceAsNull) && arr == nil { + return enc.WriteToken(jsontext.Null) + } + // Optimize for marshaling an empty slice without any preceding whitespace. + if !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '['), "[]"...) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + } + + if err := enc.WriteToken(jsontext.BeginArray); err != nil { + return err + } + for _, val := range arr { + if err := marshalValueAny(enc, val, mo); err != nil { + return err + } + } + if err := enc.WriteToken(jsontext.EndArray); err != nil { + return err + } + return nil +} + +// unmarshalArrayAny unmarshals a JSON array as a Go []any. +// It panics if not decoding a JSON array. +func unmarshalArrayAny(dec *jsontext.Decoder, uo *jsonopts.Struct) ([]any, error) { + switch tok, err := dec.ReadToken(); { + case err != nil: + return nil, err + case tok.Kind() != '[': + panic("BUG: invalid kind: " + tok.Kind().String()) + } + arr := []any{} + var errUnmarshal error + for dec.PeekKind() != ']' { + val, err := unmarshalValueAny(dec, uo) + arr = append(arr, val) + if err != nil { + if isFatalError(err, uo.Flags) { + return arr, err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + } + if _, err := dec.ReadToken(); err != nil { + return arr, err + } + return arr, errUnmarshal +} diff --git a/src/encoding/json/v2/arshal_default.go b/src/encoding/json/v2/arshal_default.go new file mode 100644 index 0000000000..5ca51c6635 --- /dev/null +++ b/src/encoding/json/v2/arshal_default.go @@ -0,0 +1,1910 @@ +// Copyright 2020 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 + +package json + +import ( + "bytes" + "cmp" + "encoding" + "encoding/base32" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "math" + "reflect" + "slices" + "strconv" + "strings" + "sync" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +// optimizeCommon specifies whether to use optimizations targeted for certain +// common patterns, rather than using the slower, but more general logic. +// All tests should pass regardless of whether this is true or not. +const optimizeCommon = true + +var ( + // Most natural Go type that correspond with each JSON type. + anyType = reflect.TypeFor[any]() // JSON value + boolType = reflect.TypeFor[bool]() // JSON bool + stringType = reflect.TypeFor[string]() // JSON string + float64Type = reflect.TypeFor[float64]() // JSON number + mapStringAnyType = reflect.TypeFor[map[string]any]() // JSON object + sliceAnyType = reflect.TypeFor[[]any]() // JSON array + + bytesType = reflect.TypeFor[[]byte]() + emptyStructType = reflect.TypeFor[struct{}]() +) + +const startDetectingCyclesAfter = 1000 + +type seenPointers = map[any]struct{} + +type typedPointer struct { + typ reflect.Type + ptr any // always stores unsafe.Pointer, but avoids depending on unsafe + len int // remember slice length to avoid false positives +} + +// visitPointer visits pointer p of type t, reporting an error if seen before. +// If successfully visited, then the caller must eventually call leave. +func visitPointer(m *seenPointers, v reflect.Value) error { + p := typedPointer{v.Type(), v.UnsafePointer(), sliceLen(v)} + if _, ok := (*m)[p]; ok { + return internal.ErrCycle + } + if *m == nil { + *m = make(seenPointers) + } + (*m)[p] = struct{}{} + return nil +} +func leavePointer(m *seenPointers, v reflect.Value) { + p := typedPointer{v.Type(), v.UnsafePointer(), sliceLen(v)} + delete(*m, p) +} + +func sliceLen(v reflect.Value) int { + if v.Kind() == reflect.Slice { + return v.Len() + } + return 0 +} + +func len64[Bytes ~[]byte | ~string](in Bytes) int64 { + return int64(len(in)) +} + +func makeDefaultArshaler(t reflect.Type) *arshaler { + switch t.Kind() { + case reflect.Bool: + return makeBoolArshaler(t) + case reflect.String: + return makeStringArshaler(t) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return makeIntArshaler(t) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return makeUintArshaler(t) + case reflect.Float32, reflect.Float64: + return makeFloatArshaler(t) + case reflect.Map: + return makeMapArshaler(t) + case reflect.Struct: + return makeStructArshaler(t) + case reflect.Slice: + fncs := makeSliceArshaler(t) + if t.Elem().Kind() == reflect.Uint8 { + return makeBytesArshaler(t, fncs) + } + return fncs + case reflect.Array: + fncs := makeArrayArshaler(t) + if t.Elem().Kind() == reflect.Uint8 { + return makeBytesArshaler(t, fncs) + } + return fncs + case reflect.Pointer: + return makePointerArshaler(t) + case reflect.Interface: + return makeInterfaceArshaler(t) + default: + return makeInvalidArshaler(t) + } +} + +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) + } + + // Optimize for marshaling without preceding whitespace. + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = strconv.AppendBool(xe.Tokens.MayAppendDelim(xe.Buf, 't'), va.Bool()) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + + if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { + if va.Bool() { + return enc.WriteToken(jsontext.String("true")) + } else { + return enc.WriteToken(jsontext.String("false")) + } + } + 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) + } + tok, err := dec.ReadToken() + if err != nil { + return err + } + k := tok.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetBool(false) + } + return nil + case 't', 'f': + if !uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { + va.SetBool(tok.Bool()) + return nil + } + case '"': + if uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { + switch tok.String() { + case "true": + va.SetBool(true) + case "false": + va.SetBool(false) + default: + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && tok.String() == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetBool(false) + } + return nil + } + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) + } + return nil + } + } + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) + } + return &fncs +} + +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) + } + + // Optimize for marshaling without preceding whitespace. + s := va.String() + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyBoolsAndStrings) && !xe.Tokens.Last.NeedObjectName() { + b := xe.Buf + b = xe.Tokens.MayAppendDelim(b, '"') + b, err := jsonwire.AppendQuote(b, s, &mo.Flags) + if err == nil { + xe.Buf = b + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + // Otherwise, the string contains invalid UTF-8, + // so let the logic below construct the proper error. + } + + if mo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { + b, err := jsonwire.AppendQuote(nil, s, &mo.Flags) + if err != nil { + return newMarshalErrorBefore(enc, t, &jsontext.SyntacticError{Err: err}) + } + q, err := jsontext.AppendQuote(nil, b) + if err != nil { + panic("BUG: second AppendQuote should never fail: " + err.Error()) + } + return enc.WriteValue(q) + } + return enc.WriteToken(jsontext.String(s)) + } + 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 flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + k := val.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetString("") + } + return nil + case '"': + val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if uo.Flags.Get(jsonflags.StringifyBoolsAndStrings) { + val, err = jsontext.AppendUnquote(nil, val) + if err != nil { + return newUnmarshalErrorAfter(dec, t, err) + } + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetString("") + } + return nil + } + } + if xd.StringCache == nil { + xd.StringCache = new(stringCache) + } + str := makeString(xd.StringCache, val) + va.SetString(str) + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + return &fncs +} + +var ( + appendEncodeBase16 = hex.AppendEncode + appendEncodeBase32 = base32.StdEncoding.AppendEncode + appendEncodeBase32Hex = base32.HexEncoding.AppendEncode + appendEncodeBase64 = base64.StdEncoding.AppendEncode + appendEncodeBase64URL = base64.URLEncoding.AppendEncode + encodedLenBase16 = hex.EncodedLen + encodedLenBase32 = base32.StdEncoding.EncodedLen + encodedLenBase32Hex = base32.HexEncoding.EncodedLen + encodedLenBase64 = base64.StdEncoding.EncodedLen + encodedLenBase64URL = base64.URLEncoding.EncodedLen + appendDecodeBase16 = hex.AppendDecode + appendDecodeBase32 = base32.StdEncoding.AppendDecode + appendDecodeBase32Hex = base32.HexEncoding.AppendDecode + appendDecodeBase64 = base64.StdEncoding.AppendDecode + appendDecodeBase64URL = base64.URLEncoding.AppendDecode +) + +func makeBytesArshaler(t reflect.Type, fncs *arshaler) *arshaler { + // NOTE: This handles both []~byte and [N]~byte. + // The v2 default is to treat a []namedByte as equivalent to []T + // since being able to convert []namedByte to []byte relies on + // dubious Go reflection behavior (see https://go.dev/issue/24746). + // For v1 emulation, we use jsonflags.FormatBytesWithLegacySemantics + // to forcibly treat []namedByte as a []byte. + marshalArray := fncs.marshal + isNamedByte := t.Elem().PkgPath() != "" + hasMarshaler := implementsAny(t.Elem(), allMarshalerTypes...) + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + if !mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && isNamedByte { + return marshalArray(enc, va, mo) // treat as []T or [N]T + } + 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 = "" + return marshalArray(enc, va, mo) + default: + return newInvalidFormatError(enc, t, mo) + } + } else if mo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && + (va.Kind() == reflect.Array || 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) + } + return xe.AppendRaw('"', true, func(b []byte) ([]byte, error) { + return appendEncode(b, va.Bytes()), nil + }) + } + unmarshalArray := fncs.unmarshal + fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + if !uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && isNamedByte { + return unmarshalArray(dec, va, uo) // treat as []T or [N]T + } + 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 = "" + return unmarshalArray(dec, va, uo) + default: + return newInvalidFormatError(dec, t, uo) + } + } else if uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) && + (va.Kind() == reflect.Array || dec.PeekKind() == '[') { + return unmarshalArray(dec, va, uo) + } + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + k := val.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) || va.Kind() != reflect.Array { + va.SetZero() + } + return nil + case '"': + // NOTE: The v2 default is to strictly comply with RFC 4648. + // Section 3.2 specifies that padding is required. + // Section 3.3 specifies that non-alphabet characters + // (e.g., '\r' or '\n') must be rejected. + // Section 3.5 specifies that unnecessary non-zero bits in + // the last quantum may be rejected. Since this is optional, + // we do not reject such inputs. + val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + b, err := appendDecode(va.Bytes()[:0], val) + if err != nil { + return newUnmarshalErrorAfter(dec, t, err) + } + if len(val) != encodedLen(len(b)) && !uo.Flags.Get(jsonflags.FormatBytesWithLegacySemantics) { + // TODO(https://go.dev/issue/53845): RFC 4648, section 3.3, + // specifies that non-alphabet characters must be rejected. + // Unfortunately, the "base32" and "base64" packages allow + // '\r' and '\n' characters by default. + i := bytes.IndexAny(val, "\r\n") + err := fmt.Errorf("illegal character %s at offset %d", jsonwire.QuoteRune(val[i:]), i) + return newUnmarshalErrorAfter(dec, t, err) + } + + if va.Kind() == reflect.Array { + dst := va.Bytes() + 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) + } + } else { + if b == nil { + b = []byte{} + } + va.SetBytes(b) + } + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + return fncs +} + +func makeIntArshaler(t reflect.Type) *arshaler { + var fncs 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) + } + + // Optimize for marshaling without preceding whitespace or string escaping. + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyNumbers) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = strconv.AppendInt(xe.Tokens.MayAppendDelim(xe.Buf, '0'), va.Int(), 10) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + + k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) + 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) + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + k := val.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetInt(0) + } + return nil + case '"': + if !stringify { + 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) + } + return nil + } + fallthrough + case '0': + if stringify && k == '0' { + break + } + var negOffset int + neg := len(val) > 0 && val[0] == '-' + if neg { + negOffset = 1 + } + n, ok := jsonwire.ParseUint(val[negOffset:]) + maxInt := uint64(1) << (bits - 1) + overflow := (neg && n > maxInt) || (!neg && n > maxInt-1) + if !ok { + if n != math.MaxUint64 { + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) + } + overflow = true + } + if overflow { + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange) + } + if neg { + va.SetInt(int64(-n)) + } else { + va.SetInt(int64(+n)) + } + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + return &fncs +} + +func makeUintArshaler(t reflect.Type) *arshaler { + var fncs 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) + } + + // Optimize for marshaling without preceding whitespace or string escaping. + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyNumbers) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = strconv.AppendUint(xe.Tokens.MayAppendDelim(xe.Buf, '0'), va.Uint(), 10) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + + k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) + 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) + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + k := val.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetUint(0) + } + return nil + case '"': + if !stringify { + 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) + } + return nil + } + fallthrough + case '0': + if stringify && k == '0' { + break + } + n, ok := jsonwire.ParseUint(val) + maxUint := uint64(1) << bits + overflow := n > maxUint-1 + if !ok { + if n != math.MaxUint64 { + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) + } + overflow = true + } + if overflow { + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange) + } + va.SetUint(n) + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + return &fncs +} + +func makeFloatArshaler(t reflect.Type) *arshaler { + var fncs arshaler + bits := t.Bits() + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + xe := export.Encoder(enc) + var allowNonFinite bool + if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() { + if mo.Format == "nonfinite" { + allowNonFinite = true + } else { + return newInvalidFormatError(enc, t, mo) + } + } + + fv := va.Float() + if math.IsNaN(fv) || math.IsInf(fv, 0) { + if !allowNonFinite { + err := fmt.Errorf("unsupported value: %v", fv) + return newMarshalErrorBefore(enc, t, err) + } + return enc.WriteToken(jsontext.Float(fv)) + } + + // Optimize for marshaling without preceding whitespace or string escaping. + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace|jsonflags.StringifyNumbers) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = jsonwire.AppendFloat(xe.Tokens.MayAppendDelim(xe.Buf, '0'), fv, bits) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + + k := stringOrNumberKind(xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) + 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) + var allowNonFinite bool + if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() { + if uo.Format == "nonfinite" { + allowNonFinite = true + } else { + return newInvalidFormatError(dec, t, uo) + } + } + stringify := xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + k := val.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetFloat(0) + } + return nil + case '"': + val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if allowNonFinite { + switch string(val) { + case "NaN": + va.SetFloat(math.NaN()) + return nil + case "Infinity": + va.SetFloat(math.Inf(+1)) + return nil + case "-Infinity": + va.SetFloat(math.Inf(-1)) + return nil + } + } + if !stringify { + break + } + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetFloat(0) + } + return nil + } + if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil { + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax) + } + fallthrough + case '0': + if stringify && k == '0' { + break + } + fv, ok := jsonwire.ParseFloat(val, bits) + va.SetFloat(fv) + if !ok { + return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange) + } + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + return &fncs +} + +func makeMapArshaler(t reflect.Type) *arshaler { + // NOTE: The logic below disables namespaces for tracking duplicate names + // when handling map keys with a unique representation. + + // NOTE: Values retrieved from a map are not addressable, + // so we shallow copy the values to make them addressable and + // store them back into the map afterwards. + + var fncs arshaler + var ( + once sync.Once + keyFncs *arshaler + valFncs *arshaler + ) + init := func() { + keyFncs = lookupArshaler(t.Key()) + valFncs = lookupArshaler(t.Elem()) + } + nillableLegacyKey := t.Key().Kind() == reflect.Pointer && + implementsAny(t.Key(), textMarshalerType, textAppenderType) + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + // Check for cycles. + xe := export.Encoder(enc) + if xe.Tokens.Depth() > startDetectingCyclesAfter { + if err := visitPointer(&xe.SeenPointers, va.Value); err != nil { + return newMarshalErrorBefore(enc, t, err) + } + defer leavePointer(&xe.SeenPointers, va.Value) + } + + 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) + } + } + + // Handle empty maps. + n := va.Len() + if n == 0 { + if emitNull && va.IsNil() { + return enc.WriteToken(jsontext.Null) + } + // Optimize for marshaling an empty map without any preceding whitespace. + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '{'), "{}"...) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + } + + once.Do(init) + if err := enc.WriteToken(jsontext.BeginObject); err != nil { + return err + } + if n > 0 { + nonDefaultKey := keyFncs.nonDefault + marshalKey := keyFncs.marshal + marshalVal := valFncs.marshal + if mo.Marshalers != nil { + var ok bool + marshalKey, ok = mo.Marshalers.(*Marshalers).lookup(marshalKey, t.Key()) + marshalVal, _ = mo.Marshalers.(*Marshalers).lookup(marshalVal, t.Elem()) + nonDefaultKey = nonDefaultKey || ok + } + k := newAddressableValue(t.Key()) + v := newAddressableValue(t.Elem()) + + // A Go map guarantees that each entry has a unique key. + // As such, disable the expensive duplicate name check if we know + // that every Go key will serialize as a unique JSON string. + if !nonDefaultKey && mapKeyWithUniqueRepresentation(k.Kind(), mo.Flags.Get(jsonflags.AllowInvalidUTF8)) { + xe.Tokens.Last.DisableNamespace() + } + + switch { + case !mo.Flags.Get(jsonflags.Deterministic) || n <= 1: + for iter := va.Value.MapRange(); iter.Next(); { + k.SetIterKey(iter) + err := marshalKey(enc, k, mo) + if err != nil { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + errors.Is(err, jsontext.ErrNonStringName) && nillableLegacyKey && k.IsNil() { + err = enc.WriteToken(jsontext.String("")) + } + if err != nil { + if serr, ok := err.(*jsontext.SyntacticError); ok && serr.Err == jsontext.ErrNonStringName { + err = newMarshalErrorBefore(enc, k.Type(), err) + } + return err + } + } + v.SetIterValue(iter) + if err := marshalVal(enc, v, mo); err != nil { + return err + } + } + case !nonDefaultKey && t.Key().Kind() == reflect.String: + names := getStrings(n) + for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ { + k.SetIterKey(iter) + (*names)[i] = k.String() + } + names.Sort() + for _, name := range *names { + if err := enc.WriteToken(jsontext.String(name)); err != nil { + return err + } + // TODO(https://go.dev/issue/57061): Use v.SetMapIndexOf. + k.SetString(name) + v.Set(va.MapIndex(k.Value)) + if err := marshalVal(enc, v, mo); err != nil { + return err + } + } + putStrings(names) + default: + type member struct { + name string // unquoted name + key addressableValue + val addressableValue + } + members := make([]member, n) + keys := reflect.MakeSlice(reflect.SliceOf(t.Key()), n, n) + vals := reflect.MakeSlice(reflect.SliceOf(t.Elem()), n, n) + for i, iter := 0, va.Value.MapRange(); i < n && iter.Next(); i++ { + // Marshal the member name. + k := addressableValue{keys.Index(i), true} // indexed slice element is always addressable + k.SetIterKey(iter) + v := addressableValue{vals.Index(i), true} // indexed slice element is always addressable + v.SetIterValue(iter) + err := marshalKey(enc, k, mo) + if err != nil { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + errors.Is(err, jsontext.ErrNonStringName) && nillableLegacyKey && k.IsNil() { + err = enc.WriteToken(jsontext.String("")) + } + if err != nil { + if serr, ok := err.(*jsontext.SyntacticError); ok && serr.Err == jsontext.ErrNonStringName { + err = newMarshalErrorBefore(enc, k.Type(), err) + } + return err + } + } + name := xe.UnwriteOnlyObjectMemberName() + members[i] = member{name, k, v} + } + // TODO: If AllowDuplicateNames is enabled, then sort according + // to reflect.Value as well if the names are equal. + // See internal/fmtsort. + slices.SortFunc(members, func(x, y member) int { + return strings.Compare(x.name, y.name) + }) + for _, member := range members { + if err := enc.WriteToken(jsontext.String(member.name)); err != nil { + return err + } + if err := marshalVal(enc, member.val, mo); err != nil { + return err + } + } + } + } + if err := enc.WriteToken(jsontext.EndObject); err != nil { + return err + } + 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() { + switch uo.Format { + case "emitnull", "emitempty": + uo.Format = "" // only relevant for marshaling + default: + return newInvalidFormatError(dec, t, uo) + } + } + tok, err := dec.ReadToken() + if err != nil { + return err + } + k := tok.Kind() + switch k { + case 'n': + va.SetZero() + return nil + case '{': + once.Do(init) + if va.IsNil() { + va.Set(reflect.MakeMap(t)) + } + + nonDefaultKey := keyFncs.nonDefault + unmarshalKey := keyFncs.unmarshal + unmarshalVal := valFncs.unmarshal + if uo.Unmarshalers != nil { + var ok bool + unmarshalKey, ok = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshalKey, t.Key()) + unmarshalVal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshalVal, t.Elem()) + nonDefaultKey = nonDefaultKey || ok + } + k := newAddressableValue(t.Key()) + v := newAddressableValue(t.Elem()) + + // Manually check for duplicate entries by virtue of whether the + // unmarshaled key already exists in the destination Go map. + // Consequently, syntactically different names (e.g., "0" and "-0") + // will be rejected as duplicates since they semantically refer + // to the same Go value. This is an unusual interaction + // between syntax and semantics, but is more correct. + if !nonDefaultKey && mapKeyWithUniqueRepresentation(k.Kind(), uo.Flags.Get(jsonflags.AllowInvalidUTF8)) { + xd.Tokens.Last.DisableNamespace() + } + + // In the rare case where the map is not already empty, + // then we need to manually track which keys we already saw + // since existing presence alone is insufficient to indicate + // whether the input had a duplicate name. + var seen reflect.Value + if !uo.Flags.Get(jsonflags.AllowDuplicateNames) && va.Len() > 0 { + seen = reflect.MakeMap(reflect.MapOf(k.Type(), emptyStructType)) + } + + var errUnmarshal error + for dec.PeekKind() != '}' { + // Unmarshal the map entry key. + k.SetZero() + err := unmarshalKey(dec, k, uo) + if err != nil { + if isFatalError(err, uo.Flags) { + return err + } + if err := dec.SkipValue(); err != nil { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + continue + } + if k.Kind() == reflect.Interface && !k.IsNil() && !k.Elem().Type().Comparable() { + err := newUnmarshalErrorAfter(dec, t, fmt.Errorf("invalid incomparable key type %v", k.Elem().Type())) + if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err + } + if err2 := dec.SkipValue(); err2 != nil { + return err2 + } + errUnmarshal = cmp.Or(errUnmarshal, err) + continue + } + + // Check if a pre-existing map entry value exists for this key. + if v2 := va.MapIndex(k.Value); v2.IsValid() { + if !uo.Flags.Get(jsonflags.AllowDuplicateNames) && (!seen.IsValid() || seen.MapIndex(k.Value).IsValid()) { + // TODO: Unread the object name. + name := xd.PreviousTokenOrValue() + return newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(name)) + } + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + v.Set(v2) + } else { + v.SetZero() + } + } else { + v.SetZero() + } + + // Unmarshal the map entry value. + err = unmarshalVal(dec, v, uo) + va.SetMapIndex(k.Value, v.Value) + if seen.IsValid() { + seen.SetMapIndex(k.Value, reflect.Zero(emptyStructType)) + } + if err != nil { + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + } + if _, err := dec.ReadToken(); err != nil { + return err + } + return errUnmarshal + } + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) + } + return &fncs +} + +// 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. +func mapKeyWithUniqueRepresentation(k reflect.Kind, allowInvalidUTF8 bool) bool { + switch k { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + 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. + return !allowInvalidUTF8 + default: + // Floating-point kinds are not listed above since NaNs + // can appear multiple times and all serialize as "NaN". + return false + } +} + +var errNilField = errors.New("cannot set embedded pointer to unexported struct type") + +func makeStructArshaler(t reflect.Type) *arshaler { + // NOTE: The logic below disables namespaces for tracking duplicate names + // and does the tracking locally with an efficient bit-set based on which + // Go struct fields were seen. + + var fncs arshaler + var ( + once sync.Once + fields structFields + errInit *SemanticError + ) + init := func() { + fields, errInit = makeStructFields(t) + } + 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) + } + once.Do(init) + if errInit != nil && !mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return newMarshalErrorBefore(enc, errInit.GoType, errInit.Err) + } + if err := enc.WriteToken(jsontext.BeginObject); err != nil { + return err + } + var seenIdxs uintSet + prevIdx := -1 + xe.Tokens.Last.DisableNamespace() // we manually ensure unique names below + for i := range fields.flattened { + f := &fields.flattened[i] + 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() { + continue // implies a nil inlined field + } + } + + // OmitZero skips the field if the Go value is zero, + // which we can determine up front without calling the marshaler. + if (f.omitzero || mo.Flags.Get(jsonflags.OmitZeroStructFields)) && + ((f.isZero == nil && v.IsZero()) || (f.isZero != nil && f.isZero(v))) { + continue + } + + // Check for the legacy definition of omitempty. + if f.omitempty && mo.Flags.Get(jsonflags.OmitEmptyWithLegacyDefinition) && isLegacyEmpty(v) { + continue + } + + marshal := f.fncs.marshal + nonDefault := f.fncs.nonDefault + if mo.Marshalers != nil { + var ok bool + marshal, ok = mo.Marshalers.(*Marshalers).lookup(marshal, f.typ) + nonDefault = nonDefault || ok + } + + // OmitEmpty skips the field if the marshaled JSON value is empty, + // which we can know up front if there are no custom marshalers, + // otherwise we must marshal the value and unwrite it if empty. + if f.omitempty && !mo.Flags.Get(jsonflags.OmitEmptyWithLegacyDefinition) && + !nonDefault && f.isEmpty != nil && f.isEmpty(v) { + continue // fast path for omitempty + } + + // Write the object member name. + // + // The logic below is semantically equivalent to: + // enc.WriteToken(String(f.name)) + // but specialized and simplified because: + // 1. The Encoder must be expecting an object name. + // 2. The object namespace is guaranteed to be disabled. + // 3. The object name is guaranteed to be valid and pre-escaped. + // 4. There is no need to flush the buffer (for unwrite purposes). + // 5. There is no possibility of an error occurring. + if optimizeCommon { + // Append any delimiters or optional whitespace. + b := xe.Buf + if xe.Tokens.Last.Length() > 0 { + b = append(b, ',') + if mo.Flags.Get(jsonflags.SpaceAfterComma) { + b = append(b, ' ') + } + } + if mo.Flags.Get(jsonflags.Multiline) { + b = xe.AppendIndent(b, xe.Tokens.NeedIndent('"')) + } + + // Append the token to the output and to the state machine. + n0 := len(b) // offset before calling AppendQuote + if !f.nameNeedEscape { + b = append(b, f.quotedName...) + } else { + b, _ = jsonwire.AppendQuote(b, f.name, &mo.Flags) + } + xe.Buf = b + xe.Names.ReplaceLastQuotedOffset(n0) + xe.Tokens.Last.Increment() + } else { + if err := enc.WriteToken(jsontext.String(f.name)); err != nil { + return err + } + } + + // 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) + } + } + if f.format != "" { + mo.FormatDepth = xe.Tokens.Depth() + mo.Format = f.format + } + err := marshal(enc, v, mo) + mo.Flags = flagsOriginal + mo.Format = "" + if err != nil { + return err + } + + // Try unwriting the member if empty (slow path for omitempty). + if f.omitempty && !mo.Flags.Get(jsonflags.OmitEmptyWithLegacyDefinition) { + var prevName *string + if prevIdx >= 0 { + prevName = &fields.flattened[prevIdx].name + } + if xe.UnwriteEmptyObjectMember(prevName) { + continue + } + } + + // 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 { + seenIdxs.insert(uint(f.id)) + } + prevIdx = f.id + } + if fields.inlinedFallback != nil && !(mo.Flags.Get(jsonflags.DiscardUnknownMembers) && fields.inlinedFallback.unknown) { + 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 + // 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 { + return seenIdxs.insert(uint(f.id)) + } + for _, f := range foldedFields { + if f.matchFoldedName(name, &mo.Flags) { + return seenIdxs.insert(uint(f.id)) + } + } + } + + // Check that the name does not match any other name + // previously marshaled from the inlined fallback. + return xe.Namespaces.Last().InsertUnquoted(name) + } + } + if err := marshalInlinedFallbackAll(enc, va, mo, fields.inlinedFallback, insertUnquotedName); err != nil { + return err + } + } + if err := enc.WriteToken(jsontext.EndObject); err != nil { + return err + } + 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) + } + tok, err := dec.ReadToken() + if err != nil { + return err + } + k := tok.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetZero() + } + return nil + case '{': + once.Do(init) + if errInit != nil && !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return newUnmarshalErrorAfter(dec, errInit.GoType, errInit.Err) + } + var seenIdxs uintSet + xd.Tokens.Last.DisableNamespace() + var errUnmarshal error + for dec.PeekKind() != '}' { + // Process the object member name. + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + name := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + f := fields.byActualName[string(name)] + if f == nil { + for _, f2 := range fields.lookupByFoldedName(name) { + if f2.matchFoldedName(name, &uo.Flags) { + f = f2 + break + } + } + if f == nil { + if uo.Flags.Get(jsonflags.RejectUnknownMembers) && (fields.inlinedFallback == nil || fields.inlinedFallback.unknown) { + err := newUnmarshalErrorAfter(dec, t, ErrUnknownName) + if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + if !uo.Flags.Get(jsonflags.AllowDuplicateNames) && !xd.Namespaces.Last().InsertUnquoted(name) { + // TODO: Unread the object name. + return newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(val)) + } + + if fields.inlinedFallback == 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 { + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + } + continue + } + } + if !uo.Flags.Get(jsonflags.AllowDuplicateNames) && !seenIdxs.insert(uint(f.id)) { + // TODO: Unread the object name. + return newDuplicateNameError(dec.StackPointer(), nil, dec.InputOffset()-len64(val)) + } + + // Process the object member value. + unmarshal := f.fncs.unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, f.typ) + } + 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) + } + } + if f.format != "" { + uo.FormatDepth = xd.Tokens.Depth() + uo.Format = f.format + } + 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) + if !v.IsValid() { + err := newUnmarshalErrorBefore(dec, t, errNilField) + if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + unmarshal = func(dec *jsontext.Decoder, _ addressableValue, _ *jsonopts.Struct) error { + return dec.SkipValue() + } + } + } + err = unmarshal(dec, v, uo) + uo.Flags = flagsOriginal + uo.Format = "" + if err != nil { + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + } + if _, err := dec.ReadToken(); err != nil { + return err + } + return errUnmarshal + } + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) + } + return &fncs +} + +func (va addressableValue) fieldByIndex(index []int, mayAlloc bool) addressableValue { + for _, i := range index { + va = va.indirect(mayAlloc) + if !va.IsValid() { + return va + } + va = addressableValue{va.Field(i), va.forcedAddr} // addressable if struct value is addressable + } + return va +} + +func (va addressableValue) indirect(mayAlloc bool) addressableValue { + if va.Kind() == reflect.Pointer { + if va.IsNil() { + if !mayAlloc || !va.CanSet() { + return addressableValue{} + } + va.Set(reflect.New(va.Type().Elem())) + } + va = addressableValue{va.Elem(), false} // dereferenced pointer is always addressable + } + return va +} + +// isLegacyEmpty reports whether a value is empty according to the v1 definition. +func isLegacyEmpty(v addressableValue) bool { + // Equivalent to encoding/json.isEmptyValue@v1.21.0. + switch v.Kind() { + case reflect.Bool: + return v.Bool() == false + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.String, reflect.Map, reflect.Slice, reflect.Array: + return v.Len() == 0 + case reflect.Pointer, reflect.Interface: + return v.IsNil() + } + 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 ( + once sync.Once + valFncs *arshaler + ) + init := func() { + valFncs = lookupArshaler(t.Elem()) + } + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + // Check for cycles. + xe := export.Encoder(enc) + if xe.Tokens.Depth() > startDetectingCyclesAfter { + if err := visitPointer(&xe.SeenPointers, va.Value); err != nil { + return newMarshalErrorBefore(enc, t, err) + } + defer leavePointer(&xe.SeenPointers, va.Value) + } + + 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) + } + } + + // Handle empty slices. + n := va.Len() + if n == 0 { + if emitNull && va.IsNil() { + return enc.WriteToken(jsontext.Null) + } + // Optimize for marshaling an empty slice without any preceding whitespace. + if optimizeCommon && !mo.Flags.Get(jsonflags.AnyWhitespace) && !xe.Tokens.Last.NeedObjectName() { + xe.Buf = append(xe.Tokens.MayAppendDelim(xe.Buf, '['), "[]"...) + xe.Tokens.Last.Increment() + if xe.NeedFlush() { + return xe.Flush() + } + return nil + } + } + + once.Do(init) + if err := enc.WriteToken(jsontext.BeginArray); err != nil { + return err + } + marshal := valFncs.marshal + if mo.Marshalers != nil { + marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t.Elem()) + } + for i := range n { + v := addressableValue{va.Index(i), false} // indexed slice element is always addressable + if err := marshal(enc, v, mo); err != nil { + return err + } + } + if err := enc.WriteToken(jsontext.EndArray); err != nil { + return err + } + return nil + } + 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) + } + } + + tok, err := dec.ReadToken() + if err != nil { + return err + } + k := tok.Kind() + switch k { + case 'n': + va.SetZero() + return nil + case '[': + once.Do(init) + unmarshal := valFncs.unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t.Elem()) + } + mustZero := true // we do not know the cleanliness of unused capacity + cap := va.Cap() + if cap > 0 { + va.SetLen(cap) + } + var i int + var errUnmarshal error + for dec.PeekKind() != ']' { + if i == cap { + va.Value.Grow(1) + cap = va.Cap() + va.SetLen(cap) + mustZero = false // reflect.Value.Grow ensures new capacity is zero-initialized + } + v := addressableValue{va.Index(i), false} // indexed slice element is always addressable + i++ + if mustZero && !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + v.SetZero() + } + if err := unmarshal(dec, v, uo); err != nil { + if isFatalError(err, uo.Flags) { + va.SetLen(i) + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + } + if i == 0 { + va.Set(emptySlice) + } else { + va.SetLen(i) + } + if _, err := dec.ReadToken(); err != nil { + return err + } + return errUnmarshal + } + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) + } + return &fncs +} + +var errArrayUnderflow = errors.New("too few array elements") +var errArrayOverflow = errors.New("too many array elements") + +func makeArrayArshaler(t reflect.Type) *arshaler { + var fncs arshaler + var ( + once sync.Once + valFncs *arshaler + ) + init := func() { + valFncs = lookupArshaler(t.Elem()) + } + 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) + } + once.Do(init) + if err := enc.WriteToken(jsontext.BeginArray); err != nil { + return err + } + marshal := valFncs.marshal + if mo.Marshalers != nil { + marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t.Elem()) + } + for i := range n { + v := addressableValue{va.Index(i), va.forcedAddr} // indexed array element is addressable if array is addressable + if err := marshal(enc, v, mo); err != nil { + return err + } + } + if err := enc.WriteToken(jsontext.EndArray); err != nil { + return err + } + 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) + } + tok, err := dec.ReadToken() + if err != nil { + return err + } + k := tok.Kind() + switch k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetZero() + } + return nil + case '[': + once.Do(init) + unmarshal := valFncs.unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t.Elem()) + } + var i int + var errUnmarshal error + for dec.PeekKind() != ']' { + if i >= n { + if err := dec.SkipValue(); err != nil { + return err + } + err = errArrayOverflow + continue + } + v := addressableValue{va.Index(i), va.forcedAddr} // indexed array element is addressable if array is addressable + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + v.SetZero() + } + if err := unmarshal(dec, v, uo); err != nil { + if isFatalError(err, uo.Flags) { + return err + } + errUnmarshal = cmp.Or(errUnmarshal, err) + } + i++ + } + for ; i < n; i++ { + va.Index(i).SetZero() + err = errArrayUnderflow + } + if _, err := dec.ReadToken(); err != nil { + return err + } + if err != nil && !uo.Flags.Get(jsonflags.UnmarshalArrayFromAnyLength) { + return newUnmarshalErrorAfter(dec, t, err) + } + return errUnmarshal + } + return newUnmarshalErrorAfterWithSkipping(dec, uo, t, nil) + } + return &fncs +} + +func makePointerArshaler(t reflect.Type) *arshaler { + var fncs arshaler + var ( + once sync.Once + valFncs *arshaler + ) + init := func() { + valFncs = lookupArshaler(t.Elem()) + } + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + // Check for cycles. + xe := export.Encoder(enc) + if xe.Tokens.Depth() > startDetectingCyclesAfter { + if err := visitPointer(&xe.SeenPointers, va.Value); err != nil { + return newMarshalErrorBefore(enc, t, err) + } + defer leavePointer(&xe.SeenPointers, va.Value) + } + + // NOTE: Struct.Format is forwarded to underlying marshal. + if va.IsNil() { + return enc.WriteToken(jsontext.Null) + } + once.Do(init) + marshal := valFncs.marshal + if mo.Marshalers != nil { + marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, t.Elem()) + } + v := addressableValue{va.Elem(), false} // dereferenced pointer is always addressable + return marshal(enc, v, mo) + } + fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + // NOTE: Struct.Format is forwarded to underlying unmarshal. + if dec.PeekKind() == 'n' { + if _, err := dec.ReadToken(); err != nil { + return err + } + va.SetZero() + return nil + } + once.Do(init) + unmarshal := valFncs.unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, t.Elem()) + } + if va.IsNil() { + va.Set(reflect.New(t.Elem())) + } + v := addressableValue{va.Elem(), false} // dereferenced pointer is always addressable + if err := unmarshal(dec, v, uo); err != nil { + return err + } + if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && + uo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringifyBoolsAndStrings) { + // A JSON null quoted within a JSON string should take effect + // within the pointer value, rather than the indirect value. + // + // TODO: This does not correctly handle escaped nulls + // (e.g., "\u006e\u0075\u006c\u006c"), but is good enough + // for such an esoteric use case of the `string` option. + if string(export.Decoder(dec).PreviousTokenOrValue()) == `"null"` { + va.SetZero() + } + } + return nil + } + return &fncs +} + +var errNilInterface = errors.New("cannot derive concrete type for nil interface with finite type set") + +func makeInterfaceArshaler(t reflect.Type) *arshaler { + // NOTE: Values retrieved from an interface are not addressable, + // so we shallow copy the values to make them addressable and + // store them back into the interface afterwards. + + var fncs arshaler + var whichMarshaler reflect.Type + for _, iface := range allMarshalerTypes { + if t.Implements(iface) { + whichMarshaler = t + break + } + } + 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 va.IsNil() { + return enc.WriteToken(jsontext.Null) + } else if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && whichMarshaler != nil { + // The marshaler for a pointer never calls the method on a nil receiver. + // Wrap the nil pointer within a struct type so that marshal + // instead appears on a value receiver and may be called. + if va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil() { + v2 := newAddressableValue(whichMarshaler) + switch whichMarshaler { + case jsonMarshalerToType: + v2.Set(reflect.ValueOf(struct{ MarshalerTo }{va.Elem().Interface().(MarshalerTo)})) + case jsonMarshalerType: + v2.Set(reflect.ValueOf(struct{ Marshaler }{va.Elem().Interface().(Marshaler)})) + case textAppenderType: + v2.Set(reflect.ValueOf(struct{ encoding.TextAppender }{va.Elem().Interface().(encoding.TextAppender)})) + case textMarshalerType: + v2.Set(reflect.ValueOf(struct{ encoding.TextMarshaler }{va.Elem().Interface().(encoding.TextMarshaler)})) + } + va = v2 + } + } + v := newAddressableValue(va.Elem().Type()) + v.Set(va.Elem()) + marshal := lookupArshaler(v.Type()).marshal + if mo.Marshalers != nil { + marshal, _ = mo.Marshalers.(*Marshalers).lookup(marshal, v.Type()) + } + // Optimize for the any type if there are no special options. + if optimizeCommon && + t == anyType && !mo.Flags.Get(jsonflags.StringifyNumbers|jsonflags.StringifyBoolsAndStrings) && mo.Format == "" && + (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.Get(jsonflags.MergeWithLegacySemantics) && !va.IsNil() { + // Legacy merge behavior is difficult to explain. + // In general, it only merges for non-nil pointer kinds. + // As a special case, unmarshaling a JSON null into a pointer + // sets a concrete nil pointer of the underlying type + // (rather than setting the interface value itself to nil). + e := va.Elem() + if e.Kind() == reflect.Pointer && !e.IsNil() { + if dec.PeekKind() == 'n' && e.Elem().Kind() == reflect.Pointer { + if _, err := dec.ReadToken(); err != nil { + return err + } + va.Elem().Elem().SetZero() + return nil + } + } else { + va.SetZero() + } + } + if dec.PeekKind() == 'n' { + if _, err := dec.ReadToken(); err != nil { + return err + } + va.SetZero() + return nil + } + var v addressableValue + if va.IsNil() { + // Optimize for the any type if there are no special options. + // We do not care about stringified numbers since JSON strings + // are always unmarshaled into an any value as Go strings. + // Duplicate name check must be enforced since unmarshalValueAny + // does not implement merge semantics. + if optimizeCommon && + t == anyType && !uo.Flags.Get(jsonflags.AllowDuplicateNames) && uo.Format == "" && + (uo.Unmarshalers == nil || !uo.Unmarshalers.(*Unmarshalers).fromAny) { + v, err := unmarshalValueAny(dec, uo) + // We must check for nil interface values up front. + // See https://go.dev/issue/52310. + if v != nil { + va.Set(reflect.ValueOf(v)) + } + return err + } + + k := dec.PeekKind() + if !isAnyType(t) { + return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errNilInterface) + } + switch k { + case 'f', 't': + v = newAddressableValue(boolType) + case '"': + v = newAddressableValue(stringType) + case '0': + if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) { + v = addressableValue{reflect.ValueOf(internal.NewRawNumber()).Elem(), true} + } else { + v = newAddressableValue(float64Type) + } + case '{': + v = newAddressableValue(mapStringAnyType) + case '[': + v = newAddressableValue(sliceAnyType) + default: + // If k is invalid (e.g., due to an I/O or syntax error), then + // that will be cached by PeekKind and returned by ReadValue. + // If k is '}' or ']', then ReadValue must error since + // those are invalid kinds at the start of a JSON value. + _, err := dec.ReadValue() + return err + } + } else { + // Shallow copy the existing value to keep it addressable. + // Any mutations at the top-level of the value will be observable + // since we always store this value back into the interface value. + v = newAddressableValue(va.Elem().Type()) + v.Set(va.Elem()) + } + unmarshal := lookupArshaler(v.Type()).unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, v.Type()) + } + err := unmarshal(dec, v, uo) + va.Set(v.Value) + return err + } + return &fncs +} + +// isAnyType reports wether 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 + // necessarily mean it can hold every possible Go type. + // See https://go.dev/issue/45346. + return t == anyType || anyType.Implements(t) +} + +func makeInvalidArshaler(t reflect.Type) *arshaler { + var fncs arshaler + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + return newMarshalErrorBefore(enc, t, nil) + } + fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + return newUnmarshalErrorBefore(dec, t, nil) + } + return &fncs +} + +func stringOrNumberKind(isString bool) jsontext.Kind { + if isString { + return '"' + } else { + return '0' + } +} + +type uintSet64 uint64 + +func (s uintSet64) has(i uint) bool { return s&(1< 0 } +func (s *uintSet64) set(i uint) { *s |= 1 << i } + +// uintSet is a set of unsigned integers. +// It is optimized for most integers being close to zero. +type uintSet struct { + lo uintSet64 + hi []uintSet64 +} + +// has reports whether i is in the set. +func (s *uintSet) has(i uint) bool { + if i < 64 { + return s.lo.has(i) + } else { + i -= 64 + iHi, iLo := int(i/64), i%64 + return iHi < len(s.hi) && s.hi[iHi].has(iLo) + } +} + +// insert inserts i into the set and reports whether it was the first insertion. +func (s *uintSet) insert(i uint) bool { + // TODO: Make this inlinable at least for the lower 64-bit case. + if i < 64 { + has := s.lo.has(i) + s.lo.set(i) + return !has + } else { + i -= 64 + iHi, iLo := int(i/64), i%64 + if iHi >= len(s.hi) { + s.hi = append(s.hi, make([]uintSet64, iHi+1-len(s.hi))...) + s.hi = s.hi[:cap(s.hi)] + } + has := s.hi[iHi].has(iLo) + s.hi[iHi].set(iLo) + return !has + } +} diff --git a/src/encoding/json/v2/arshal_funcs.go b/src/encoding/json/v2/arshal_funcs.go new file mode 100644 index 0000000000..5986c54732 --- /dev/null +++ b/src/encoding/json/v2/arshal_funcs.go @@ -0,0 +1,432 @@ +// Copyright 2020 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 + +package json + +import ( + "errors" + "fmt" + "reflect" + "sync" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/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 errNonSingularValue = errors.New("must read or write exactly one value") + +// Marshalers is a list of functions that may override the marshal behavior +// of specific types. Populate [WithMarshalers] to use it with +// [Marshal], [MarshalWrite], or [MarshalEncode]. +// A nil *Marshalers is equivalent to an empty list. +// There are no exported fields or methods on Marshalers. +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, +// otherwise the default marshaling behavior is used. +// +// For example: +// +// m1 := JoinMarshalers(f1, f2) +// m2 := JoinMarshalers(f0, m1, f3) // equivalent to m3 +// m3 := JoinMarshalers(f0, f1, f2, f3) // equivalent to m2 +func JoinMarshalers(ms ...*Marshalers) *Marshalers { + return newMarshalers(ms...) +} + +// Unmarshalers is a list of functions that may override the unmarshal behavior +// of specific types. Populate [WithUnmarshalers] to use it with +// [Unmarshal], [UnmarshalRead], or [UnmarshalDecode]. +// A nil *Unmarshalers is equivalent to an empty list. +// There are no exported fields or methods on Unmarshalers. +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, +// otherwise the default unmarshaling behavior is used. +// +// For example: +// +// u1 := JoinUnmarshalers(f1, f2) +// u2 := JoinUnmarshalers(f0, u1, f3) // equivalent to u3 +// u3 := JoinUnmarshalers(f0, f1, f2, f3) // equivalent to u2 +func JoinUnmarshalers(us ...*Unmarshalers) *Unmarshalers { + return newUnmarshalers(us...) +} + +type typedMarshalers = typedArshalers[jsontext.Encoder] +type typedUnmarshalers = typedArshalers[jsontext.Decoder] +type typedArshalers[Coder any] struct { + nonComparable + + fncVals []typedArshaler[Coder] + fncCache sync.Map // map[reflect.Type]arshaler + + // fromAny reports whether any of Go types used to represent arbitrary JSON + // (i.e., any, bool, string, float64, map[string]any, or []any) matches + // any of the provided type-specific arshalers. + // + // This bit of information is needed in arshal_default.go to determine + // whether to use the specialized logic in arshal_any.go to handle + // the any interface type. The logic in arshal_any.go does not support + // type-specific arshal functions, so we must avoid using that logic + // if this is true. + fromAny bool +} +type typedMarshaler = typedArshaler[jsontext.Encoder] +type typedUnmarshaler = typedArshaler[jsontext.Decoder] +type typedArshaler[Coder any] struct { + typ reflect.Type + fnc func(*Coder, addressableValue, *jsonopts.Struct) error + maySkip bool +} + +func newMarshalers(ms ...*Marshalers) *Marshalers { return newTypedArshalers(ms...) } +func newUnmarshalers(us ...*Unmarshalers) *Unmarshalers { return newTypedArshalers(us...) } +func newTypedArshalers[Coder any](as ...*typedArshalers[Coder]) *typedArshalers[Coder] { + var a typedArshalers[Coder] + for _, a2 := range as { + if a2 != nil { + a.fncVals = append(a.fncVals, a2.fncVals...) + a.fromAny = a.fromAny || a2.fromAny + } + } + if len(a.fncVals) == 0 { + return nil + } + return &a +} + +func (a *typedArshalers[Coder]) lookup(fnc func(*Coder, addressableValue, *jsonopts.Struct) error, t reflect.Type) (func(*Coder, addressableValue, *jsonopts.Struct) error, bool) { + if a == nil { + return fnc, false + } + if v, ok := a.fncCache.Load(t); ok { + if v == nil { + return fnc, false + } + return v.(func(*Coder, addressableValue, *jsonopts.Struct) error), true + } + + // Collect a list of arshalers that can be called for this type. + // This list may be longer than 1 since some arshalers can be skipped. + var fncs []func(*Coder, addressableValue, *jsonopts.Struct) error + for _, fncVal := range a.fncVals { + if !castableTo(t, fncVal.typ) { + continue + } + fncs = append(fncs, fncVal.fnc) + if !fncVal.maySkip { + break // subsequent arshalers will never be called + } + } + + if len(fncs) == 0 { + a.fncCache.Store(t, nil) // nil to indicate that no funcs found + return fnc, false + } + + // Construct an arshaler that may call every applicable arshaler. + fncDefault := fnc + fnc = func(c *Coder, v addressableValue, o *jsonopts.Struct) error { + for _, fnc := range fncs { + if err := fnc(c, v, o); err != SkipFunc { + return err // may be nil or non-nil + } + } + return fncDefault(c, v, o) + } + + // Use the first stored so duplicate work can be garbage collected. + v, _ := a.fncCache.LoadOrStore(t, fnc) + return v.(func(*Coder, addressableValue, *jsonopts.Struct) error), true +} + +// MarshalFunc constructs a type-specific marshaler that +// specifies how to marshal values of type T. +// T can be any type except a named pointer. +// The function is always provided with a non-nil pointer value +// 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]. +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)) + if err != nil { + err = wrapSkipFunc(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 + } + err = newMarshalErrorBefore(enc, t, err) + return collapseSemanticErrors(err) + } + if err := enc.WriteValue(val); err != nil { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalFunc") // unlike unmarshal, always wrapped + } + if isSyntacticError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err + } + return nil + }, + } + return &Marshalers{fncVals: []typedMarshaler{typFnc}, fromAny: castableToFromAny(t)} +} + +// MarshalToFunc constructs a type-specific marshaler that +// specifies how to marshal values of type T. +// T can be any type except a named pointer. +// The function is always provided with a non-nil pointer value +// 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. +func MarshalToFunc[T any](fn func(*jsontext.Encoder, T) error) *Marshalers { + t := reflect.TypeFor[T]() + assertCastableTo(t, true) + typFnc := typedMarshaler{ + typ: t, + fnc: func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + xe := export.Encoder(enc) + prevDepth, prevLength := xe.Tokens.DepthLength() + xe.Flags.Set(jsonflags.WithinArshalCall | 1) + err := fn(enc, va.castTo(t).Interface().(T)) + xe.Flags.Set(jsonflags.WithinArshalCall | 0) + currDepth, currLength := xe.Tokens.DepthLength() + if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) { + err = errNonSingularValue + } + if err != nil { + if err == SkipFunc { + if prevDepth == currDepth && prevLength == currLength { + return SkipFunc + } + err = errSkipMutation + } + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalToFunc") // unlike unmarshal, always wrapped + } + if !export.IsIOError(err) { + err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err) + } + return err + } + return nil + }, + maySkip: true, + } + return &Marshalers{fncVals: []typedMarshaler{typFnc}, fromAny: castableToFromAny(t)} +} + +// UnmarshalFunc constructs a type-specific unmarshaler that +// specifies how to unmarshal values of type T. +// T must be an unnamed pointer or an interface type. +// The function is always provided with a non-nil pointer value. +// +// 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]. +func UnmarshalFunc[T any](fn func([]byte, T) error) *Unmarshalers { + t := reflect.TypeFor[T]() + assertCastableTo(t, false) + typFnc := typedUnmarshaler{ + typ: t, + fnc: func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + val, err := dec.ReadValue() + if err != nil { + return err // must be a syntactic or I/O error + } + err = fn(val, va.castTo(t).Interface().(T)) + if err != nil { + err = wrapSkipFunc(err, "unmarshal function of type func([]byte, T) error") + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err // unlike marshal, never wrapped + } + err = newUnmarshalErrorAfter(dec, t, err) + return collapseSemanticErrors(err) + } + return nil + }, + } + return &Unmarshalers{fncVals: []typedUnmarshaler{typFnc}, fromAny: castableToFromAny(t)} +} + +// UnmarshalFromFunc constructs a type-specific unmarshaler that +// specifies how to unmarshal values of type T. +// T must be an unnamed pointer or an interface type. +// The function is always provided with a non-nil pointer value. +// +// 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. +func UnmarshalFromFunc[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers { + t := reflect.TypeFor[T]() + assertCastableTo(t, false) + typFnc := typedUnmarshaler{ + typ: t, + fnc: func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + xd := export.Decoder(dec) + prevDepth, prevLength := xd.Tokens.DepthLength() + xd.Flags.Set(jsonflags.WithinArshalCall | 1) + err := fn(dec, va.castTo(t).Interface().(T)) + xd.Flags.Set(jsonflags.WithinArshalCall | 0) + currDepth, currLength := xd.Tokens.DepthLength() + if err == nil && (prevDepth != currDepth || prevLength+1 != currLength) { + err = errNonSingularValue + } + if err != nil { + if err == SkipFunc { + if prevDepth == currDepth && prevLength == currLength { + return SkipFunc + } + err = errSkipMutation + } + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { + return err2 + } + return err // unlike marshal, never wrapped + } + if !isSyntacticError(err) && !export.IsIOError(err) { + err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err) + } + return err + } + return nil + }, + maySkip: true, + } + return &Unmarshalers{fncVals: []typedUnmarshaler{typFnc}, fromAny: castableToFromAny(t)} +} + +// assertCastableTo asserts that "to" is a valid type to be casted to. +// These are the Go types that type-specific arshalers may operate upon. +// +// Let AllTypes be the universal set of all possible Go types. +// This function generally asserts that: +// +// len([from for from in AllTypes if castableTo(from, to)]) > 0 +// +// otherwise it panics. +// +// As a special-case if marshal is false, then we forbid any non-pointer or +// non-interface type since it is almost always a bug trying to unmarshal +// into something where the end-user caller did not pass in an addressable value +// since they will not observe the mutations. +func assertCastableTo(to reflect.Type, marshal bool) { + switch to.Kind() { + case reflect.Interface: + return + case reflect.Pointer: + // Only allow unnamed pointers to be consistent with the fact that + // taking the address of a value produces an unnamed pointer type. + if to.Name() == "" { + return + } + default: + // Technically, non-pointer types are permissible for unmarshal. + // However, they are often a bug since the receiver would be immutable. + // Thus, only allow them for marshaling. + if marshal { + return + } + } + if marshal { + panic(fmt.Sprintf("input type %v must be an interface type, an unnamed pointer type, or a non-pointer type", to)) + } else { + panic(fmt.Sprintf("input type %v must be an interface type or an unnamed pointer type", to)) + } +} + +// castableTo checks whether values of type "from" can be casted to type "to". +// Nil pointer or interface "from" values are never considered castable. +// +// This function must be kept in sync with addressableValue.castTo. +func castableTo(from, to reflect.Type) bool { + switch to.Kind() { + case reflect.Interface: + // TODO: This breaks when ordinary interfaces can have type sets + // since interfaces now exist where only the value form of a type (T) + // implements the interface, but not the pointer variant (*T). + // See https://go.dev/issue/45346. + return reflect.PointerTo(from).Implements(to) + case reflect.Pointer: + // Common case for unmarshaling. + // From must be a concrete or interface type. + return reflect.PointerTo(from) == to + default: + // Common case for marshaling. + // From must be a concrete type. + return from == to + } +} + +// castTo casts va to the specified type. +// If the type is an interface, then the underlying type will always +// be a non-nil pointer to a concrete type. +// +// Requirement: castableTo(va.Type(), to) must hold. +func (va addressableValue) castTo(to reflect.Type) reflect.Value { + switch to.Kind() { + case reflect.Interface: + return va.Addr().Convert(to) + case reflect.Pointer: + return va.Addr() + default: + return va.Value + } +} + +// castableToFromAny reports whether "to" can be casted to from any +// of the dynamic types used to represent arbitrary JSON. +func castableToFromAny(to reflect.Type) bool { + for _, from := range []reflect.Type{anyType, boolType, stringType, float64Type, mapStringAnyType, sliceAnyType} { + if castableTo(from, to) { + return true + } + } + return false +} + +func wrapSkipFunc(err error, what string) error { + if err == SkipFunc { + return errors.New(what + " cannot be skipped") + } + return err +} diff --git a/src/encoding/json/v2/arshal_inlined.go b/src/encoding/json/v2/arshal_inlined.go new file mode 100644 index 0000000000..0b5782fdcc --- /dev/null +++ b/src/encoding/json/v2/arshal_inlined.go @@ -0,0 +1,230 @@ +// Copyright 2020 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 + +package json + +import ( + "bytes" + "errors" + "io" + "reflect" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +// This package supports "inlining" 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 +// 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 +// represent any arbitrary JSON object member. Explicitly named fields take +// precedence over the inlined fallback. Only one inlined fallback is allowed. + +var errRawInlinedNotObject = errors.New("inlined raw value must be a JSON object") + +var jsontextValueType = reflect.TypeFor[jsontext.Value]() + +// marshalInlinedFallbackAll marshals all the members in an inlined fallback. +func marshalInlinedFallbackAll(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct, f *structField, insertUnquotedName func([]byte) bool) error { + v := addressableValue{va.Field(f.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 + } + } + v = v.indirect(false) + if !v.IsValid() { + return nil + } + + if v.Type() == jsontextValueType { + // TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo. + b := *v.Addr().Interface().(*jsontext.Value) + if len(b) == 0 { // TODO: Should this be nil? What if it were all whitespace? + return nil + } + + dec := export.GetBufferedDecoder(b) + defer export.PutBufferedDecoder(dec) + xd := export.Decoder(dec) + xd.Flags.Set(jsonflags.AllowDuplicateNames | jsonflags.AllowInvalidUTF8 | 1) + + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return newMarshalErrorBefore(enc, v.Type(), err) + } + if tok.Kind() != '{' { + return newMarshalErrorBefore(enc, v.Type(), errRawInlinedNotObject) + } + for dec.PeekKind() != '}' { + // Parse the JSON object name. + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return newMarshalErrorBefore(enc, v.Type(), err) + } + if insertUnquotedName != nil { + name := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if !insertUnquotedName(name) { + return newDuplicateNameError(enc.StackPointer().Parent(), val, enc.OutputOffset()) + } + } + if err := enc.WriteValue(val); err != nil { + return err + } + + // Parse the JSON object value. + val, err = xd.ReadValue(&flags) + if err != nil { + return newMarshalErrorBefore(enc, v.Type(), err) + } + if err := enc.WriteValue(val); err != nil { + return err + } + } + if _, err := dec.ReadToken(); err != nil { + return newMarshalErrorBefore(enc, v.Type(), err) + } + if err := xd.CheckEOF(); err != nil { + return newMarshalErrorBefore(enc, v.Type(), err) + } + return nil + } else { + m := v // must be a map[~string]V + n := m.Len() + if n == 0 { + return nil + } + mk := newAddressableValue(m.Type().Key()) + mv := newAddressableValue(m.Type().Elem()) + marshalKey := func(mk addressableValue) error { + b, err := jsonwire.AppendQuote(enc.UnusedBuffer(), mk.String(), &mo.Flags) + if err != nil { + return newMarshalErrorBefore(enc, m.Type().Key(), err) + } + if insertUnquotedName != nil { + isVerbatim := bytes.IndexByte(b, '\\') < 0 + name := jsonwire.UnquoteMayCopy(b, isVerbatim) + if !insertUnquotedName(name) { + return newDuplicateNameError(enc.StackPointer().Parent(), b, enc.OutputOffset()) + } + } + return enc.WriteValue(b) + } + marshalVal := f.fncs.marshal + if mo.Marshalers != nil { + marshalVal, _ = mo.Marshalers.(*Marshalers).lookup(marshalVal, mv.Type()) + } + if !mo.Flags.Get(jsonflags.Deterministic) || n <= 1 { + for iter := m.MapRange(); iter.Next(); { + mk.SetIterKey(iter) + if err := marshalKey(mk); err != nil { + return err + } + mv.Set(iter.Value()) + if err := marshalVal(enc, mv, mo); err != nil { + return err + } + } + } else { + names := getStrings(n) + for i, iter := 0, m.Value.MapRange(); i < n && iter.Next(); i++ { + mk.SetIterKey(iter) + (*names)[i] = mk.String() + } + names.Sort() + for _, name := range *names { + mk.SetString(name) + if err := marshalKey(mk); err != nil { + return err + } + // TODO(https://go.dev/issue/57061): Use mv.SetMapIndexOf. + mv.Set(m.MapIndex(mk.Value)) + if err := marshalVal(enc, mv, mo); err != nil { + return err + } + } + putStrings(names) + } + return nil + } +} + +// unmarshalInlinedFallbackNext unmarshals only the next member in an inlined fallback. +func unmarshalInlinedFallbackNext(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct, f *structField, quotedName, unquotedName []byte) error { + v := addressableValue{va.Field(f.index0), va.forcedAddr} // addressable if struct value is addressable + if len(f.index) > 0 { + v = v.fieldByIndex(f.index, true) + } + v = v.indirect(true) + + if v.Type() == jsontextValueType { + b := v.Addr().Interface().(*jsontext.Value) + if len(*b) == 0 { // TODO: Should this be nil? What if it were all whitespace? + *b = append(*b, '{') + } else { + *b = jsonwire.TrimSuffixWhitespace(*b) + if jsonwire.HasSuffixByte(*b, '}') { + // TODO: When merging into an object for the first time, + // should we verify that it is valid? + *b = jsonwire.TrimSuffixByte(*b, '}') + *b = jsonwire.TrimSuffixWhitespace(*b) + if !jsonwire.HasSuffixByte(*b, ',') && !jsonwire.HasSuffixByte(*b, '{') { + *b = append(*b, ',') + } + } else { + return newUnmarshalErrorAfterWithSkipping(dec, uo, v.Type(), errRawInlinedNotObject) + } + } + *b = append(*b, quotedName...) + *b = append(*b, ':') + val, err := dec.ReadValue() + if err != nil { + return err + } + *b = append(*b, val...) + *b = append(*b, '}') + return nil + } else { + name := string(unquotedName) // TODO: Intern this? + + m := v // must be a map[~string]V + if m.IsNil() { + m.Set(reflect.MakeMap(m.Type())) + } + mk := reflect.ValueOf(name) + if mkt := m.Type().Key(); mkt != stringType { + mk = mk.Convert(mkt) + } + mv := newAddressableValue(m.Type().Elem()) // TODO: Cache across calls? + if v2 := m.MapIndex(mk); v2.IsValid() { + mv.Set(v2) + } + + unmarshal := f.fncs.unmarshal + if uo.Unmarshalers != nil { + unmarshal, _ = uo.Unmarshalers.(*Unmarshalers).lookup(unmarshal, mv.Type()) + } + err := unmarshal(dec, mv, uo) + m.SetMapIndex(mk, mv.Value) + if err != nil { + return err + } + return nil + } +} diff --git a/src/encoding/json/v2/arshal_methods.go b/src/encoding/json/v2/arshal_methods.go new file mode 100644 index 0000000000..099be298c2 --- /dev/null +++ b/src/encoding/json/v2/arshal_methods.go @@ -0,0 +1,337 @@ +// Copyright 2020 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 + +package json + +import ( + "encoding" + "errors" + "reflect" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +var errNonStringValue = errors.New("JSON value must be string type") + +// Interfaces for custom serialization. +var ( + jsonMarshalerType = reflect.TypeFor[Marshaler]() + jsonMarshalerToType = reflect.TypeFor[MarshalerTo]() + jsonUnmarshalerType = reflect.TypeFor[Unmarshaler]() + jsonUnmarshalerFromType = reflect.TypeFor[UnmarshalerFrom]() + textAppenderType = reflect.TypeFor[encoding.TextAppender]() + textMarshalerType = reflect.TypeFor[encoding.TextMarshaler]() + textUnmarshalerType = reflect.TypeFor[encoding.TextUnmarshaler]() + + allMarshalerTypes = []reflect.Type{jsonMarshalerToType, jsonMarshalerType, textAppenderType, textMarshalerType} + allUnmarshalerTypes = []reflect.Type{jsonUnmarshalerFromType, jsonUnmarshalerType, textUnmarshalerType} + allMethodTypes = append(allMarshalerTypes, allUnmarshalerTypes...) +) + +// Marshaler is implemented by types that can marshal themselves. +// It is recommended that types implement [MarshalerTo] unless the implementation +// is trying to avoid a hard dependency on the "jsontext" package. +// +// It is recommended that implementations return a buffer that is safe +// for the caller to retain and potentially mutate. +type 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. +// 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]. +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. +// +// 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. +// +// Implementations must not retain or mutate the input []byte. +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. +// 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. +// +// Implementations must not retain the pointer to [jsontext.Decoder]. +type UnmarshalerFrom interface { + UnmarshalJSONFrom(*jsontext.Decoder) error + + // TODO: Should users call the UnmarshalDecode function or + // should/can they call this method directly? Does it matter? +} + +func makeMethodArshaler(fncs *arshaler, t reflect.Type) *arshaler { + // Avoid injecting method arshaler on the pointer or interface version + // to avoid ever calling the method on a nil pointer or interface receiver. + // Let it be injected on the value receiver (which is always addressable). + if t.Kind() == reflect.Pointer || t.Kind() == reflect.Interface { + return fncs + } + + if needAddr, ok := implements(t, textMarshalerType); ok { + fncs.nonDefault = true + prevMarshal := fncs.marshal + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + (needAddr && va.forcedAddr) { + return prevMarshal(enc, va, mo) + } + marshaler := va.Addr().Interface().(encoding.TextMarshaler) + if err := export.Encoder(enc).AppendRaw('"', false, func(b []byte) ([]byte, error) { + b2, err := marshaler.MarshalText() + return append(b, b2...), err + }); err != nil { + err = wrapSkipFunc(err, "marshal method") + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalText") // unlike unmarshal, always wrapped + } + if !isSemanticError(err) && !export.IsIOError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err + } + return nil + } + } + + if needAddr, ok := implements(t, textAppenderType); ok { + fncs.nonDefault = true + prevMarshal := fncs.marshal + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + (needAddr && va.forcedAddr) { + return prevMarshal(enc, va, mo) + } + appender := va.Addr().Interface().(encoding.TextAppender) + if err := export.Encoder(enc).AppendRaw('"', false, appender.AppendText); err != nil { + err = wrapSkipFunc(err, "append method") + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "AppendText") // unlike unmarshal, always wrapped + } + if !isSemanticError(err) && !export.IsIOError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err + } + return nil + } + } + + if needAddr, ok := implements(t, jsonMarshalerType); ok { + fncs.nonDefault = true + prevMarshal := fncs.marshal + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + ((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) { + return prevMarshal(enc, va, mo) + } + marshaler := va.Addr().Interface().(Marshaler) + val, err := marshaler.MarshalJSON() + if err != nil { + err = wrapSkipFunc(err, "marshal method") + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped + } + err = newMarshalErrorBefore(enc, t, err) + return collapseSemanticErrors(err) + } + if err := enc.WriteValue(val); err != nil { + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped + } + if isSyntacticError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err + } + return nil + } + } + + if needAddr, ok := implements(t, jsonMarshalerToType); ok { + fncs.nonDefault = true + prevMarshal := fncs.marshal + fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error { + if mo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + ((needAddr && va.forcedAddr) || export.Encoder(enc).Tokens.Last.NeedObjectName()) { + return prevMarshal(enc, va, mo) + } + xe := export.Encoder(enc) + prevDepth, prevLength := xe.Tokens.DepthLength() + xe.Flags.Set(jsonflags.WithinArshalCall | 1) + err := va.Addr().Interface().(MarshalerTo).MarshalJSONTo(enc) + xe.Flags.Set(jsonflags.WithinArshalCall | 0) + currDepth, currLength := xe.Tokens.DepthLength() + if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil { + err = errNonSingularValue + } + if err != nil { + err = wrapSkipFunc(err, "marshal method") + if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSONTo") // unlike unmarshal, always wrapped + } + if !export.IsIOError(err) { + err = newSemanticErrorWithPosition(enc, t, prevDepth, prevLength, err) + } + return err + } + return nil + } + } + + if _, ok := implements(t, textUnmarshalerType); ok { + fncs.nonDefault = true + fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + xd := export.Decoder(dec) + var flags jsonwire.ValueFlags + val, err := xd.ReadValue(&flags) + if err != nil { + return err // must be a syntactic or I/O error + } + if val.Kind() == 'n' { + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + va.SetZero() + } + return nil + } + if val.Kind() != '"' { + return newUnmarshalErrorAfter(dec, t, errNonStringValue) + } + s := jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + unmarshaler := va.Addr().Interface().(encoding.TextUnmarshaler) + if err := unmarshaler.UnmarshalText(s); err != nil { + err = wrapSkipFunc(err, "unmarshal method") + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err // unlike marshal, never wrapped + } + if !isSemanticError(err) && !isSyntacticError(err) && !export.IsIOError(err) { + err = newUnmarshalErrorAfter(dec, t, err) + } + return err + } + return nil + } + } + + if _, ok := implements(t, jsonUnmarshalerType); ok { + fncs.nonDefault = true + prevUnmarshal := fncs.unmarshal + fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + export.Decoder(dec).Tokens.Last.NeedObjectName() { + return prevUnmarshal(dec, va, uo) + } + val, err := dec.ReadValue() + if err != nil { + return err // must be a syntactic or I/O error + } + unmarshaler := va.Addr().Interface().(Unmarshaler) + if err := unmarshaler.UnmarshalJSON(val); err != nil { + err = wrapSkipFunc(err, "unmarshal method") + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err // unlike marshal, never wrapped + } + err = newUnmarshalErrorAfter(dec, t, err) + return collapseSemanticErrors(err) + } + return nil + } + } + + if _, ok := implements(t, jsonUnmarshalerFromType); ok { + fncs.nonDefault = true + prevUnmarshal := fncs.unmarshal + fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error { + if uo.Flags.Get(jsonflags.CallMethodsWithLegacySemantics) && + export.Decoder(dec).Tokens.Last.NeedObjectName() { + return prevUnmarshal(dec, va, uo) + } + xd := export.Decoder(dec) + prevDepth, prevLength := xd.Tokens.DepthLength() + xd.Flags.Set(jsonflags.WithinArshalCall | 1) + err := va.Addr().Interface().(UnmarshalerFrom).UnmarshalJSONFrom(dec) + xd.Flags.Set(jsonflags.WithinArshalCall | 0) + currDepth, currLength := xd.Tokens.DepthLength() + if (prevDepth != currDepth || prevLength+1 != currLength) && err == nil { + err = errNonSingularValue + } + if err != nil { + err = wrapSkipFunc(err, "unmarshal method") + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := xd.SkipUntil(prevDepth, prevLength+1); err2 != nil { + return err2 + } + return err // unlike marshal, never wrapped + } + if !isSyntacticError(err) && !export.IsIOError(err) { + err = newSemanticErrorWithPosition(dec, t, prevDepth, prevLength, err) + } + return err + } + return nil + } + } + + return fncs +} + +// implementsAny is like t.Implements(ifaceType) for a list of interfaces, +// but checks whether either t or reflect.PointerTo(t) implements the interface. +func implementsAny(t reflect.Type, ifaceTypes ...reflect.Type) bool { + for _, ifaceType := range ifaceTypes { + if _, ok := implements(t, ifaceType); ok { + return true + } + } + return false +} + +// implements is like t.Implements(ifaceType) but checks whether +// either t or reflect.PointerTo(t) implements the interface. +// It also reports whether the value needs to be addressed +// in order to satisfy the interface. +func implements(t, ifaceType reflect.Type) (needAddr, ok bool) { + switch { + case t.Implements(ifaceType): + return false, true + case reflect.PointerTo(t).Implements(ifaceType): + return true, true + default: + return false, false + } +} diff --git a/src/encoding/json/v2/arshal_test.go b/src/encoding/json/v2/arshal_test.go new file mode 100644 index 0000000000..f1060cccb5 --- /dev/null +++ b/src/encoding/json/v2/arshal_test.go @@ -0,0 +1,9488 @@ +// Copyright 2020 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 + +package json + +import ( + "bytes" + "encoding" + "encoding/base32" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "io" + "math" + "net" + "net/netip" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsontest" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +func newNonStringNameError(offset int64, pointer jsontext.Pointer) error { + return &jsontext.SyntacticError{ByteOffset: offset, JSONPointer: pointer, Err: jsontext.ErrNonStringName} +} + +func newInvalidCharacterError(prefix, where string, offset int64, pointer jsontext.Pointer) error { + return &jsontext.SyntacticError{ByteOffset: offset, JSONPointer: pointer, Err: jsonwire.NewInvalidCharacterError(prefix, where)} +} + +func newInvalidUTF8Error(offset int64, pointer jsontext.Pointer) error { + return &jsontext.SyntacticError{ByteOffset: offset, JSONPointer: pointer, Err: jsonwire.ErrInvalidUTF8} +} + +func newParseTimeError(layout, value, layoutElem, valueElem, message string) error { + return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message} +} + +func EM(err error) *SemanticError { + return &SemanticError{action: "marshal", Err: err} +} + +func EU(err error) *SemanticError { + return &SemanticError{action: "unmarshal", Err: err} +} + +func (e *SemanticError) withVal(val string) *SemanticError { + e.JSONValue = jsontext.Value(val) + return e +} + +func (e *SemanticError) withPos(prefix string, pointer jsontext.Pointer) *SemanticError { + e.ByteOffset = int64(len(prefix)) + e.JSONPointer = pointer + return e +} + +func (e *SemanticError) withType(k jsontext.Kind, t reflect.Type) *SemanticError { + e.JSONKind = k + e.GoType = t + return e +} + +var ( + errInvalidFormatFlag = errors.New(`invalid format flag "invalid"`) + errSomeError = errors.New("some error") + errMustNotCall = errors.New("must not call") +) + +func T[T any]() reflect.Type { return reflect.TypeFor[T]() } + +type ( + jsonObject = map[string]any + jsonArray = []any + + namedAny any + namedBool bool + namedString string + NamedString string + namedBytes []byte + namedInt64 int64 + namedUint64 uint64 + namedFloat64 float64 + namedByte byte + netipAddr = netip.Addr + + recursiveMap map[string]recursiveMap + recursiveSlice []recursiveSlice + recursivePointer struct{ P *recursivePointer } + + structEmpty struct{} + structConflicting struct { + A string `json:"conflict"` + B string `json:"conflict"` + } + structNoneExported struct { + unexported string + } + structUnexportedIgnored struct { + ignored string `json:"-"` + } + structMalformedTag struct { + Malformed string `json:"\""` + } + structUnexportedTag struct { + unexported string `json:"name"` + } + structExportedEmbedded struct { + NamedString + } + structExportedEmbeddedTag struct { + NamedString `json:"name"` + } + structUnexportedEmbedded struct { + namedString + } + structUnexportedEmbeddedTag struct { + namedString `json:"name"` + } + structUnexportedEmbeddedMethodTag struct { + // netipAddr cannot be marshaled since the MarshalText method + // cannot be called on an unexported field. + netipAddr `json:"name"` + + // Bogus MarshalText and AppendText methods are declared on + // structUnexportedEmbeddedMethodTag to prevent it from + // implementing those method interfaces. + } + structUnexportedEmbeddedStruct struct { + structOmitZeroAll + FizzBuzz int + structNestedAddr + } + structUnexportedEmbeddedStructPointer struct { + *structOmitZeroAll + FizzBuzz int + *structNestedAddr + } + structNestedAddr struct { + Addr netip.Addr + } + structIgnoredUnexportedEmbedded struct { + namedString `json:"-"` + } + structWeirdNames struct { + Empty string `json:"''"` + Comma string `json:"','"` + Quote string `json:"'\"'"` + } + structNoCase struct { + Aaa string `json:",case:strict"` + AA_A string + AaA string `json:",case:ignore"` + AAa string `json:",case:ignore"` + AAA string + } + structScalars struct { + unexported bool + Ignored bool `json:"-"` + + Bool bool + String string + Bytes []byte + Int int64 + Uint uint64 + Float float64 + } + structSlices struct { + unexported bool + Ignored bool `json:"-"` + + SliceBool []bool + SliceString []string + SliceBytes [][]byte + SliceInt []int64 + SliceUint []uint64 + SliceFloat []float64 + } + structMaps struct { + unexported bool + Ignored bool `json:"-"` + + MapBool map[string]bool + MapString map[string]string + MapBytes map[string][]byte + MapInt map[string]int64 + MapUint map[string]uint64 + MapFloat map[string]float64 + } + structAll struct { + Bool bool + String string + Bytes []byte + Int int64 + Uint uint64 + Float float64 + Map map[string]string + StructScalars structScalars + StructMaps structMaps + StructSlices structSlices + Slice []string + Array [1]string + Pointer *structAll + Interface any + } + structStringifiedAll struct { + Bool bool `json:",string"` + String string `json:",string"` + Bytes []byte `json:",string"` + Int int64 `json:",string"` + Uint uint64 `json:",string"` + Float float64 `json:",string"` + Map map[string]string `json:",string"` + StructScalars structScalars `json:",string"` + StructMaps structMaps `json:",string"` + StructSlices structSlices `json:",string"` + Slice []string `json:",string"` + Array [1]string `json:",string"` + Pointer *structStringifiedAll `json:",string"` + Interface any `json:",string"` + } + structOmitZeroAll struct { + Bool bool `json:",omitzero"` + String string `json:",omitzero"` + Bytes []byte `json:",omitzero"` + Int int64 `json:",omitzero"` + Uint uint64 `json:",omitzero"` + Float float64 `json:",omitzero"` + Map map[string]string `json:",omitzero"` + StructScalars structScalars `json:",omitzero"` + StructMaps structMaps `json:",omitzero"` + StructSlices structSlices `json:",omitzero"` + Slice []string `json:",omitzero"` + Array [1]string `json:",omitzero"` + Pointer *structOmitZeroAll `json:",omitzero"` + Interface any `json:",omitzero"` + } + structOmitZeroMethodAll struct { + ValueAlwaysZero valueAlwaysZero `json:",omitzero"` + ValueNeverZero valueNeverZero `json:",omitzero"` + PointerAlwaysZero pointerAlwaysZero `json:",omitzero"` + PointerNeverZero pointerNeverZero `json:",omitzero"` + PointerValueAlwaysZero *valueAlwaysZero `json:",omitzero"` + PointerValueNeverZero *valueNeverZero `json:",omitzero"` + PointerPointerAlwaysZero *pointerAlwaysZero `json:",omitzero"` + PointerPointerNeverZero *pointerNeverZero `json:",omitzero"` + PointerPointerValueAlwaysZero **valueAlwaysZero `json:",omitzero"` + PointerPointerValueNeverZero **valueNeverZero `json:",omitzero"` + PointerPointerPointerAlwaysZero **pointerAlwaysZero `json:",omitzero"` + PointerPointerPointerNeverZero **pointerNeverZero `json:",omitzero"` + } + structOmitZeroMethodInterfaceAll struct { + ValueAlwaysZero isZeroer `json:",omitzero"` + ValueNeverZero isZeroer `json:",omitzero"` + PointerValueAlwaysZero isZeroer `json:",omitzero"` + PointerValueNeverZero isZeroer `json:",omitzero"` + PointerPointerAlwaysZero isZeroer `json:",omitzero"` + PointerPointerNeverZero isZeroer `json:",omitzero"` + } + structOmitEmptyAll struct { + Bool bool `json:",omitempty"` + PointerBool *bool `json:",omitempty"` + String string `json:",omitempty"` + StringEmpty stringMarshalEmpty `json:",omitempty"` + StringNonEmpty stringMarshalNonEmpty `json:",omitempty"` + PointerString *string `json:",omitempty"` + PointerStringEmpty *stringMarshalEmpty `json:",omitempty"` + PointerStringNonEmpty *stringMarshalNonEmpty `json:",omitempty"` + Bytes []byte `json:",omitempty"` + BytesEmpty bytesMarshalEmpty `json:",omitempty"` + BytesNonEmpty bytesMarshalNonEmpty `json:",omitempty"` + PointerBytes *[]byte `json:",omitempty"` + PointerBytesEmpty *bytesMarshalEmpty `json:",omitempty"` + PointerBytesNonEmpty *bytesMarshalNonEmpty `json:",omitempty"` + Float float64 `json:",omitempty"` + PointerFloat *float64 `json:",omitempty"` + Map map[string]string `json:",omitempty"` + MapEmpty mapMarshalEmpty `json:",omitempty"` + MapNonEmpty mapMarshalNonEmpty `json:",omitempty"` + PointerMap *map[string]string `json:",omitempty"` + PointerMapEmpty *mapMarshalEmpty `json:",omitempty"` + PointerMapNonEmpty *mapMarshalNonEmpty `json:",omitempty"` + Slice []string `json:",omitempty"` + SliceEmpty sliceMarshalEmpty `json:",omitempty"` + SliceNonEmpty sliceMarshalNonEmpty `json:",omitempty"` + PointerSlice *[]string `json:",omitempty"` + PointerSliceEmpty *sliceMarshalEmpty `json:",omitempty"` + PointerSliceNonEmpty *sliceMarshalNonEmpty `json:",omitempty"` + Pointer *structOmitZeroEmptyAll `json:",omitempty"` + Interface any `json:",omitempty"` + } + structOmitZeroEmptyAll struct { + Bool bool `json:",omitzero,omitempty"` + String string `json:",omitzero,omitempty"` + Bytes []byte `json:",omitzero,omitempty"` + Int int64 `json:",omitzero,omitempty"` + Uint uint64 `json:",omitzero,omitempty"` + Float float64 `json:",omitzero,omitempty"` + Map map[string]string `json:",omitzero,omitempty"` + Slice []string `json:",omitzero,omitempty"` + Array [1]string `json:",omitzero,omitempty"` + Pointer *structOmitZeroEmptyAll `json:",omitzero,omitempty"` + Interface any `json:",omitzero,omitempty"` + } + structFormatBytes struct { + Base16 []byte `json:",format:base16"` + Base32 []byte `json:",format:base32"` + Base32Hex []byte `json:",format:base32hex"` + Base64 []byte `json:",format:base64"` + Base64URL []byte `json:",format:base64url"` + Array []byte `json:",format:array"` + } + structFormatArrayBytes struct { + Base16 [4]byte `json:",format:base16"` + Base32 [4]byte `json:",format:base32"` + Base32Hex [4]byte `json:",format:base32hex"` + Base64 [4]byte `json:",format:base64"` + Base64URL [4]byte `json:",format:base64url"` + Array [4]byte `json:",format:array"` + Default [4]byte + } + structFormatFloats struct { + NonFinite float64 `json:",format:nonfinite"` + PointerNonFinite *float64 `json:",format:nonfinite"` + } + structFormatMaps struct { + EmitNull map[string]string `json:",format:emitnull"` + PointerEmitNull *map[string]string `json:",format:emitnull"` + EmitEmpty map[string]string `json:",format:emitempty"` + PointerEmitEmpty *map[string]string `json:",format:emitempty"` + EmitDefault map[string]string + PointerEmitDefault *map[string]string + } + structFormatSlices struct { + EmitNull []string `json:",format:emitnull"` + PointerEmitNull *[]string `json:",format:emitnull"` + EmitEmpty []string `json:",format:emitempty"` + PointerEmitEmpty *[]string `json:",format:emitempty"` + EmitDefault []string + PointerEmitDefault *[]string + } + structFormatInvalid struct { + Bool bool `json:",omitzero,format:invalid"` + String string `json:",omitzero,format:invalid"` + Bytes []byte `json:",omitzero,format:invalid"` + Int int64 `json:",omitzero,format:invalid"` + Uint uint64 `json:",omitzero,format:invalid"` + Float float64 `json:",omitzero,format:invalid"` + Map map[string]string `json:",omitzero,format:invalid"` + Struct structAll `json:",omitzero,format:invalid"` + Slice []string `json:",omitzero,format:invalid"` + Array [1]string `json:",omitzero,format:invalid"` + Interface any `json:",omitzero,format:invalid"` + } + structDurationFormat struct { + D1 time.Duration + D2 time.Duration `json:",format:units"` + D3 time.Duration `json:",format:sec"` + D4 time.Duration `json:",string,format:sec"` + D5 time.Duration `json:",format:milli"` + D6 time.Duration `json:",string,format:milli"` + D7 time.Duration `json:",format:micro"` + D8 time.Duration `json:",string,format:micro"` + D9 time.Duration `json:",format:nano"` + D10 time.Duration `json:",string,format:nano"` + } + structTimeFormat struct { + T1 time.Time + T2 time.Time `json:",format:ANSIC"` + T3 time.Time `json:",format:UnixDate"` + T4 time.Time `json:",format:RubyDate"` + T5 time.Time `json:",format:RFC822"` + T6 time.Time `json:",format:RFC822Z"` + T7 time.Time `json:",format:RFC850"` + T8 time.Time `json:",format:RFC1123"` + T9 time.Time `json:",format:RFC1123Z"` + T10 time.Time `json:",format:RFC3339"` + T11 time.Time `json:",format:RFC3339Nano"` + T12 time.Time `json:",format:Kitchen"` + T13 time.Time `json:",format:Stamp"` + T14 time.Time `json:",format:StampMilli"` + T15 time.Time `json:",format:StampMicro"` + T16 time.Time `json:",format:StampNano"` + T17 time.Time `json:",format:DateTime"` + T18 time.Time `json:",format:DateOnly"` + T19 time.Time `json:",format:TimeOnly"` + T20 time.Time `json:",format:'2006-01-02'"` + T21 time.Time `json:",format:'\"weird\"2006'"` + T22 time.Time `json:",format:unix"` + T23 time.Time `json:",string,format:unix"` + T24 time.Time `json:",format:unixmilli"` + T25 time.Time `json:",string,format:unixmilli"` + T26 time.Time `json:",format:unixmicro"` + T27 time.Time `json:",string,format:unixmicro"` + T28 time.Time `json:",format:unixnano"` + T29 time.Time `json:",string,format:unixnano"` + } + structInlined struct { + X structInlinedL1 `json:",inline"` + *StructEmbed2 // implicit inline + } + structInlinedL1 struct { + X *structInlinedL2 `json:",inline"` + StructEmbed1 `json:",inline"` + } + structInlinedL2 struct{ A, B, C string } + StructEmbed1 struct{ C, D, E string } + StructEmbed2 struct{ E, F, G string } + structUnknownTextValue struct { + A int `json:",omitzero"` + X jsontext.Value `json:",unknown"` + B int `json:",omitzero"` + } + structInlineTextValue struct { + A int `json:",omitzero"` + X jsontext.Value `json:",inline"` + B int `json:",omitzero"` + } + structInlinePointerTextValue struct { + A int `json:",omitzero"` + X *jsontext.Value `json:",inline"` + B int `json:",omitzero"` + } + structInlinePointerInlineTextValue struct { + X *struct { + A int + X jsontext.Value `json:",inline"` + } `json:",inline"` + } + structInlineInlinePointerTextValue struct { + X struct { + X *jsontext.Value `json:",inline"` + } `json:",inline"` + } + structInlineMapStringAny struct { + A int `json:",omitzero"` + X jsonObject `json:",inline"` + B int `json:",omitzero"` + } + structInlinePointerMapStringAny struct { + A int `json:",omitzero"` + X *jsonObject `json:",inline"` + B int `json:",omitzero"` + } + structInlinePointerInlineMapStringAny struct { + X *struct { + A int + X jsonObject `json:",inline"` + } `json:",inline"` + } + structInlineInlinePointerMapStringAny struct { + X struct { + X *jsonObject `json:",inline"` + } `json:",inline"` + } + structInlineMapStringInt struct { + X map[string]int `json:",inline"` + } + structInlineMapNamedStringInt struct { + X map[namedString]int `json:",inline"` + } + structInlineMapNamedStringAny struct { + A int `json:",omitzero"` + X map[namedString]any `json:",inline"` + B int `json:",omitzero"` + } + structNoCaseInlineTextValue struct { + AAA string `json:",omitempty,case:strict"` + AA_b string `json:",omitempty"` + AaA string `json:",omitempty,case:ignore"` + AAa string `json:",omitempty,case:ignore"` + Aaa string `json:",omitempty"` + X jsontext.Value `json:",inline"` + } + structNoCaseInlineMapStringAny struct { + AAA string `json:",omitempty"` + AaA string `json:",omitempty,case:ignore"` + AAa string `json:",omitempty,case:ignore"` + Aaa string `json:",omitempty"` + X jsonObject `json:",inline"` + } + + allMethods struct { + method string // the method that was called + value []byte // the raw value to provide or store + } + allMethodsExceptJSONv2 struct { + allMethods + MarshalJSONTo struct{} // cancel out MarshalJSONTo method with collision + UnmarshalJSONFrom struct{} // cancel out UnmarshalJSONFrom method with collision + } + allMethodsExceptJSONv1 struct { + allMethods + MarshalJSON struct{} // cancel out MarshalJSON method with collision + UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision + } + allMethodsExceptText struct { + allMethods + MarshalText struct{} // cancel out MarshalText method with collision + UnmarshalText struct{} // cancel out UnmarshalText method with collision + } + onlyMethodJSONv2 struct { + allMethods + MarshalJSON struct{} // cancel out MarshalJSON method with collision + UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision + MarshalText struct{} // cancel out MarshalText method with collision + UnmarshalText struct{} // cancel out UnmarshalText method with collision + } + onlyMethodJSONv1 struct { + allMethods + MarshalJSONTo struct{} // cancel out MarshalJSONTo method with collision + UnmarshalJSONFrom struct{} // cancel out UnmarshalJSONFrom method with collision + MarshalText struct{} // cancel out MarshalText method with collision + UnmarshalText struct{} // cancel out UnmarshalText method with collision + } + onlyMethodText struct { + allMethods + MarshalJSONTo struct{} // cancel out MarshalJSONTo method with collision + UnmarshalJSONFrom struct{} // cancel out UnmarshalJSONFrom method with collision + MarshalJSON struct{} // cancel out MarshalJSON method with collision + UnmarshalJSON struct{} // cancel out UnmarshalJSON method with collision + } + + structMethodJSONv2 struct{ value string } + structMethodJSONv1 struct{ value string } + structMethodText struct{ value string } + + marshalJSONv2Func func(*jsontext.Encoder) error + marshalJSONv1Func func() ([]byte, error) + appendTextFunc func([]byte) ([]byte, error) + marshalTextFunc func() ([]byte, error) + unmarshalJSONv2Func func(*jsontext.Decoder) error + unmarshalJSONv1Func func([]byte) error + unmarshalTextFunc func([]byte) error + + nocaseString string + + stringMarshalEmpty string + stringMarshalNonEmpty string + bytesMarshalEmpty []byte + bytesMarshalNonEmpty []byte + mapMarshalEmpty map[string]string + mapMarshalNonEmpty map[string]string + sliceMarshalEmpty []string + sliceMarshalNonEmpty []string + + valueAlwaysZero string + valueNeverZero string + pointerAlwaysZero string + pointerNeverZero string + + valueStringer struct{} + pointerStringer struct{} + + cyclicA struct { + B1 cyclicB `json:",inline"` + B2 cyclicB `json:",inline"` + } + cyclicB struct { + F int + A *cyclicA `json:",inline"` + } +) + +func (structUnexportedEmbeddedMethodTag) MarshalText() {} +func (structUnexportedEmbeddedMethodTag) AppendText() {} + +func (p *allMethods) MarshalJSONTo(enc *jsontext.Encoder) error { + if got, want := "MarshalJSONTo", p.method; got != want { + return fmt.Errorf("called wrong method: got %v, want %v", got, want) + } + return enc.WriteValue(p.value) +} +func (p *allMethods) MarshalJSON() ([]byte, error) { + if got, want := "MarshalJSON", p.method; got != want { + return nil, fmt.Errorf("called wrong method: got %v, want %v", got, want) + } + return p.value, nil +} +func (p *allMethods) MarshalText() ([]byte, error) { + if got, want := "MarshalText", p.method; got != want { + return nil, fmt.Errorf("called wrong method: got %v, want %v", got, want) + } + return p.value, nil +} + +func (p *allMethods) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + p.method = "UnmarshalJSONFrom" + val, err := dec.ReadValue() + p.value = val + return err +} +func (p *allMethods) UnmarshalJSON(val []byte) error { + p.method = "UnmarshalJSON" + p.value = val + return nil +} +func (p *allMethods) UnmarshalText(val []byte) error { + p.method = "UnmarshalText" + p.value = val + return nil +} + +func (s structMethodJSONv2) MarshalJSONTo(enc *jsontext.Encoder) error { + return enc.WriteToken(jsontext.String(s.value)) +} +func (s *structMethodJSONv2) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + tok, err := dec.ReadToken() + if err != nil { + return err + } + if k := tok.Kind(); k != '"' { + return EU(nil).withType(k, T[structMethodJSONv2]()) + } + s.value = tok.String() + return nil +} + +func (s structMethodJSONv1) MarshalJSON() ([]byte, error) { + return jsontext.AppendQuote(nil, s.value) +} +func (s *structMethodJSONv1) UnmarshalJSON(b []byte) error { + if k := jsontext.Value(b).Kind(); k != '"' { + return EU(nil).withType(k, T[structMethodJSONv1]()) + } + b, _ = jsontext.AppendUnquote(nil, b) + s.value = string(b) + return nil +} + +func (s structMethodText) MarshalText() ([]byte, error) { + return []byte(s.value), nil +} +func (s *structMethodText) UnmarshalText(b []byte) error { + s.value = string(b) + return nil +} + +func (f marshalJSONv2Func) MarshalJSONTo(enc *jsontext.Encoder) error { + return f(enc) +} +func (f marshalJSONv1Func) MarshalJSON() ([]byte, error) { + return f() +} +func (f appendTextFunc) AppendText(b []byte) ([]byte, error) { + return f(b) +} +func (f marshalTextFunc) MarshalText() ([]byte, error) { + return f() +} +func (f unmarshalJSONv2Func) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + return f(dec) +} +func (f unmarshalJSONv1Func) UnmarshalJSON(b []byte) error { + return f(b) +} +func (f unmarshalTextFunc) UnmarshalText(b []byte) error { + return f(b) +} + +func (k nocaseString) MarshalText() ([]byte, error) { + return []byte(strings.ToLower(string(k))), nil +} +func (k *nocaseString) UnmarshalText(b []byte) error { + *k = nocaseString(strings.ToLower(string(b))) + return nil +} + +func (stringMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`""`), nil } +func (stringMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`"value"`), nil } +func (bytesMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`[]`), nil } +func (bytesMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`["value"]`), nil } +func (mapMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`{}`), nil } +func (mapMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`{"key":"value"}`), nil } +func (sliceMarshalEmpty) MarshalJSON() ([]byte, error) { return []byte(`[]`), nil } +func (sliceMarshalNonEmpty) MarshalJSON() ([]byte, error) { return []byte(`["value"]`), nil } + +func (valueAlwaysZero) IsZero() bool { return true } +func (valueNeverZero) IsZero() bool { return false } +func (*pointerAlwaysZero) IsZero() bool { return true } +func (*pointerNeverZero) IsZero() bool { return false } + +func (valueStringer) String() string { return "" } +func (*pointerStringer) String() string { return "" } + +func addr[T any](v T) *T { + return &v +} + +func mustParseTime(layout, value string) time.Time { + t, err := time.Parse(layout, value) + if err != nil { + panic(err) + } + return t +} + +var invalidFormatOption = &jsonopts.Struct{ + ArshalValues: jsonopts.ArshalValues{FormatDepth: 1000, Format: "invalid"}, +} + +func TestMarshal(t *testing.T) { + tests := []struct { + name jsontest.CaseName + opts []Options + in any + want string + wantErr error + + canonicalize bool // canonicalize the output before comparing? + useWriter bool // call MarshalWrite instead of Marshal + }{{ + name: jsontest.Name("Nil"), + in: nil, + want: `null`, + }, { + name: jsontest.Name("Bools"), + in: []bool{false, true}, + want: `[false,true]`, + }, { + name: jsontest.Name("Bools/Named"), + in: []namedBool{false, true}, + want: `[false,true]`, + }, { + name: jsontest.Name("Bools/NotStringified"), + opts: []Options{StringifyNumbers(true)}, + in: []bool{false, true}, + want: `[false,true]`, + }, { + name: jsontest.Name("Bools/StringifiedBool"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + in: []bool{false, true}, + want: `["false","true"]`, + }, { + name: jsontest.Name("Bools/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: true, + want: `true`, + }, { + name: jsontest.Name("Strings"), + in: []string{"", "hello", "世界"}, + want: `["","hello","世界"]`, + }, { + name: jsontest.Name("Strings/Named"), + in: []namedString{"", "hello", "世界"}, + want: `["","hello","世界"]`, + }, { + name: jsontest.Name("Strings/StringifiedBool"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + in: []string{"", "hello", "世界"}, + want: `["\"\"","\"hello\"","\"世界\""]`, + }, { + name: jsontest.Name("Strings/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: "string", + want: `"string"`, + }, { + name: jsontest.Name("Bytes"), + in: [][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}, + want: `["","","AQ==","AQI=","AQID"]`, + }, { + name: jsontest.Name("Bytes/FormatNilSliceAsNull"), + opts: []Options{FormatNilSliceAsNull(true)}, + in: [][]byte{nil, {}}, + want: `[null,""]`, + }, { + name: jsontest.Name("Bytes/Large"), + in: []byte("the quick brown fox jumped over the lazy dog and ate the homework that I spent so much time on."), + want: `"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2cgYW5kIGF0ZSB0aGUgaG9tZXdvcmsgdGhhdCBJIHNwZW50IHNvIG11Y2ggdGltZSBvbi4="`, + }, { + name: jsontest.Name("Bytes/Named"), + in: []namedBytes{nil, {}, {1}, {1, 2}, {1, 2, 3}}, + want: `["","","AQ==","AQI=","AQID"]`, + }, { + name: jsontest.Name("Bytes/NotStringified"), + opts: []Options{StringifyNumbers(true)}, + in: [][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}, + want: `["","","AQ==","AQI=","AQID"]`, + }, { + // NOTE: []namedByte is not assignable to []byte, + // so the following should be treated as a slice of uints. + name: jsontest.Name("Bytes/Invariant"), + in: [][]namedByte{nil, {}, {1}, {1, 2}, {1, 2, 3}}, + want: `[[],[],[1],[1,2],[1,2,3]]`, + }, { + // NOTE: This differs in behavior from v1, + // but keeps the representation of slices and arrays more consistent. + name: jsontest.Name("Bytes/ByteArray"), + in: [5]byte{'h', 'e', 'l', 'l', 'o'}, + want: `"aGVsbG8="`, + }, { + // NOTE: []namedByte is not assignable to []byte, + // so the following should be treated as an array of uints. + name: jsontest.Name("Bytes/NamedByteArray"), + in: [5]namedByte{'h', 'e', 'l', 'l', 'o'}, + want: `[104,101,108,108,111]`, + }, { + name: jsontest.Name("Bytes/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: []byte("hello"), + want: `"aGVsbG8="`, + }, { + name: jsontest.Name("Ints"), + in: []any{ + int(0), int8(math.MinInt8), int16(math.MinInt16), int32(math.MinInt32), int64(math.MinInt64), namedInt64(-6464), + }, + want: `[0,-128,-32768,-2147483648,-9223372036854775808,-6464]`, + }, { + name: jsontest.Name("Ints/Stringified"), + opts: []Options{StringifyNumbers(true)}, + in: []any{ + int(0), int8(math.MinInt8), int16(math.MinInt16), int32(math.MinInt32), int64(math.MinInt64), namedInt64(-6464), + }, + want: `["0","-128","-32768","-2147483648","-9223372036854775808","-6464"]`, + }, { + name: jsontest.Name("Ints/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: int(0), + want: `0`, + }, { + name: jsontest.Name("Uints"), + in: []any{ + uint(0), uint8(math.MaxUint8), uint16(math.MaxUint16), uint32(math.MaxUint32), uint64(math.MaxUint64), namedUint64(6464), uintptr(1234), + }, + want: `[0,255,65535,4294967295,18446744073709551615,6464,1234]`, + }, { + name: jsontest.Name("Uints/Stringified"), + opts: []Options{StringifyNumbers(true)}, + in: []any{ + uint(0), uint8(math.MaxUint8), uint16(math.MaxUint16), uint32(math.MaxUint32), uint64(math.MaxUint64), namedUint64(6464), + }, + want: `["0","255","65535","4294967295","18446744073709551615","6464"]`, + }, { + name: jsontest.Name("Uints/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: uint(0), + want: `0`, + }, { + name: jsontest.Name("Floats"), + in: []any{ + float32(math.MaxFloat32), float64(math.MaxFloat64), namedFloat64(64.64), + }, + want: `[3.4028235e+38,1.7976931348623157e+308,64.64]`, + }, { + name: jsontest.Name("Floats/Stringified"), + opts: []Options{StringifyNumbers(true)}, + in: []any{ + float32(math.MaxFloat32), float64(math.MaxFloat64), namedFloat64(64.64), + }, + want: `["3.4028235e+38","1.7976931348623157e+308","64.64"]`, + }, { + name: jsontest.Name("Floats/Invalid/NaN"), + opts: []Options{StringifyNumbers(true)}, + in: math.NaN(), + wantErr: EM(fmt.Errorf("unsupported value: %v", math.NaN())).withType(0, float64Type), + }, { + name: jsontest.Name("Floats/Invalid/PositiveInfinity"), + in: math.Inf(+1), + wantErr: EM(fmt.Errorf("unsupported value: %v", math.Inf(+1))).withType(0, float64Type), + }, { + name: jsontest.Name("Floats/Invalid/NegativeInfinity"), + in: math.Inf(-1), + wantErr: EM(fmt.Errorf("unsupported value: %v", math.Inf(-1))).withType(0, float64Type), + }, { + name: jsontest.Name("Floats/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: float64(0), + want: `0`, + }, { + name: jsontest.Name("Maps/InvalidKey/Bool"), + in: map[bool]string{false: "value"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, boolType), + }, { + name: jsontest.Name("Maps/InvalidKey/NamedBool"), + in: map[namedBool]string{false: "value"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[namedBool]()), + }, { + name: jsontest.Name("Maps/InvalidKey/Array"), + in: map[[1]string]string{{"key"}: "value"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[[1]string]()), + }, { + name: jsontest.Name("Maps/InvalidKey/Channel"), + in: map[chan string]string{make(chan string): "value"}, + want: `{`, + wantErr: EM(nil).withPos(`{`, "").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Maps/ValidKey/Int"), + in: map[int64]string{math.MinInt64: "MinInt64", 0: "Zero", math.MaxInt64: "MaxInt64"}, + canonicalize: true, + want: `{"-9223372036854775808":"MinInt64","0":"Zero","9223372036854775807":"MaxInt64"}`, + }, { + name: jsontest.Name("Maps/ValidKey/PointerInt"), + in: map[*int64]string{addr(int64(math.MinInt64)): "MinInt64", addr(int64(0)): "Zero", addr(int64(math.MaxInt64)): "MaxInt64"}, + canonicalize: true, + want: `{"-9223372036854775808":"MinInt64","0":"Zero","9223372036854775807":"MaxInt64"}`, + }, { + name: jsontest.Name("Maps/DuplicateName/PointerInt"), + in: map[*int64]string{addr(int64(0)): "0", addr(int64(0)): "0"}, + canonicalize: true, + want: `{"0":"0"`, + wantErr: newDuplicateNameError("", []byte(`"0"`), len64(`{"0":"0",`)), + }, { + name: jsontest.Name("Maps/ValidKey/NamedInt"), + in: map[namedInt64]string{math.MinInt64: "MinInt64", 0: "Zero", math.MaxInt64: "MaxInt64"}, + canonicalize: true, + want: `{"-9223372036854775808":"MinInt64","0":"Zero","9223372036854775807":"MaxInt64"}`, + }, { + name: jsontest.Name("Maps/ValidKey/Uint"), + in: map[uint64]string{0: "Zero", math.MaxUint64: "MaxUint64"}, + canonicalize: true, + want: `{"0":"Zero","18446744073709551615":"MaxUint64"}`, + }, { + name: jsontest.Name("Maps/ValidKey/NamedUint"), + in: map[namedUint64]string{0: "Zero", math.MaxUint64: "MaxUint64"}, + canonicalize: true, + want: `{"0":"Zero","18446744073709551615":"MaxUint64"}`, + }, { + name: jsontest.Name("Maps/ValidKey/Float"), + in: map[float64]string{3.14159: "value"}, + want: `{"3.14159":"value"}`, + }, { + name: jsontest.Name("Maps/InvalidKey/Float/NaN"), + in: map[float64]string{math.NaN(): "NaN", math.NaN(): "NaN"}, + want: `{`, + wantErr: EM(errors.New("unsupported value: NaN")).withPos(`{`, "").withType(0, float64Type), + }, { + name: jsontest.Name("Maps/ValidKey/Interface"), + in: map[any]any{ + "key": "key", + namedInt64(-64): int32(-32), + namedUint64(+64): uint32(+32), + namedFloat64(64.64): float32(32.32), + }, + canonicalize: true, + want: `{"-64":-32,"64":32,"64.64":32.32,"key":"key"}`, + }, { + name: jsontest.Name("Maps/DuplicateName/String/AllowInvalidUTF8+AllowDuplicateNames"), + opts: []Options{jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, + in: map[string]string{"\x80": "", "\x81": ""}, + want: `{"�":"","�":""}`, + }, { + name: jsontest.Name("Maps/DuplicateName/String/AllowInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: map[string]string{"\x80": "", "\x81": ""}, + want: `{"�":""`, + wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":"",`)), + }, { + name: jsontest.Name("Maps/DuplicateName/NoCaseString/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + in: map[nocaseString]string{"hello": "", "HELLO": ""}, + want: `{"hello":"","hello":""}`, + }, { + name: jsontest.Name("Maps/DuplicateName/NoCaseString"), + in: map[nocaseString]string{"hello": "", "HELLO": ""}, + want: `{"hello":""`, + wantErr: EM(newDuplicateNameError("", []byte(`"hello"`), len64(`{"hello":"",`))).withPos(`{"hello":"",`, "").withType(0, T[nocaseString]()), + }, { + name: jsontest.Name("Maps/DuplicateName/NaNs/Deterministic+AllowDuplicateNames"), + opts: []Options{ + WithMarshalers( + MarshalFunc(func(v float64) ([]byte, error) { return []byte(`"NaN"`), nil }), + ), + Deterministic(true), + jsontext.AllowDuplicateNames(true), + }, + in: map[float64]string{math.NaN(): "NaN", math.NaN(): "NaN"}, + want: `{"NaN":"NaN","NaN":"NaN"}`, + }, { + name: jsontest.Name("Maps/InvalidValue/Channel"), + in: map[string]chan string{ + "key": nil, + }, + want: `{"key"`, + wantErr: EM(nil).withPos(`{"key":`, "/key").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Maps/String/Deterministic"), + opts: []Options{Deterministic(true)}, + in: map[string]int{"a": 0, "b": 1, "c": 2}, + want: `{"a":0,"b":1,"c":2}`, + }, { + name: jsontest.Name("Maps/String/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), + opts: []Options{ + Deterministic(true), + jsontext.AllowInvalidUTF8(true), + jsontext.AllowDuplicateNames(false), + }, + in: map[string]int{"\xff": 0, "\xfe": 1}, + want: `{"�":1`, + wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":1,`)), + }, { + name: jsontest.Name("Maps/String/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), + opts: []Options{ + Deterministic(true), + jsontext.AllowInvalidUTF8(true), + jsontext.AllowDuplicateNames(true), + }, + in: map[string]int{"\xff": 0, "\xfe": 1}, + want: `{"�":1,"�":0}`, + }, { + name: jsontest.Name("Maps/String/Deterministic+MarshalFuncs"), + opts: []Options{ + Deterministic(true), + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v string) error { + if p := enc.StackPointer(); p != "/X" { + return fmt.Errorf("invalid stack pointer: got %s, want /X", p) + } + switch v { + case "a": + return enc.WriteToken(jsontext.String("b")) + case "b": + return enc.WriteToken(jsontext.String("a")) + default: + return fmt.Errorf("invalid value: %q", v) + } + })), + }, + in: map[namedString]map[string]int{"X": {"a": -1, "b": 1}}, + want: `{"X":{"a":1,"b":-1}}`, + }, { + name: jsontest.Name("Maps/String/Deterministic+MarshalFuncs+RejectDuplicateNames"), + opts: []Options{ + Deterministic(true), + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v string) error { + if p := enc.StackPointer(); p != "/X" { + return fmt.Errorf("invalid stack pointer: got %s, want /X", p) + } + switch v { + case "a", "b": + return enc.WriteToken(jsontext.String("x")) + default: + return fmt.Errorf("invalid value: %q", v) + } + })), + jsontext.AllowDuplicateNames(false), + }, + in: map[namedString]map[string]int{"X": {"a": 1, "b": 1}}, + want: `{"X":{"x":1`, + wantErr: newDuplicateNameError("/X/x", nil, len64(`{"X":{"x":1,`)), + }, { + name: jsontest.Name("Maps/String/Deterministic+MarshalFuncs+AllowDuplicateNames"), + opts: []Options{ + Deterministic(true), + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v string) error { + if p := enc.StackPointer(); p != "/X" { + return fmt.Errorf("invalid stack pointer: got %s, want /0", p) + } + switch v { + case "a", "b": + return enc.WriteToken(jsontext.String("x")) + default: + return fmt.Errorf("invalid value: %q", v) + } + })), + jsontext.AllowDuplicateNames(true), + }, + in: map[namedString]map[string]int{"X": {"a": 1, "b": 1}}, + // NOTE: Since the names are identical, the exact values may be + // non-deterministic since sort cannot distinguish between members. + want: `{"X":{"x":1,"x":1}}`, + }, { + name: jsontest.Name("Maps/RecursiveMap"), + in: recursiveMap{ + "fizz": { + "foo": {}, + "bar": nil, + }, + "buzz": nil, + }, + canonicalize: true, + want: `{"buzz":{},"fizz":{"bar":{},"foo":{}}}`, + }, { + name: jsontest.Name("Maps/CyclicMap"), + in: func() recursiveMap { + m := recursiveMap{"k": nil} + m["k"] = m + return m + }(), + want: strings.Repeat(`{"k":`, startDetectingCyclesAfter) + `{"k"`, + wantErr: EM(internal.ErrCycle).withPos(strings.Repeat(`{"k":`, startDetectingCyclesAfter+1), jsontext.Pointer(strings.Repeat("/k", startDetectingCyclesAfter+1))).withType(0, T[recursiveMap]()), + }, { + name: jsontest.Name("Maps/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: map[string]string{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/Empty"), + in: structEmpty{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/UnexportedIgnored"), + in: structUnexportedIgnored{ignored: "ignored"}, + want: `{}`, + }, { + name: jsontest.Name("Structs/IgnoredUnexportedEmbedded"), + in: structIgnoredUnexportedEmbedded{namedString: "ignored"}, + want: `{}`, + }, { + name: jsontest.Name("Structs/WeirdNames"), + in: structWeirdNames{Empty: "empty", Comma: "comma", Quote: "quote"}, + want: `{"":"empty",",":"comma","\"":"quote"}`, + }, { + name: jsontest.Name("Structs/EscapedNames"), + opts: []Options{jsontext.EscapeForHTML(true), jsontext.EscapeForJS(true)}, + in: struct { + S string "json:\"'abc<>&\u2028\u2029xyz'\"" + M any + I structInlineTextValue + }{ + S: "abc<>&\u2028\u2029xyz", + M: map[string]string{"abc<>&\u2028\u2029xyz": "abc<>&\u2028\u2029xyz"}, + I: structInlineTextValue{X: jsontext.Value(`{"abc<>&` + "\u2028\u2029" + `xyz":"abc<>&` + "\u2028\u2029" + `xyz"}`)}, + }, + want: `{"abc\u003c\u003e\u0026\u2028\u2029xyz":"abc\u003c\u003e\u0026\u2028\u2029xyz","M":{"abc\u003c\u003e\u0026\u2028\u2029xyz":"abc\u003c\u003e\u0026\u2028\u2029xyz"},"I":{"abc\u003c\u003e\u0026\u2028\u2029xyz":"abc\u003c\u003e\u0026\u2028\u2029xyz"}}`, + }, { + name: jsontest.Name("Structs/NoCase"), + in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}, + want: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, + }, { + name: jsontest.Name("Structs/NoCase/MatchCaseInsensitiveNames"), + opts: []Options{MatchCaseInsensitiveNames(true)}, + in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}, + want: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, + }, { + name: jsontest.Name("Structs/NoCase/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), + opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, + in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}, + want: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, + }, { + name: jsontest.Name("Structs/Normal"), + opts: []Options{jsontext.Multiline(true)}, + in: structAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, + MapUint: map[string]uint64{"": +64}, + MapFloat: map[string]float64{"": 3.14159}, + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, + SliceUint: []uint64{+64}, + SliceFloat: []float64{3.14159}, + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structAll), + Interface: (*structAll)(nil), + }, + want: `{ + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64, + "Uint": 64, + "Float": 3.14159, + "Map": { + "key": "value" + }, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64, + "Uint": 64, + "Float": 3.14159 + }, + "StructMaps": { + "MapBool": { + "": true + }, + "MapString": { + "": "hello" + }, + "MapBytes": { + "": "AQID" + }, + "MapInt": { + "": -64 + }, + "MapUint": { + "": 64 + }, + "MapFloat": { + "": 3.14159 + } + }, + "StructSlices": { + "SliceBool": [ + true + ], + "SliceString": [ + "hello" + ], + "SliceBytes": [ + "AQID" + ], + "SliceInt": [ + -64 + ], + "SliceUint": [ + 64 + ], + "SliceFloat": [ + 3.14159 + ] + }, + "Slice": [ + "fizz", + "buzz" + ], + "Array": [ + "goodbye" + ], + "Pointer": { + "Bool": false, + "String": "", + "Bytes": "", + "Int": 0, + "Uint": 0, + "Float": 0, + "Map": {}, + "StructScalars": { + "Bool": false, + "String": "", + "Bytes": "", + "Int": 0, + "Uint": 0, + "Float": 0 + }, + "StructMaps": { + "MapBool": {}, + "MapString": {}, + "MapBytes": {}, + "MapInt": {}, + "MapUint": {}, + "MapFloat": {} + }, + "StructSlices": { + "SliceBool": [], + "SliceString": [], + "SliceBytes": [], + "SliceInt": [], + "SliceUint": [], + "SliceFloat": [] + }, + "Slice": [], + "Array": [ + "" + ], + "Pointer": null, + "Interface": null + }, + "Interface": null +}`, + }, { + name: jsontest.Name("Structs/SpaceAfterColonAndComma"), + opts: []Options{jsontext.SpaceAfterColon(true), jsontext.SpaceAfterComma(true)}, + in: structOmitZeroAll{Int: 1, Uint: 1}, + want: `{"Int": 1, "Uint": 1}`, + }, { + name: jsontest.Name("Structs/SpaceAfterColon"), + opts: []Options{jsontext.SpaceAfterColon(true)}, + in: structOmitZeroAll{Int: 1, Uint: 1}, + want: `{"Int": 1,"Uint": 1}`, + }, { + name: jsontest.Name("Structs/SpaceAfterComma"), + opts: []Options{jsontext.SpaceAfterComma(true)}, + in: structOmitZeroAll{Int: 1, Uint: 1, Slice: []string{"a", "b"}}, + want: `{"Int":1, "Uint":1, "Slice":["a", "b"]}`, + }, { + name: jsontest.Name("Structs/Stringified"), + opts: []Options{jsontext.Multiline(true)}, + in: structStringifiedAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, // should be stringified + Uint: +64, // should be stringified + Float: 3.14159, // should be stringified + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, // should be stringified + Uint: +64, // should be stringified + Float: 3.14159, // should be stringified + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, // should be stringified + MapUint: map[string]uint64{"": +64}, // should be stringified + MapFloat: map[string]float64{"": 3.14159}, // should be stringified + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, // should be stringified + SliceUint: []uint64{+64}, // should be stringified + SliceFloat: []float64{3.14159}, // should be stringified + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structStringifiedAll), // should be stringified + Interface: (*structStringifiedAll)(nil), + }, + want: `{ + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159", + "Map": { + "key": "value" + }, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159" + }, + "StructMaps": { + "MapBool": { + "": true + }, + "MapString": { + "": "hello" + }, + "MapBytes": { + "": "AQID" + }, + "MapInt": { + "": "-64" + }, + "MapUint": { + "": "64" + }, + "MapFloat": { + "": "3.14159" + } + }, + "StructSlices": { + "SliceBool": [ + true + ], + "SliceString": [ + "hello" + ], + "SliceBytes": [ + "AQID" + ], + "SliceInt": [ + "-64" + ], + "SliceUint": [ + "64" + ], + "SliceFloat": [ + "3.14159" + ] + }, + "Slice": [ + "fizz", + "buzz" + ], + "Array": [ + "goodbye" + ], + "Pointer": { + "Bool": false, + "String": "", + "Bytes": "", + "Int": "0", + "Uint": "0", + "Float": "0", + "Map": {}, + "StructScalars": { + "Bool": false, + "String": "", + "Bytes": "", + "Int": "0", + "Uint": "0", + "Float": "0" + }, + "StructMaps": { + "MapBool": {}, + "MapString": {}, + "MapBytes": {}, + "MapInt": {}, + "MapUint": {}, + "MapFloat": {} + }, + "StructSlices": { + "SliceBool": [], + "SliceString": [], + "SliceBytes": [], + "SliceInt": [], + "SliceUint": [], + "SliceFloat": [] + }, + "Slice": [], + "Array": [ + "" + ], + "Pointer": null, + "Interface": null + }, + "Interface": null +}`, + }, { + name: jsontest.Name("Structs/LegacyStringified"), + opts: []Options{jsontext.Multiline(true), jsonflags.StringifyWithLegacySemantics | 1}, + in: structStringifiedAll{ + Bool: true, // should be stringified + String: "hello", // should be stringified + Bytes: []byte{1, 2, 3}, + Int: -64, // should be stringified + Uint: +64, // should be stringified + Float: 3.14159, // should be stringified + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, + MapUint: map[string]uint64{"": +64}, + MapFloat: map[string]float64{"": 3.14159}, + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, + SliceUint: []uint64{+64}, + SliceFloat: []float64{3.14159}, + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structStringifiedAll), // should be stringified + Interface: (*structStringifiedAll)(nil), + }, + want: `{ + "Bool": "true", + "String": "\"hello\"", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159", + "Map": { + "key": "value" + }, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64, + "Uint": 64, + "Float": 3.14159 + }, + "StructMaps": { + "MapBool": { + "": true + }, + "MapString": { + "": "hello" + }, + "MapBytes": { + "": "AQID" + }, + "MapInt": { + "": -64 + }, + "MapUint": { + "": 64 + }, + "MapFloat": { + "": 3.14159 + } + }, + "StructSlices": { + "SliceBool": [ + true + ], + "SliceString": [ + "hello" + ], + "SliceBytes": [ + "AQID" + ], + "SliceInt": [ + -64 + ], + "SliceUint": [ + 64 + ], + "SliceFloat": [ + 3.14159 + ] + }, + "Slice": [ + "fizz", + "buzz" + ], + "Array": [ + "goodbye" + ], + "Pointer": { + "Bool": "false", + "String": "\"\"", + "Bytes": "", + "Int": "0", + "Uint": "0", + "Float": "0", + "Map": {}, + "StructScalars": { + "Bool": false, + "String": "", + "Bytes": "", + "Int": 0, + "Uint": 0, + "Float": 0 + }, + "StructMaps": { + "MapBool": {}, + "MapString": {}, + "MapBytes": {}, + "MapInt": {}, + "MapUint": {}, + "MapFloat": {} + }, + "StructSlices": { + "SliceBool": [], + "SliceString": [], + "SliceBytes": [], + "SliceInt": [], + "SliceUint": [], + "SliceFloat": [] + }, + "Slice": [], + "Array": [ + "" + ], + "Pointer": null, + "Interface": null + }, + "Interface": null +}`, + }, { + name: jsontest.Name("Structs/OmitZero/Zero"), + in: structOmitZeroAll{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/OmitZeroOption/Zero"), + opts: []Options{OmitZeroStructFields(true)}, + in: structAll{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/OmitZero/NonZero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitZeroAll{ + Bool: true, // not omitted since true is non-zero + String: " ", // not omitted since non-empty string is non-zero + Bytes: []byte{}, // not omitted since allocated slice is non-zero + Int: 1, // not omitted since 1 is non-zero + Uint: 1, // not omitted since 1 is non-zero + Float: math.SmallestNonzeroFloat64, // not omitted since still slightly above zero + Map: map[string]string{}, // not omitted since allocated map is non-zero + StructScalars: structScalars{unexported: true}, // not omitted since unexported is non-zero + StructSlices: structSlices{Ignored: true}, // not omitted since Ignored is non-zero + StructMaps: structMaps{MapBool: map[string]bool{}}, // not omitted since MapBool is non-zero + Slice: []string{}, // not omitted since allocated slice is non-zero + Array: [1]string{" "}, // not omitted since single array element is non-zero + Pointer: new(structOmitZeroAll), // not omitted since pointer is non-zero (even if all fields of the struct value are zero) + Interface: (*structOmitZeroAll)(nil), // not omitted since interface value is non-zero (even if interface value is a nil pointer) + }, + want: `{ + "Bool": true, + "String": " ", + "Bytes": "", + "Int": 1, + "Uint": 1, + "Float": 5e-324, + "Map": {}, + "StructScalars": { + "Bool": false, + "String": "", + "Bytes": "", + "Int": 0, + "Uint": 0, + "Float": 0 + }, + "StructMaps": { + "MapBool": {}, + "MapString": {}, + "MapBytes": {}, + "MapInt": {}, + "MapUint": {}, + "MapFloat": {} + }, + "StructSlices": { + "SliceBool": [], + "SliceString": [], + "SliceBytes": [], + "SliceInt": [], + "SliceUint": [], + "SliceFloat": [] + }, + "Slice": [], + "Array": [ + " " + ], + "Pointer": {}, + "Interface": null +}`, + }, { + name: jsontest.Name("Structs/OmitZeroOption/NonZero"), + opts: []Options{OmitZeroStructFields(true), jsontext.Multiline(true)}, + in: structAll{ + Bool: true, + String: " ", + Bytes: []byte{}, + Int: 1, + Uint: 1, + Float: math.SmallestNonzeroFloat64, + Map: map[string]string{}, + StructScalars: structScalars{unexported: true}, + StructSlices: structSlices{Ignored: true}, + StructMaps: structMaps{MapBool: map[string]bool{}}, + Slice: []string{}, + Array: [1]string{" "}, + Pointer: new(structAll), + Interface: (*structAll)(nil), + }, + want: `{ + "Bool": true, + "String": " ", + "Bytes": "", + "Int": 1, + "Uint": 1, + "Float": 5e-324, + "Map": {}, + "StructScalars": {}, + "StructMaps": { + "MapBool": {} + }, + "StructSlices": {}, + "Slice": [], + "Array": [ + " " + ], + "Pointer": {}, + "Interface": null +}`, + }, { + name: jsontest.Name("Structs/OmitZeroMethod/Zero"), + in: structOmitZeroMethodAll{}, + want: `{"ValueNeverZero":"","PointerNeverZero":""}`, + }, { + name: jsontest.Name("Structs/OmitZeroMethod/NonZero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitZeroMethodAll{ + ValueAlwaysZero: valueAlwaysZero("nonzero"), + ValueNeverZero: valueNeverZero("nonzero"), + PointerAlwaysZero: pointerAlwaysZero("nonzero"), + PointerNeverZero: pointerNeverZero("nonzero"), + PointerValueAlwaysZero: addr(valueAlwaysZero("nonzero")), + PointerValueNeverZero: addr(valueNeverZero("nonzero")), + PointerPointerAlwaysZero: addr(pointerAlwaysZero("nonzero")), + PointerPointerNeverZero: addr(pointerNeverZero("nonzero")), + PointerPointerValueAlwaysZero: addr(addr(valueAlwaysZero("nonzero"))), // marshaled since **valueAlwaysZero does not implement IsZero + PointerPointerValueNeverZero: addr(addr(valueNeverZero("nonzero"))), + PointerPointerPointerAlwaysZero: addr(addr(pointerAlwaysZero("nonzero"))), // marshaled since **pointerAlwaysZero does not implement IsZero + PointerPointerPointerNeverZero: addr(addr(pointerNeverZero("nonzero"))), + }, + want: `{ + "ValueNeverZero": "nonzero", + "PointerNeverZero": "nonzero", + "PointerValueNeverZero": "nonzero", + "PointerPointerNeverZero": "nonzero", + "PointerPointerValueAlwaysZero": "nonzero", + "PointerPointerValueNeverZero": "nonzero", + "PointerPointerPointerAlwaysZero": "nonzero", + "PointerPointerPointerNeverZero": "nonzero" +}`, + }, { + name: jsontest.Name("Structs/OmitZeroMethod/Interface/Zero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitZeroMethodInterfaceAll{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/OmitZeroMethod/Interface/PartialZero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitZeroMethodInterfaceAll{ + ValueAlwaysZero: valueAlwaysZero(""), + ValueNeverZero: valueNeverZero(""), + PointerValueAlwaysZero: (*valueAlwaysZero)(nil), + PointerValueNeverZero: (*valueNeverZero)(nil), // nil pointer, so method not called + PointerPointerAlwaysZero: (*pointerAlwaysZero)(nil), + PointerPointerNeverZero: (*pointerNeverZero)(nil), // nil pointer, so method not called + }, + want: `{ + "ValueNeverZero": "" +}`, + }, { + name: jsontest.Name("Structs/OmitZeroMethod/Interface/NonZero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitZeroMethodInterfaceAll{ + ValueAlwaysZero: valueAlwaysZero("nonzero"), + ValueNeverZero: valueNeverZero("nonzero"), + PointerValueAlwaysZero: addr(valueAlwaysZero("nonzero")), + PointerValueNeverZero: addr(valueNeverZero("nonzero")), + PointerPointerAlwaysZero: addr(pointerAlwaysZero("nonzero")), + PointerPointerNeverZero: addr(pointerNeverZero("nonzero")), + }, + want: `{ + "ValueNeverZero": "nonzero", + "PointerValueNeverZero": "nonzero", + "PointerPointerNeverZero": "nonzero" +}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/Zero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitEmptyAll{}, + want: `{ + "Bool": false, + "StringNonEmpty": "value", + "BytesNonEmpty": [ + "value" + ], + "Float": 0, + "MapNonEmpty": { + "key": "value" + }, + "SliceNonEmpty": [ + "value" + ] +}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/EmptyNonZero"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitEmptyAll{ + String: string(""), + StringEmpty: stringMarshalEmpty(""), + StringNonEmpty: stringMarshalNonEmpty(""), + PointerString: addr(string("")), + PointerStringEmpty: addr(stringMarshalEmpty("")), + PointerStringNonEmpty: addr(stringMarshalNonEmpty("")), + Bytes: []byte(""), + BytesEmpty: bytesMarshalEmpty([]byte("")), + BytesNonEmpty: bytesMarshalNonEmpty([]byte("")), + PointerBytes: addr([]byte("")), + PointerBytesEmpty: addr(bytesMarshalEmpty([]byte(""))), + PointerBytesNonEmpty: addr(bytesMarshalNonEmpty([]byte(""))), + Map: map[string]string{}, + MapEmpty: mapMarshalEmpty{}, + MapNonEmpty: mapMarshalNonEmpty{}, + PointerMap: addr(map[string]string{}), + PointerMapEmpty: addr(mapMarshalEmpty{}), + PointerMapNonEmpty: addr(mapMarshalNonEmpty{}), + Slice: []string{}, + SliceEmpty: sliceMarshalEmpty{}, + SliceNonEmpty: sliceMarshalNonEmpty{}, + PointerSlice: addr([]string{}), + PointerSliceEmpty: addr(sliceMarshalEmpty{}), + PointerSliceNonEmpty: addr(sliceMarshalNonEmpty{}), + Pointer: &structOmitZeroEmptyAll{}, + Interface: []string{}, + }, + want: `{ + "Bool": false, + "StringNonEmpty": "value", + "PointerStringNonEmpty": "value", + "BytesNonEmpty": [ + "value" + ], + "PointerBytesNonEmpty": [ + "value" + ], + "Float": 0, + "MapNonEmpty": { + "key": "value" + }, + "PointerMapNonEmpty": { + "key": "value" + }, + "SliceNonEmpty": [ + "value" + ], + "PointerSliceNonEmpty": [ + "value" + ] +}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/NonEmpty"), + opts: []Options{jsontext.Multiline(true)}, + in: structOmitEmptyAll{ + Bool: true, + PointerBool: addr(true), + String: string("value"), + StringEmpty: stringMarshalEmpty("value"), + StringNonEmpty: stringMarshalNonEmpty("value"), + PointerString: addr(string("value")), + PointerStringEmpty: addr(stringMarshalEmpty("value")), + PointerStringNonEmpty: addr(stringMarshalNonEmpty("value")), + Bytes: []byte("value"), + BytesEmpty: bytesMarshalEmpty([]byte("value")), + BytesNonEmpty: bytesMarshalNonEmpty([]byte("value")), + PointerBytes: addr([]byte("value")), + PointerBytesEmpty: addr(bytesMarshalEmpty([]byte("value"))), + PointerBytesNonEmpty: addr(bytesMarshalNonEmpty([]byte("value"))), + Float: math.Copysign(0, -1), + PointerFloat: addr(math.Copysign(0, -1)), + Map: map[string]string{"": ""}, + MapEmpty: mapMarshalEmpty{"key": "value"}, + MapNonEmpty: mapMarshalNonEmpty{"key": "value"}, + PointerMap: addr(map[string]string{"": ""}), + PointerMapEmpty: addr(mapMarshalEmpty{"key": "value"}), + PointerMapNonEmpty: addr(mapMarshalNonEmpty{"key": "value"}), + Slice: []string{""}, + SliceEmpty: sliceMarshalEmpty{"value"}, + SliceNonEmpty: sliceMarshalNonEmpty{"value"}, + PointerSlice: addr([]string{""}), + PointerSliceEmpty: addr(sliceMarshalEmpty{"value"}), + PointerSliceNonEmpty: addr(sliceMarshalNonEmpty{"value"}), + Pointer: &structOmitZeroEmptyAll{Float: math.SmallestNonzeroFloat64}, + Interface: []string{""}, + }, + want: `{ + "Bool": true, + "PointerBool": true, + "String": "value", + "StringNonEmpty": "value", + "PointerString": "value", + "PointerStringNonEmpty": "value", + "Bytes": "dmFsdWU=", + "BytesNonEmpty": [ + "value" + ], + "PointerBytes": "dmFsdWU=", + "PointerBytesNonEmpty": [ + "value" + ], + "Float": -0, + "PointerFloat": -0, + "Map": { + "": "" + }, + "MapNonEmpty": { + "key": "value" + }, + "PointerMap": { + "": "" + }, + "PointerMapNonEmpty": { + "key": "value" + }, + "Slice": [ + "" + ], + "SliceNonEmpty": [ + "value" + ], + "PointerSlice": [ + "" + ], + "PointerSliceNonEmpty": [ + "value" + ], + "Pointer": { + "Float": 5e-324 + }, + "Interface": [ + "" + ] +}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/Legacy/Zero"), + opts: []Options{jsonflags.OmitEmptyWithLegacyDefinition | 1}, + in: structOmitEmptyAll{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/Legacy/NonEmpty"), + opts: []Options{jsontext.Multiline(true), jsonflags.OmitEmptyWithLegacyDefinition | 1}, + in: structOmitEmptyAll{ + Bool: true, + PointerBool: addr(true), + String: string("value"), + StringEmpty: stringMarshalEmpty("value"), + StringNonEmpty: stringMarshalNonEmpty("value"), + PointerString: addr(string("value")), + PointerStringEmpty: addr(stringMarshalEmpty("value")), + PointerStringNonEmpty: addr(stringMarshalNonEmpty("value")), + Bytes: []byte("value"), + BytesEmpty: bytesMarshalEmpty([]byte("value")), + BytesNonEmpty: bytesMarshalNonEmpty([]byte("value")), + PointerBytes: addr([]byte("value")), + PointerBytesEmpty: addr(bytesMarshalEmpty([]byte("value"))), + PointerBytesNonEmpty: addr(bytesMarshalNonEmpty([]byte("value"))), + Float: math.Copysign(0, -1), + PointerFloat: addr(math.Copysign(0, -1)), + Map: map[string]string{"": ""}, + MapEmpty: mapMarshalEmpty{"key": "value"}, + MapNonEmpty: mapMarshalNonEmpty{"key": "value"}, + PointerMap: addr(map[string]string{"": ""}), + PointerMapEmpty: addr(mapMarshalEmpty{"key": "value"}), + PointerMapNonEmpty: addr(mapMarshalNonEmpty{"key": "value"}), + Slice: []string{""}, + SliceEmpty: sliceMarshalEmpty{"value"}, + SliceNonEmpty: sliceMarshalNonEmpty{"value"}, + PointerSlice: addr([]string{""}), + PointerSliceEmpty: addr(sliceMarshalEmpty{"value"}), + PointerSliceNonEmpty: addr(sliceMarshalNonEmpty{"value"}), + Pointer: &structOmitZeroEmptyAll{Float: math.Copysign(0, -1)}, + Interface: []string{""}, + }, + want: `{ + "Bool": true, + "PointerBool": true, + "String": "value", + "StringEmpty": "", + "StringNonEmpty": "value", + "PointerString": "value", + "PointerStringEmpty": "", + "PointerStringNonEmpty": "value", + "Bytes": "dmFsdWU=", + "BytesEmpty": [], + "BytesNonEmpty": [ + "value" + ], + "PointerBytes": "dmFsdWU=", + "PointerBytesEmpty": [], + "PointerBytesNonEmpty": [ + "value" + ], + "PointerFloat": -0, + "Map": { + "": "" + }, + "MapEmpty": {}, + "MapNonEmpty": { + "key": "value" + }, + "PointerMap": { + "": "" + }, + "PointerMapEmpty": {}, + "PointerMapNonEmpty": { + "key": "value" + }, + "Slice": [ + "" + ], + "SliceEmpty": [], + "SliceNonEmpty": [ + "value" + ], + "PointerSlice": [ + "" + ], + "PointerSliceEmpty": [], + "PointerSliceNonEmpty": [ + "value" + ], + "Pointer": {}, + "Interface": [ + "" + ] +}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/NonEmptyString"), + in: struct { + X string `json:",omitempty"` + }{`"`}, + want: `{"X":"\""}`, + }, { + name: jsontest.Name("Structs/OmitZeroEmpty/Zero"), + in: structOmitZeroEmptyAll{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/OmitZeroEmpty/Empty"), + in: structOmitZeroEmptyAll{ + Bytes: []byte{}, + Map: map[string]string{}, + Slice: []string{}, + Pointer: &structOmitZeroEmptyAll{}, + Interface: []string{}, + }, + want: `{}`, + }, { + name: jsontest.Name("Structs/OmitEmpty/PathologicalDepth"), + in: func() any { + type X struct { + X *X `json:",omitempty"` + } + var make func(int) *X + make = func(n int) *X { + if n == 0 { + return nil + } + return &X{make(n - 1)} + } + return make(100) + }(), + want: `{}`, + useWriter: true, + }, { + name: jsontest.Name("Structs/OmitEmpty/PathologicalBreadth"), + in: func() any { + var fields []reflect.StructField + for i := range 100 { + fields = append(fields, reflect.StructField{ + Name: fmt.Sprintf("X%d", i), + Type: T[stringMarshalEmpty](), + Tag: `json:",omitempty"`, + }) + } + return reflect.New(reflect.StructOf(fields)).Interface() + }(), + want: `{}`, + useWriter: true, + }, { + name: jsontest.Name("Structs/OmitEmpty/PathologicalTree"), + in: func() any { + type X struct { + XL, XR *X `json:",omitempty"` + } + var make func(int) *X + make = func(n int) *X { + if n == 0 { + return nil + } + return &X{make(n - 1), make(n - 1)} + } + return make(8) + }(), + want: `{}`, + useWriter: true, + }, { + name: jsontest.Name("Structs/OmitZeroEmpty/NonEmpty"), + in: structOmitZeroEmptyAll{ + Bytes: []byte("value"), + Map: map[string]string{"": ""}, + Slice: []string{""}, + Pointer: &structOmitZeroEmptyAll{Bool: true}, + Interface: []string{""}, + }, + want: `{"Bytes":"dmFsdWU=","Map":{"":""},"Slice":[""],"Pointer":{"Bool":true},"Interface":[""]}`, + }, { + name: jsontest.Name("Structs/Format/Bytes"), + opts: []Options{jsontext.Multiline(true)}, + in: structFormatBytes{ + Base16: []byte("\x01\x23\x45\x67\x89\xab\xcd\xef"), + Base32: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), + Base32Hex: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), + Base64: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2Û¯\xc3\x1c\xb3\xd3]\xb7㞻\xf3ß¿"), + Base64URL: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2Û¯\xc3\x1c\xb3\xd3]\xb7㞻\xf3ß¿"), + Array: []byte{1, 2, 3, 4}, + }, + want: `{ + "Base16": "0123456789abcdef", + "Base32": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", + "Base32Hex": "0123456789ABCDEFGHIJKLMNOPQRSTUV", + "Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + "Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + "Array": [ + 1, + 2, + 3, + 4 + ] +}`}, { + name: jsontest.Name("Structs/Format/ArrayBytes"), + opts: []Options{jsontext.Multiline(true)}, + in: structFormatArrayBytes{ + Base16: [4]byte{1, 2, 3, 4}, + Base32: [4]byte{1, 2, 3, 4}, + Base32Hex: [4]byte{1, 2, 3, 4}, + Base64: [4]byte{1, 2, 3, 4}, + Base64URL: [4]byte{1, 2, 3, 4}, + Array: [4]byte{1, 2, 3, 4}, + Default: [4]byte{1, 2, 3, 4}, + }, + want: `{ + "Base16": "01020304", + "Base32": "AEBAGBA=", + "Base32Hex": "0410610=", + "Base64": "AQIDBA==", + "Base64URL": "AQIDBA==", + "Array": [ + 1, + 2, + 3, + 4 + ], + "Default": "AQIDBA==" +}`}, { + name: jsontest.Name("Structs/Format/ArrayBytes/Legacy"), + opts: []Options{jsontext.Multiline(true), jsonflags.FormatBytesWithLegacySemantics | 1}, + in: structFormatArrayBytes{ + Base16: [4]byte{1, 2, 3, 4}, + Base32: [4]byte{1, 2, 3, 4}, + Base32Hex: [4]byte{1, 2, 3, 4}, + Base64: [4]byte{1, 2, 3, 4}, + Base64URL: [4]byte{1, 2, 3, 4}, + Array: [4]byte{1, 2, 3, 4}, + Default: [4]byte{1, 2, 3, 4}, + }, + want: `{ + "Base16": "01020304", + "Base32": "AEBAGBA=", + "Base32Hex": "0410610=", + "Base64": "AQIDBA==", + "Base64URL": "AQIDBA==", + "Array": [ + 1, + 2, + 3, + 4 + ], + "Default": [ + 1, + 2, + 3, + 4 + ] +}`}, { + name: jsontest.Name("Structs/Format/Bytes/Array"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(in byte) ([]byte, error) { + if in > 3 { + return []byte("true"), nil + } else { + return []byte("false"), nil + } + })), + }, + in: struct { + Array []byte `json:",format:array"` + }{ + Array: []byte{1, 6, 2, 5, 3, 4}, + }, + want: `{"Array":[false,true,false,true,false,true]}`, + }, { + name: jsontest.Name("Structs/Format/Floats"), + opts: []Options{jsontext.Multiline(true)}, + in: []structFormatFloats{ + {NonFinite: math.Pi, PointerNonFinite: addr(math.Pi)}, + {NonFinite: math.NaN(), PointerNonFinite: addr(math.NaN())}, + {NonFinite: math.Inf(-1), PointerNonFinite: addr(math.Inf(-1))}, + {NonFinite: math.Inf(+1), PointerNonFinite: addr(math.Inf(+1))}, + }, + want: `[ + { + "NonFinite": 3.141592653589793, + "PointerNonFinite": 3.141592653589793 + }, + { + "NonFinite": "NaN", + "PointerNonFinite": "NaN" + }, + { + "NonFinite": "-Infinity", + "PointerNonFinite": "-Infinity" + }, + { + "NonFinite": "Infinity", + "PointerNonFinite": "Infinity" + } +]`, + }, { + name: jsontest.Name("Structs/Format/Maps"), + opts: []Options{jsontext.Multiline(true)}, + in: []structFormatMaps{{ + EmitNull: map[string]string(nil), PointerEmitNull: addr(map[string]string(nil)), + EmitEmpty: map[string]string(nil), PointerEmitEmpty: addr(map[string]string(nil)), + EmitDefault: map[string]string(nil), PointerEmitDefault: addr(map[string]string(nil)), + }, { + EmitNull: map[string]string{}, PointerEmitNull: addr(map[string]string{}), + EmitEmpty: map[string]string{}, PointerEmitEmpty: addr(map[string]string{}), + EmitDefault: map[string]string{}, PointerEmitDefault: addr(map[string]string{}), + }, { + EmitNull: map[string]string{"k": "v"}, PointerEmitNull: addr(map[string]string{"k": "v"}), + EmitEmpty: map[string]string{"k": "v"}, PointerEmitEmpty: addr(map[string]string{"k": "v"}), + EmitDefault: map[string]string{"k": "v"}, PointerEmitDefault: addr(map[string]string{"k": "v"}), + }}, + want: `[ + { + "EmitNull": null, + "PointerEmitNull": null, + "EmitEmpty": {}, + "PointerEmitEmpty": {}, + "EmitDefault": {}, + "PointerEmitDefault": {} + }, + { + "EmitNull": {}, + "PointerEmitNull": {}, + "EmitEmpty": {}, + "PointerEmitEmpty": {}, + "EmitDefault": {}, + "PointerEmitDefault": {} + }, + { + "EmitNull": { + "k": "v" + }, + "PointerEmitNull": { + "k": "v" + }, + "EmitEmpty": { + "k": "v" + }, + "PointerEmitEmpty": { + "k": "v" + }, + "EmitDefault": { + "k": "v" + }, + "PointerEmitDefault": { + "k": "v" + } + } +]`, + }, { + name: jsontest.Name("Structs/Format/Maps/FormatNilMapAsNull"), + opts: []Options{ + FormatNilMapAsNull(true), + jsontext.Multiline(true), + }, + in: []structFormatMaps{{ + EmitNull: map[string]string(nil), PointerEmitNull: addr(map[string]string(nil)), + EmitEmpty: map[string]string(nil), PointerEmitEmpty: addr(map[string]string(nil)), + EmitDefault: map[string]string(nil), PointerEmitDefault: addr(map[string]string(nil)), + }, { + EmitNull: map[string]string{}, PointerEmitNull: addr(map[string]string{}), + EmitEmpty: map[string]string{}, PointerEmitEmpty: addr(map[string]string{}), + EmitDefault: map[string]string{}, PointerEmitDefault: addr(map[string]string{}), + }, { + EmitNull: map[string]string{"k": "v"}, PointerEmitNull: addr(map[string]string{"k": "v"}), + EmitEmpty: map[string]string{"k": "v"}, PointerEmitEmpty: addr(map[string]string{"k": "v"}), + EmitDefault: map[string]string{"k": "v"}, PointerEmitDefault: addr(map[string]string{"k": "v"}), + }}, + want: `[ + { + "EmitNull": null, + "PointerEmitNull": null, + "EmitEmpty": {}, + "PointerEmitEmpty": {}, + "EmitDefault": null, + "PointerEmitDefault": null + }, + { + "EmitNull": {}, + "PointerEmitNull": {}, + "EmitEmpty": {}, + "PointerEmitEmpty": {}, + "EmitDefault": {}, + "PointerEmitDefault": {} + }, + { + "EmitNull": { + "k": "v" + }, + "PointerEmitNull": { + "k": "v" + }, + "EmitEmpty": { + "k": "v" + }, + "PointerEmitEmpty": { + "k": "v" + }, + "EmitDefault": { + "k": "v" + }, + "PointerEmitDefault": { + "k": "v" + } + } +]`, + }, { + name: jsontest.Name("Structs/Format/Slices"), + opts: []Options{jsontext.Multiline(true)}, + in: []structFormatSlices{{ + EmitNull: []string(nil), PointerEmitNull: addr([]string(nil)), + EmitEmpty: []string(nil), PointerEmitEmpty: addr([]string(nil)), + EmitDefault: []string(nil), PointerEmitDefault: addr([]string(nil)), + }, { + EmitNull: []string{}, PointerEmitNull: addr([]string{}), + EmitEmpty: []string{}, PointerEmitEmpty: addr([]string{}), + EmitDefault: []string{}, PointerEmitDefault: addr([]string{}), + }, { + EmitNull: []string{"v"}, PointerEmitNull: addr([]string{"v"}), + EmitEmpty: []string{"v"}, PointerEmitEmpty: addr([]string{"v"}), + EmitDefault: []string{"v"}, PointerEmitDefault: addr([]string{"v"}), + }}, + want: `[ + { + "EmitNull": null, + "PointerEmitNull": null, + "EmitEmpty": [], + "PointerEmitEmpty": [], + "EmitDefault": [], + "PointerEmitDefault": [] + }, + { + "EmitNull": [], + "PointerEmitNull": [], + "EmitEmpty": [], + "PointerEmitEmpty": [], + "EmitDefault": [], + "PointerEmitDefault": [] + }, + { + "EmitNull": [ + "v" + ], + "PointerEmitNull": [ + "v" + ], + "EmitEmpty": [ + "v" + ], + "PointerEmitEmpty": [ + "v" + ], + "EmitDefault": [ + "v" + ], + "PointerEmitDefault": [ + "v" + ] + } +]`, + }, { + name: jsontest.Name("Structs/Format/Invalid/Bool"), + in: structFormatInvalid{Bool: true}, + want: `{"Bool"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, boolType), + }, { + name: jsontest.Name("Structs/Format/Invalid/String"), + in: structFormatInvalid{String: "string"}, + want: `{"String"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"String":`, "/String").withType(0, stringType), + }, { + name: jsontest.Name("Structs/Format/Invalid/Bytes"), + in: structFormatInvalid{Bytes: []byte("bytes")}, + want: `{"Bytes"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Bytes":`, "/Bytes").withType(0, bytesType), + }, { + name: jsontest.Name("Structs/Format/Invalid/Int"), + in: structFormatInvalid{Int: 1}, + want: `{"Int"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Int":`, "/Int").withType(0, T[int64]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Uint"), + in: structFormatInvalid{Uint: 1}, + want: `{"Uint"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Uint":`, "/Uint").withType(0, T[uint64]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Float"), + in: structFormatInvalid{Float: 1}, + want: `{"Float"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Float":`, "/Float").withType(0, T[float64]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Map"), + in: structFormatInvalid{Map: map[string]string{}}, + want: `{"Map"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Map":`, "/Map").withType(0, T[map[string]string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Struct"), + in: structFormatInvalid{Struct: structAll{Bool: true}}, + want: `{"Struct"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Struct":`, "/Struct").withType(0, T[structAll]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Slice"), + in: structFormatInvalid{Slice: []string{}}, + want: `{"Slice"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Slice":`, "/Slice").withType(0, T[[]string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Array"), + in: structFormatInvalid{Array: [1]string{"string"}}, + want: `{"Array"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Array":`, "/Array").withType(0, T[[1]string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Interface"), + in: structFormatInvalid{Interface: "anything"}, + want: `{"Interface"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"Interface":`, "/Interface").withType(0, T[any]()), + }, { + name: jsontest.Name("Structs/Inline/Zero"), + in: structInlined{}, + want: `{"D":""}`, + }, { + name: jsontest.Name("Structs/Inline/Alloc"), + in: structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{}, + StructEmbed1: StructEmbed1{}, + }, + StructEmbed2: &StructEmbed2{}, + }, + want: `{"A":"","B":"","D":"","E":"","F":"","G":""}`, + }, { + name: jsontest.Name("Structs/Inline/NonZero"), + in: structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{A: "A1", B: "B1", C: "C1"}, + StructEmbed1: StructEmbed1{C: "C2", D: "D2", E: "E2"}, + }, + StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, + }, + want: `{"A":"A1","B":"B1","D":"D2","E":"E3","F":"F3","G":"G3"}`, + }, { + name: jsontest.Name("Structs/Inline/DualCycle"), + in: cyclicA{ + B1: cyclicB{F: 1}, // B1.F ignored since it conflicts with B2.F + B2: cyclicB{F: 2}, // B2.F ignored since it conflicts with B1.F + }, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Nil"), + in: structInlineTextValue{X: jsontext.Value(nil)}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Empty"), + in: structInlineTextValue{X: jsontext.Value("")}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/NonEmptyN1"), + in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" } `)}, + want: `{"fizz":"buzz"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/NonEmptyN2"), + in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "foo" : "bar" } `)}, + want: `{"fizz":"buzz","foo":"bar"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/NonEmptyWithOthers"), + in: structInlineTextValue{ + A: 1, + X: jsontext.Value(` { "fizz" : "buzz" , "foo" : "bar" } `), + B: 2, + }, + // NOTE: Inlined fallback fields are always serialized last. + want: `{"A":1,"B":2,"fizz":"buzz","foo":"bar"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/RejectDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(false)}, + in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "fizz" : "buzz" } `)}, + want: `{"fizz":"buzz"`, + wantErr: newDuplicateNameError("/fizz", nil, len64(`{"fizz":"buzz"`)), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + in: structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" , "fizz" : "buzz" } `)}, + want: `{"fizz":"buzz","fizz":"buzz"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/RejectInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(false)}, + in: structInlineTextValue{X: jsontext.Value(`{"` + "\xde\xad\xbe\xef" + `":"value"}`)}, + want: `{`, + wantErr: newInvalidUTF8Error(len64(`{"`+"\xde\xad"), ""), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/AllowInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: structInlineTextValue{X: jsontext.Value(`{"` + "\xde\xad\xbe\xef" + `":"value"}`)}, + want: `{"ޭ��":"value"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidWhitespace"), + in: structInlineTextValue{X: jsontext.Value("\n\r\t ")}, + want: `{`, + wantErr: EM(io.ErrUnexpectedEOF).withPos(`{`, "").withType(0, T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidObject"), + in: structInlineTextValue{X: jsontext.Value(` true `)}, + want: `{`, + wantErr: EM(errRawInlinedNotObject).withPos(`{`, "").withType(0, T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidObjectName"), + in: structInlineTextValue{X: jsontext.Value(` { true : false } `)}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(" { "), "")).withPos(`{`, "").withType(0, T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidEndObject"), + in: structInlineTextValue{X: jsontext.Value(` { "name" : false , } `)}, + want: `{"name":false`, + wantErr: EM(newInvalidCharacterError(",", "at start of value", len64(` { "name" : false `), "")).withPos(`{"name":false,`, "").withType(0, T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/InvalidDualObject"), + in: structInlineTextValue{X: jsontext.Value(`{}{}`)}, + want: `{`, + wantErr: EM(newInvalidCharacterError("{", "after top-level value", len64(`{}`), "")).withPos(`{`, "").withType(0, T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Nil"), + in: structInlinePointerInlineTextValue{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Nil"), + in: structInlinePointerTextValue{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/NonEmpty"), + in: structInlinePointerTextValue{X: addr(jsontext.Value(` { "fizz" : "buzz" } `))}, + want: `{"fizz":"buzz"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Nested/Nil"), + in: structInlineInlinePointerTextValue{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nil"), + in: structInlineMapStringAny{X: nil}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Empty"), + in: structInlineMapStringAny{X: make(jsonObject)}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/NonEmptyN1"), + in: structInlineMapStringAny{X: jsonObject{"fizz": nil}}, + want: `{"fizz":null}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/NonEmptyN2"), + in: structInlineMapStringAny{X: jsonObject{"fizz": time.Time{}, "buzz": math.Pi}}, + want: `{"buzz":3.141592653589793,"fizz":"0001-01-01T00:00:00Z"}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/NonEmptyWithOthers"), + in: structInlineMapStringAny{ + A: 1, + X: jsonObject{"fizz": nil}, + B: 2, + }, + // NOTE: Inlined fallback fields are always serialized last. + want: `{"A":1,"B":2,"fizz":null}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/RejectInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(false)}, + in: structInlineMapStringAny{X: jsonObject{"\xde\xad\xbe\xef": nil}}, + want: `{`, + wantErr: EM(jsonwire.ErrInvalidUTF8).withPos(`{`, "").withType(0, stringType), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/AllowInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: structInlineMapStringAny{X: jsonObject{"\xde\xad\xbe\xef": nil}}, + want: `{"ޭ��":null}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/InvalidValue"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: structInlineMapStringAny{X: jsonObject{"name": make(chan string)}}, + want: `{"name"`, + wantErr: EM(nil).withPos(`{"name":`, "/name").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Nil"), + in: structInlinePointerInlineMapStringAny{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MarshalFunc"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { + return []byte(fmt.Sprintf(`"%v"`, v)), nil + })), + }, + in: structInlineMapStringAny{X: jsonObject{"fizz": 3.14159}}, + want: `{"fizz":"3.14159"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Nil"), + in: structInlinePointerMapStringAny{X: nil}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/NonEmpty"), + in: structInlinePointerMapStringAny{X: addr(jsonObject{"name": "value"})}, + want: `{"name":"value"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Nested/Nil"), + in: structInlineInlinePointerMapStringAny{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt"), + in: structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }, + want: `{"one":1,"two":2,"zero":0}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Deterministic"), + opts: []Options{Deterministic(true)}, + in: structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }, + want: `{"one":1,"two":2,"zero":0}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), + opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(false)}, + in: structInlineMapStringInt{ + X: map[string]int{"\xff": 0, "\xfe": 1}, + }, + want: `{"�":1`, + wantErr: newDuplicateNameError("", []byte(`"�"`), len64(`{"�":1`)), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), + opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, + in: structInlineMapStringInt{ + X: map[string]int{"\xff": 0, "\xfe": 1}, + }, + want: `{"�":1,"�":0}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/StringifiedNumbers"), + opts: []Options{StringifyNumbers(true)}, + in: structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }, + want: `{"one":"1","two":"2","zero":"0"}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/MarshalFunc"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + // Marshalers do not affect the string key of inlined maps. + MarshalFunc(func(v string) ([]byte, error) { + return []byte(fmt.Sprintf(`"%q"`, strings.ToUpper(v))), nil + }), + MarshalFunc(func(v int) ([]byte, error) { + return []byte(fmt.Sprintf(`"%v"`, v)), nil + }), + )), + }, + in: structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }, + want: `{"one":"1","two":"2","zero":"0"}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt"), + in: structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, + }, + want: `{"one":1,"two":2,"zero":0}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/Deterministic"), + opts: []Options{Deterministic(true)}, + in: structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, + }, + want: `{"one":1,"two":2,"zero":0}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/Nil"), + in: structInlineMapNamedStringAny{X: nil}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/Empty"), + in: structInlineMapNamedStringAny{X: make(map[namedString]any)}, + want: `{}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/NonEmptyN1"), + in: structInlineMapNamedStringAny{X: map[namedString]any{"fizz": nil}}, + want: `{"fizz":null}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/NonEmptyN2"), + in: structInlineMapNamedStringAny{X: map[namedString]any{"fizz": time.Time{}, "buzz": math.Pi}}, + want: `{"buzz":3.141592653589793,"fizz":"0001-01-01T00:00:00Z"}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/NonEmptyWithOthers"), + in: structInlineMapNamedStringAny{ + A: 1, + X: map[namedString]any{"fizz": nil}, + B: 2, + }, + // NOTE: Inlined fallback fields are always serialized last. + want: `{"A":1,"B":2,"fizz":null}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/RejectInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(false)}, + in: structInlineMapNamedStringAny{X: map[namedString]any{"\xde\xad\xbe\xef": nil}}, + want: `{`, + wantErr: EM(jsonwire.ErrInvalidUTF8).withPos(`{`, "").withType(0, T[namedString]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/AllowInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: structInlineMapNamedStringAny{X: map[namedString]any{"\xde\xad\xbe\xef": nil}}, + want: `{"ޭ��":null}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/InvalidValue"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: structInlineMapNamedStringAny{X: map[namedString]any{"name": make(chan string)}}, + want: `{"name"`, + wantErr: EM(nil).withPos(`{"name":`, "/name").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MarshalFunc"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { + return []byte(fmt.Sprintf(`"%v"`, v)), nil + })), + }, + in: structInlineMapNamedStringAny{X: map[namedString]any{"fizz": 3.14159}}, + want: `{"fizz":"3.14159"}`, + }, { + name: jsontest.Name("Structs/InlinedFallback/DiscardUnknownMembers"), + opts: []Options{DiscardUnknownMembers(true)}, + in: structInlineTextValue{ + A: 1, + X: jsontext.Value(` { "fizz" : "buzz" } `), + B: 2, + }, + // NOTE: DiscardUnknownMembers has no effect since this is "inline". + want: `{"A":1,"B":2,"fizz":"buzz"}`, + }, { + name: jsontest.Name("Structs/UnknownFallback/DiscardUnknownMembers"), + opts: []Options{DiscardUnknownMembers(true)}, + in: structUnknownTextValue{ + A: 1, + X: jsontext.Value(` { "fizz" : "buzz" } `), + B: 2, + }, + want: `{"A":1,"B":2}`, + }, { + name: jsontest.Name("Structs/UnknownFallback"), + in: structUnknownTextValue{ + A: 1, + X: jsontext.Value(` { "fizz" : "buzz" } `), + B: 2, + }, + want: `{"A":1,"B":2,"fizz":"buzz"}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/Other"), + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"dupe":"","dupe":""}`), + }, + want: `{"dupe":""`, + wantErr: newDuplicateNameError("", []byte(`"dupe"`), len64(`{"dupe":""`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/Other/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"dupe": "", "dupe": ""}`), + }, + want: `{"dupe":"","dupe":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactDifferent"), + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"Aaa": "", "AaA": "", "AAa": "", "AAA": ""}`), + }, + want: `{"Aaa":"","AaA":"","AAa":"","AAA":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactConflict"), + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"Aaa": "", "Aaa": ""}`), + }, + want: `{"Aaa":""`, + wantErr: newDuplicateNameError("", []byte(`"Aaa"`), len64(`{"Aaa":""`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactConflict/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"Aaa": "", "Aaa": ""}`), + }, + want: `{"Aaa":"","Aaa":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/NoCaseConflict"), + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"Aaa": "", "AaA": "", "aaa": ""}`), + }, + want: `{"Aaa":"","AaA":""`, + wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"Aaa":"","AaA":""`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/NoCaseConflict/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + in: structNoCaseInlineTextValue{ + X: jsontext.Value(`{"Aaa": "", "AaA": "", "aaa": ""}`), + }, + want: `{"Aaa":"","AaA":"","aaa":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactDifferentWithField"), + in: structNoCaseInlineTextValue{ + AAA: "x", + AaA: "x", + X: jsontext.Value(`{"Aaa": ""}`), + }, + want: `{"AAA":"x","AaA":"x","Aaa":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/ExactConflictWithField"), + in: structNoCaseInlineTextValue{ + AAA: "x", + AaA: "x", + X: jsontext.Value(`{"AAA": ""}`), + }, + want: `{"AAA":"x","AaA":"x"`, + wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"x","AaA":"x"`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/NoCaseConflictWithField"), + in: structNoCaseInlineTextValue{ + AAA: "x", + AaA: "x", + X: jsontext.Value(`{"aaa": ""}`), + }, + want: `{"AAA":"x","AaA":"x"`, + wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"AAA":"x","AaA":"x"`)), + }, { + name: jsontest.Name("Structs/DuplicateName/MatchCaseInsensitiveDelimiter"), + in: structNoCaseInlineTextValue{ + AaA: "x", + X: jsontext.Value(`{"aa_a": ""}`), + }, + want: `{"AaA":"x"`, + wantErr: newDuplicateNameError("", []byte(`"aa_a"`), len64(`{"AaA":"x"`)), + }, { + name: jsontest.Name("Structs/DuplicateName/MatchCaseSensitiveDelimiter"), + opts: []Options{jsonflags.MatchCaseSensitiveDelimiter | 1}, + in: structNoCaseInlineTextValue{ + AaA: "x", + X: jsontext.Value(`{"aa_a": ""}`), + }, + want: `{"AaA":"x","aa_a":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), + opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, + in: structNoCaseInlineTextValue{ + AaA: "x", + X: jsontext.Value(`{"aa_a": ""}`), + }, + want: `{"AaA":"x","aa_a":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), + opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, + in: structNoCaseInlineTextValue{ + AA_b: "x", + X: jsontext.Value(`{"aa_b": ""}`), + }, + want: `{"AA_b":"x"`, + wantErr: newDuplicateNameError("", []byte(`"aa_b"`), len64(`{"AA_b":"x"`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineMapStringAny/ExactDifferent"), + in: structNoCaseInlineMapStringAny{ + X: jsonObject{"Aaa": "", "AaA": "", "AAa": "", "AAA": ""}, + }, + want: `{"AAA":"","AAa":"","AaA":"","Aaa":""}`, + canonicalize: true, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineMapStringAny/ExactDifferentWithField"), + in: structNoCaseInlineMapStringAny{ + AAA: "x", + AaA: "x", + X: jsonObject{"Aaa": ""}, + }, + want: `{"AAA":"x","AaA":"x","Aaa":""}`, + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineMapStringAny/ExactConflictWithField"), + in: structNoCaseInlineMapStringAny{ + AAA: "x", + AaA: "x", + X: jsonObject{"AAA": ""}, + }, + want: `{"AAA":"x","AaA":"x"`, + wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"x","AaA":"x"`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCaseInlineMapStringAny/NoCaseConflictWithField"), + in: structNoCaseInlineMapStringAny{ + AAA: "x", + AaA: "x", + X: jsonObject{"aaa": ""}, + }, + want: `{"AAA":"x","AaA":"x"`, + wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"AAA":"x","AaA":"x"`)), + }, { + name: jsontest.Name("Structs/Invalid/Conflicting"), + in: structConflicting{}, + want: ``, + wantErr: EM(errors.New("Go struct fields A and B conflict over JSON object name \"conflict\"")).withType(0, T[structConflicting]()), + }, { + name: jsontest.Name("Structs/Invalid/NoneExported"), + in: structNoneExported{}, + want: ``, + wantErr: EM(errNoExportedFields).withType(0, T[structNoneExported]()), + }, { + name: jsontest.Name("Structs/Invalid/MalformedTag"), + in: structMalformedTag{}, + want: ``, + wantErr: EM(errors.New("Go struct field Malformed has malformed `json` tag: invalid character '\"' at start of option (expecting Unicode letter or single quote)")).withType(0, T[structMalformedTag]()), + }, { + name: jsontest.Name("Structs/Invalid/UnexportedTag"), + in: structUnexportedTag{}, + want: ``, + wantErr: EM(errors.New("unexported Go struct field unexported cannot have non-ignored `json:\"name\"` tag")).withType(0, T[structUnexportedTag]()), + }, { + name: jsontest.Name("Structs/Invalid/ExportedEmbedded"), + in: structExportedEmbedded{"hello"}, + want: ``, + wantErr: EM(errors.New("embedded Go struct field NamedString of non-struct type must be explicitly given a JSON name")).withType(0, T[structExportedEmbedded]()), + }, { + name: jsontest.Name("Structs/Valid/ExportedEmbedded"), + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, + in: structExportedEmbedded{"hello"}, + want: `{"NamedString":"hello"}`, + }, { + name: jsontest.Name("Structs/Valid/ExportedEmbeddedTag"), + in: structExportedEmbeddedTag{"hello"}, + want: `{"name":"hello"}`, + }, { + name: jsontest.Name("Structs/Invalid/UnexportedEmbedded"), + in: structUnexportedEmbedded{}, + want: ``, + wantErr: EM(errors.New("embedded Go struct field namedString of non-struct type must be explicitly given a JSON name")).withType(0, T[structUnexportedEmbedded]()), + }, { + name: jsontest.Name("Structs/Valid/UnexportedEmbedded"), + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, + in: structUnexportedEmbedded{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/Invalid/UnexportedEmbeddedTag"), + in: structUnexportedEmbeddedTag{}, + wantErr: EM(errors.New("Go struct field namedString is not exported")).withType(0, T[structUnexportedEmbeddedTag]()), + }, { + name: jsontest.Name("Structs/Valid/UnexportedEmbeddedTag"), + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, + in: structUnexportedEmbeddedTag{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/Invalid/UnexportedEmbeddedMethodTag"), + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, + in: structUnexportedEmbeddedMethodTag{}, + want: `{}`, + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStruct/Zero"), + in: structUnexportedEmbeddedStruct{}, + want: `{"FizzBuzz":0,"Addr":""}`, + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStruct/NonZero"), + in: structUnexportedEmbeddedStruct{structOmitZeroAll{Bool: true}, 5, structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}, + want: `{"Bool":true,"FizzBuzz":5,"Addr":"192.168.0.1"}`, + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), + in: structUnexportedEmbeddedStructPointer{}, + want: `{"FizzBuzz":0}`, + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Zero"), + in: structUnexportedEmbeddedStructPointer{&structOmitZeroAll{}, 0, &structNestedAddr{}}, + want: `{"FizzBuzz":0,"Addr":""}`, + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/NonZero"), + in: structUnexportedEmbeddedStructPointer{&structOmitZeroAll{Bool: true}, 5, &structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}, + want: `{"Bool":true,"FizzBuzz":5,"Addr":"192.168.0.1"}`, + }, { + name: jsontest.Name("Structs/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: struct{}{}, + want: `{}`, + }, { + name: jsontest.Name("Slices/Interface"), + in: []any{ + false, true, + "hello", []byte("world"), + int32(-32), namedInt64(-64), + uint32(+32), namedUint64(+64), + float32(32.32), namedFloat64(64.64), + }, + want: `[false,true,"hello","d29ybGQ=",-32,-64,32,64,32.32,64.64]`, + }, { + name: jsontest.Name("Slices/Invalid/Channel"), + in: [](chan string){nil}, + want: `[`, + wantErr: EM(nil).withPos(`[`, "/0").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Slices/RecursiveSlice"), + in: recursiveSlice{ + nil, + {}, + {nil}, + {nil, {}}, + }, + want: `[[],[],[[]],[[],[]]]`, + }, { + name: jsontest.Name("Slices/CyclicSlice"), + in: func() recursiveSlice { + s := recursiveSlice{{}} + s[0] = s + return s + }(), + want: strings.Repeat(`[`, startDetectingCyclesAfter) + `[`, + wantErr: EM(internal.ErrCycle).withPos(strings.Repeat("[", startDetectingCyclesAfter+1), jsontext.Pointer(strings.Repeat("/0", startDetectingCyclesAfter+1))).withType(0, T[recursiveSlice]()), + }, { + name: jsontest.Name("Slices/NonCyclicSlice"), + in: func() []any { + v := []any{nil, nil} + v[1] = v[:1] + for i := 1000; i > 0; i-- { + v = []any{v} + } + return v + }(), + want: strings.Repeat(`[`, startDetectingCyclesAfter) + `[null,[null]]` + strings.Repeat(`]`, startDetectingCyclesAfter), + }, { + name: jsontest.Name("Slices/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: []string{"hello", "goodbye"}, + want: `["hello","goodbye"]`, + }, { + name: jsontest.Name("Arrays/Empty"), + in: [0]struct{}{}, + want: `[]`, + }, { + name: jsontest.Name("Arrays/Bool"), + in: [2]bool{false, true}, + want: `[false,true]`, + }, { + name: jsontest.Name("Arrays/String"), + in: [2]string{"hello", "goodbye"}, + want: `["hello","goodbye"]`, + }, { + name: jsontest.Name("Arrays/Bytes"), + in: [2][]byte{[]byte("hello"), []byte("goodbye")}, + want: `["aGVsbG8=","Z29vZGJ5ZQ=="]`, + }, { + name: jsontest.Name("Arrays/Int"), + in: [2]int64{math.MinInt64, math.MaxInt64}, + want: `[-9223372036854775808,9223372036854775807]`, + }, { + name: jsontest.Name("Arrays/Uint"), + in: [2]uint64{0, math.MaxUint64}, + want: `[0,18446744073709551615]`, + }, { + name: jsontest.Name("Arrays/Float"), + in: [2]float64{-math.MaxFloat64, +math.MaxFloat64}, + want: `[-1.7976931348623157e+308,1.7976931348623157e+308]`, + }, { + name: jsontest.Name("Arrays/Invalid/Channel"), + in: new([1]chan string), + want: `[`, + wantErr: EM(nil).withPos(`[`, "/0").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Arrays/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: [2]string{"hello", "goodbye"}, + want: `["hello","goodbye"]`, + }, { + name: jsontest.Name("Pointers/NilL0"), + in: (*int)(nil), + want: `null`, + }, { + name: jsontest.Name("Pointers/NilL1"), + in: new(*int), + want: `null`, + }, { + name: jsontest.Name("Pointers/Bool"), + in: addr(addr(bool(true))), + want: `true`, + }, { + name: jsontest.Name("Pointers/String"), + in: addr(addr(string("string"))), + want: `"string"`, + }, { + name: jsontest.Name("Pointers/Bytes"), + in: addr(addr([]byte("bytes"))), + want: `"Ynl0ZXM="`, + }, { + name: jsontest.Name("Pointers/Int"), + in: addr(addr(int(-100))), + want: `-100`, + }, { + name: jsontest.Name("Pointers/Uint"), + in: addr(addr(uint(100))), + want: `100`, + }, { + name: jsontest.Name("Pointers/Float"), + in: addr(addr(float64(3.14159))), + want: `3.14159`, + }, { + name: jsontest.Name("Pointers/CyclicPointer"), + in: func() *recursivePointer { + p := new(recursivePointer) + p.P = p + return p + }(), + want: strings.Repeat(`{"P":`, startDetectingCyclesAfter) + `{"P"`, + wantErr: EM(internal.ErrCycle).withPos(strings.Repeat(`{"P":`, startDetectingCyclesAfter+1), jsontext.Pointer(strings.Repeat("/P", startDetectingCyclesAfter+1))).withType(0, T[*recursivePointer]()), + }, { + name: jsontest.Name("Pointers/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: addr(addr(bool(true))), + want: `true`, + }, { + name: jsontest.Name("Interfaces/Nil/Empty"), + in: [1]any{nil}, + want: `[null]`, + }, { + name: jsontest.Name("Interfaces/Nil/NonEmpty"), + in: [1]io.Reader{nil}, + want: `[null]`, + }, { + name: jsontest.Name("Interfaces/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: [1]io.Reader{nil}, + want: `[null]`, + }, { + name: jsontest.Name("Interfaces/Any"), + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}, [8]byte{}}}, + want: `{"X":[null,false,"",0,{},[],"AAAAAAAAAAA="]}`, + }, { + name: jsontest.Name("Interfaces/Any/Named"), + in: struct{ X namedAny }{[]namedAny{nil, false, "", 0.0, map[string]namedAny{}, []namedAny{}, [8]byte{}}}, + want: `{"X":[null,false,"",0,{},[],"AAAAAAAAAAA="]}`, + }, { + name: jsontest.Name("Interfaces/Any/Stringified"), + opts: []Options{StringifyNumbers(true)}, + in: struct{ X any }{0.0}, + want: `{"X":"0"}`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/Any"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v any) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, + want: `"called"`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/Bool"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v bool) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, + want: `{"X":[null,"called","",0,{},[]]}`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/String"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v string) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, + want: `{"X":[null,false,"called",0,{},[]]}`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/Float64"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v float64) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, + want: `{"X":[null,false,"","called",{},[]]}`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/MapStringAny"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v map[string]any) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, + want: `{"X":[null,false,"",0,"called",[]]}`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/SliceAny"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v []any) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[]any{nil, false, "", 0.0, map[string]any{}, []any{}}}, + want: `{"X":"called"}`, + }, { + name: jsontest.Name("Interfaces/Any/MarshalFunc/Bytes"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v [8]byte) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: struct{ X any }{[8]byte{}}, + want: `{"X":"called"}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Nil"), + in: struct{ X any }{map[string]any(nil)}, + want: `{"X":{}}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Nil/FormatNilMapAsNull"), + opts: []Options{FormatNilMapAsNull(true)}, + in: struct{ X any }{map[string]any(nil)}, + want: `{"X":null}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Empty"), + in: struct{ X any }{map[string]any{}}, + want: `{"X":{}}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Empty/Multiline"), + opts: []Options{jsontext.Multiline(true), jsontext.WithIndent("")}, + in: struct{ X any }{map[string]any{}}, + want: "{\n\"X\": {}\n}", + }, { + name: jsontest.Name("Interfaces/Any/Maps/NonEmpty"), + in: struct{ X any }{map[string]any{"fizz": "buzz"}}, + want: `{"X":{"fizz":"buzz"}}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Deterministic"), + opts: []Options{Deterministic(true)}, + in: struct{ X any }{map[string]any{"alpha": "", "bravo": ""}}, + want: `{"X":{"alpha":"","bravo":""}}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Deterministic+AllowInvalidUTF8+RejectDuplicateNames"), + opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(false)}, + in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, + want: `{"X":{"�":""`, + wantErr: newDuplicateNameError("/X", []byte(`"�"`), len64(`{"X":{"�":"",`)), + }, { + name: jsontest.Name("Interfaces/Any/Maps/Deterministic+AllowInvalidUTF8+AllowDuplicateNames"), + opts: []Options{Deterministic(true), jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, + in: struct{ X any }{map[string]any{"\xff": "alpha", "\xfe": "bravo"}}, + want: `{"X":{"�":"bravo","�":"alpha"}}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/RejectInvalidUTF8"), + in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, + want: `{"X":{`, + wantErr: newInvalidUTF8Error(len64(`{"X":{`), "/X"), + }, { + name: jsontest.Name("Interfaces/Any/Maps/AllowInvalidUTF8+RejectDuplicateNames"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, + want: `{"X":{"�":""`, + wantErr: newDuplicateNameError("/X", []byte(`"�"`), len64(`{"X":{"�":"",`)), + }, { + name: jsontest.Name("Interfaces/Any/Maps/AllowInvalidUTF8+AllowDuplicateNames"), + opts: []Options{jsontext.AllowInvalidUTF8(true), jsontext.AllowDuplicateNames(true)}, + in: struct{ X any }{map[string]any{"\xff": "", "\xfe": ""}}, + want: `{"X":{"�":"","�":""}}`, + }, { + name: jsontest.Name("Interfaces/Any/Maps/Cyclic"), + in: func() any { + m := map[string]any{} + m[""] = m + return struct{ X any }{m} + }(), + want: `{"X"` + strings.Repeat(`:{""`, startDetectingCyclesAfter), + wantErr: EM(internal.ErrCycle).withPos(`{"X":`+strings.Repeat(`{"":`, startDetectingCyclesAfter), "/X"+jsontext.Pointer(strings.Repeat("/", startDetectingCyclesAfter))).withType(0, T[any]()), + }, { + name: jsontest.Name("Interfaces/Any/Slices/Nil"), + in: struct{ X any }{[]any(nil)}, + want: `{"X":[]}`, + }, { + name: jsontest.Name("Interfaces/Any/Slices/Nil/FormatNilSliceAsNull"), + opts: []Options{FormatNilSliceAsNull(true)}, + in: struct{ X any }{[]any(nil)}, + want: `{"X":null}`, + }, { + name: jsontest.Name("Interfaces/Any/Slices/Empty"), + in: struct{ X any }{[]any{}}, + want: `{"X":[]}`, + }, { + name: jsontest.Name("Interfaces/Any/Slices/Empty/Multiline"), + opts: []Options{jsontext.Multiline(true), jsontext.WithIndent("")}, + in: struct{ X any }{[]any{}}, + want: "{\n\"X\": []\n}", + }, { + name: jsontest.Name("Interfaces/Any/Slices/NonEmpty"), + in: struct{ X any }{[]any{"fizz", "buzz"}}, + want: `{"X":["fizz","buzz"]}`, + }, { + name: jsontest.Name("Interfaces/Any/Slices/Cyclic"), + in: func() any { + s := make([]any, 1) + s[0] = s + return struct{ X any }{s} + }(), + want: `{"X":` + strings.Repeat(`[`, startDetectingCyclesAfter), + wantErr: EM(internal.ErrCycle).withPos(`{"X":`+strings.Repeat(`[`, startDetectingCyclesAfter), "/X"+jsontext.Pointer(strings.Repeat("/0", startDetectingCyclesAfter))).withType(0, T[[]any]()), + }, { + name: jsontest.Name("Methods/NilPointer"), + in: struct{ X *allMethods }{X: (*allMethods)(nil)}, // method should not be called + want: `{"X":null}`, + }, { + // NOTE: Fixes https://github.com/dominikh/go-tools/issues/975. + name: jsontest.Name("Methods/NilInterface"), + in: struct{ X MarshalerTo }{X: (*allMethods)(nil)}, // method should not be called + want: `{"X":null}`, + }, { + name: jsontest.Name("Methods/AllMethods"), + in: struct{ X *allMethods }{X: &allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/AllMethodsExceptJSONv2"), + in: struct{ X *allMethodsExceptJSONv2 }{X: &allMethodsExceptJSONv2{allMethods: allMethods{method: "MarshalJSON", value: []byte(`"hello"`)}}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/AllMethodsExceptJSONv1"), + in: struct{ X *allMethodsExceptJSONv1 }{X: &allMethodsExceptJSONv1{allMethods: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/AllMethodsExceptText"), + in: struct{ X *allMethodsExceptText }{X: &allMethodsExceptText{allMethods: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/OnlyMethodJSONv2"), + in: struct{ X *onlyMethodJSONv2 }{X: &onlyMethodJSONv2{allMethods: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/OnlyMethodJSONv1"), + in: struct{ X *onlyMethodJSONv1 }{X: &onlyMethodJSONv1{allMethods: allMethods{method: "MarshalJSON", value: []byte(`"hello"`)}}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/OnlyMethodText"), + in: struct{ X *onlyMethodText }{X: &onlyMethodText{allMethods: allMethods{method: "MarshalText", value: []byte(`hello`)}}}, + want: `{"X":"hello"}`, + }, { + name: jsontest.Name("Methods/IP"), + in: net.IPv4(192, 168, 0, 100), + want: `"192.168.0.100"`, + }, { + name: jsontest.Name("Methods/NetIP"), + in: struct { + Addr netip.Addr + AddrPort netip.AddrPort + Prefix netip.Prefix + }{ + Addr: netip.AddrFrom4([4]byte{1, 2, 3, 4}), + AddrPort: netip.AddrPortFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 1234), + Prefix: netip.PrefixFrom(netip.AddrFrom4([4]byte{1, 2, 3, 4}), 24), + }, + want: `{"Addr":"1.2.3.4","AddrPort":"1.2.3.4:1234","Prefix":"1.2.3.4/24"}`, + }, { + // NOTE: Fixes https://go.dev/issue/46516. + name: jsontest.Name("Methods/Anonymous"), + in: struct{ X struct{ allMethods } }{X: struct{ allMethods }{allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}}}, + want: `{"X":"hello"}`, + }, { + // NOTE: Fixes https://go.dev/issue/22967. + name: jsontest.Name("Methods/Addressable"), + in: struct { + V allMethods + M map[string]allMethods + I any + }{ + V: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}, + M: map[string]allMethods{"K": {method: "MarshalJSONTo", value: []byte(`"hello"`)}}, + I: allMethods{method: "MarshalJSONTo", value: []byte(`"hello"`)}, + }, + want: `{"V":"hello","M":{"K":"hello"},"I":"hello"}`, + }, { + // NOTE: Fixes https://go.dev/issue/29732. + name: jsontest.Name("Methods/MapKey/JSONv2"), + in: map[structMethodJSONv2]string{{"k1"}: "v1", {"k2"}: "v2"}, + want: `{"k1":"v1","k2":"v2"}`, + canonicalize: true, + }, { + // NOTE: Fixes https://go.dev/issue/29732. + name: jsontest.Name("Methods/MapKey/JSONv1"), + in: map[structMethodJSONv1]string{{"k1"}: "v1", {"k2"}: "v2"}, + want: `{"k1":"v1","k2":"v2"}`, + canonicalize: true, + }, { + name: jsontest.Name("Methods/MapKey/Text"), + in: map[structMethodText]string{{"k1"}: "v1", {"k2"}: "v2"}, + want: `{"k1":"v1","k2":"v2"}`, + canonicalize: true, + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/Error"), + in: marshalJSONv2Func(func(*jsontext.Encoder) error { + return errSomeError + }), + wantErr: EM(errSomeError).withType(0, T[marshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/TooFew"), + in: marshalJSONv2Func(func(*jsontext.Encoder) error { + return nil // do nothing + }), + wantErr: EM(errNonSingularValue).withType(0, T[marshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/TooMany"), + in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { + enc.WriteToken(jsontext.Null) + enc.WriteToken(jsontext.Null) + return nil + }), + want: `nullnull`, + wantErr: EM(errNonSingularValue).withPos(`nullnull`, "").withType(0, T[marshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/SkipFunc"), + in: marshalJSONv2Func(func(enc *jsontext.Encoder) error { + return SkipFunc + }), + wantErr: EM(errors.New("marshal method cannot be skipped")).withType(0, T[marshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv1/Error"), + in: marshalJSONv1Func(func() ([]byte, error) { + return nil, errSomeError + }), + wantErr: EM(errSomeError).withType(0, T[marshalJSONv1Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv1/Syntax"), + in: marshalJSONv1Func(func() ([]byte, error) { + return []byte("invalid"), nil + }), + wantErr: EM(newInvalidCharacterError("i", "at start of value", 0, "")).withType(0, T[marshalJSONv1Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv1/SkipFunc"), + in: marshalJSONv1Func(func() ([]byte, error) { + return nil, SkipFunc + }), + wantErr: EM(errors.New("marshal method cannot be skipped")).withType(0, T[marshalJSONv1Func]()), + }, { + name: jsontest.Name("Methods/AppendText"), + in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "hello"...), nil }), + want: `"hello"`, + }, { + name: jsontest.Name("Methods/AppendText/Error"), + in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "hello"...), errSomeError }), + wantErr: EM(errSomeError).withType(0, T[appendTextFunc]()), + }, { + name: jsontest.Name("Methods/AppendText/NeedEscape"), + in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, `"`...), nil }), + want: `"\""`, + }, { + name: jsontest.Name("Methods/AppendText/RejectInvalidUTF8"), + in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "\xde\xad\xbe\xef"...), nil }), + wantErr: EM(newInvalidUTF8Error(0, "")).withType(0, T[appendTextFunc]()), + }, { + name: jsontest.Name("Methods/AppendText/AllowInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: appendTextFunc(func(b []byte) ([]byte, error) { return append(b, "\xde\xad\xbe\xef"...), nil }), + want: "\"\xde\xad\ufffd\ufffd\"", + }, { + name: jsontest.Name("Methods/Invalid/Text/Error"), + in: marshalTextFunc(func() ([]byte, error) { + return nil, errSomeError + }), + wantErr: EM(errSomeError).withType(0, T[marshalTextFunc]()), + }, { + name: jsontest.Name("Methods/Text/RejectInvalidUTF8"), + in: marshalTextFunc(func() ([]byte, error) { + return []byte("\xde\xad\xbe\xef"), nil + }), + wantErr: EM(newInvalidUTF8Error(0, "")).withType(0, T[marshalTextFunc]()), + }, { + name: jsontest.Name("Methods/Text/AllowInvalidUTF8"), + opts: []Options{jsontext.AllowInvalidUTF8(true)}, + in: marshalTextFunc(func() ([]byte, error) { + return []byte("\xde\xad\xbe\xef"), nil + }), + want: "\"\xde\xad\ufffd\ufffd\"", + }, { + name: jsontest.Name("Methods/Invalid/Text/SkipFunc"), + in: marshalTextFunc(func() ([]byte, error) { + return nil, SkipFunc + }), + wantErr: EM(wrapSkipFunc(SkipFunc, "marshal method")).withType(0, T[marshalTextFunc]()), + }, { + name: jsontest.Name("Methods/Invalid/MapKey/JSONv2/Syntax"), + in: map[any]string{ + addr(marshalJSONv2Func(func(enc *jsontext.Encoder) error { + return enc.WriteToken(jsontext.Null) + })): "invalid", + }, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[marshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/MapKey/JSONv1/Syntax"), + in: map[any]string{ + addr(marshalJSONv1Func(func() ([]byte, error) { + return []byte(`null`), nil + })): "invalid", + }, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[marshalJSONv1Func]()), + }, { + name: jsontest.Name("Functions/Bool/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Bool/Empty"), + opts: []Options{WithMarshalers(nil)}, + in: true, + want: `true`, + }, { + name: jsontest.Name("Functions/NamedBool/V1/NoMatch"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(namedBool) ([]byte, error) { + return nil, errMustNotCall + })), + }, + in: true, + want: `true`, + }, { + name: jsontest.Name("Functions/NamedBool/V1/Match"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(namedBool) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: namedBool(true), + want: `"called"`, + }, { + name: jsontest.Name("Functions/PointerBool/V1/Match"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v *bool) ([]byte, error) { + _ = *v // must be a non-nil pointer + return []byte(`"called"`), nil + })), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Bool/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return enc.WriteToken(jsontext.String("called")) + })), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/NamedBool/V2/NoMatch"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v namedBool) error { + return errMustNotCall + })), + }, + in: true, + want: `true`, + }, { + name: jsontest.Name("Functions/NamedBool/V2/Match"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v namedBool) error { + return enc.WriteToken(jsontext.String("called")) + })), + }, + in: namedBool(true), + want: `"called"`, + }, { + name: jsontest.Name("Functions/PointerBool/V2/Match"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { + _ = *v // must be a non-nil pointer + return enc.WriteToken(jsontext.String("called")) + })), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Bool/Empty1/NoMatch"), + opts: []Options{ + WithMarshalers(new(Marshalers)), + }, + in: true, + want: `true`, + }, { + name: jsontest.Name("Functions/Bool/Empty2/NoMatch"), + opts: []Options{ + WithMarshalers(JoinMarshalers()), + }, + in: true, + want: `true`, + }, { + name: jsontest.Name("Functions/Bool/V1/DirectError"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { + return nil, errSomeError + })), + }, + in: true, + wantErr: EM(errSomeError).withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V1/SkipError"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { + return nil, SkipFunc + })), + }, + in: true, + wantErr: EM(wrapSkipFunc(SkipFunc, "marshal function of type func(T) ([]byte, error)")).withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V1/InvalidValue"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(bool) ([]byte, error) { + return []byte("invalid"), nil + })), + }, + in: true, + wantErr: EM(newInvalidCharacterError("i", "at start of value", 0, "")).withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V2/DirectError"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return errSomeError + })), + }, + in: true, + wantErr: EM(errSomeError).withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V2/TooFew"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return nil + })), + }, + in: true, + wantErr: EM(errNonSingularValue).withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V2/TooMany"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + enc.WriteValue([]byte(`"hello"`)) + enc.WriteValue([]byte(`"world"`)) + return nil + })), + }, + in: true, + want: `"hello""world"`, + wantErr: EM(errNonSingularValue).withPos(`"hello""world"`, "").withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V2/Skipped"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return SkipFunc + })), + }, + in: true, + want: `true`, + }, { + name: jsontest.Name("Functions/Bool/V2/ProcessBeforeSkip"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + enc.WriteValue([]byte(`"hello"`)) + return SkipFunc + })), + }, + in: true, + want: `"hello"`, + wantErr: EM(errSkipMutation).withPos(`"hello"`, "").withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Bool/V2/WrappedSkipError"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return fmt.Errorf("wrap: %w", SkipFunc) + })), + }, + in: true, + wantErr: EM(fmt.Errorf("wrap: %w", SkipFunc)).withType(0, T[bool]()), + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{"called":"world"}`, + }, { + name: jsontest.Name("Functions/Map/Key/PointerNoCaseString/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v *nocaseString) ([]byte, error) { + _ = *v // must be a non-nil pointer + return []byte(`"called"`), nil + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{"called":"world"}`, + }, { + name: jsontest.Name("Functions/Map/Key/TextMarshaler/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v encoding.TextMarshaler) ([]byte, error) { + _ = *v.(*nocaseString) // must be a non-nil *nocaseString + return []byte(`"called"`), nil + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{"called":"world"}`, + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V1/InvalidValue"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { + return []byte(`null`), nil + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V2/InvalidKind"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { + return []byte(`null`), nil + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), + }, { + name: jsontest.Name("Functions/Map/Key/String/V1/DuplicateName"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v string) ([]byte, error) { + return []byte(`"name"`), nil + })), + }, + in: map[string]string{"name1": "value", "name2": "value"}, + want: `{"name":"name"`, + wantErr: EM(newDuplicateNameError("", []byte(`"name"`), len64(`{"name":"name",`))). + withPos(`{"name":"name",`, "").withType(0, T[string]()), + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{"called":"world"}`, + }, { + name: jsontest.Name("Functions/Map/Key/PointerNoCaseString/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *nocaseString) error { + _ = *v // must be a non-nil pointer + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{"called":"world"}`, + }, { + name: jsontest.Name("Functions/Map/Key/TextMarshaler/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v encoding.TextMarshaler) error { + _ = *v.(*nocaseString) // must be a non-nil *nocaseString + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{"called":"world"}`, + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V2/InvalidToken"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { + return enc.WriteToken(jsontext.Null) + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V2/InvalidValue"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { + return enc.WriteValue([]byte(`null`)) + })), + }, + in: map[nocaseString]string{"hello": "world"}, + want: `{`, + wantErr: EM(newNonStringNameError(len64(`{`), "")).withPos(`{`, "").withType(0, T[nocaseString]()), + }, { + name: jsontest.Name("Functions/Map/Value/NoCaseString/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v nocaseString) ([]byte, error) { + return []byte(`"called"`), nil + })), + }, + in: map[string]nocaseString{"hello": "world"}, + want: `{"hello":"called"}`, + }, { + name: jsontest.Name("Functions/Map/Value/PointerNoCaseString/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v *nocaseString) ([]byte, error) { + _ = *v // must be a non-nil pointer + return []byte(`"called"`), nil + })), + }, + in: map[string]nocaseString{"hello": "world"}, + want: `{"hello":"called"}`, + }, { + name: jsontest.Name("Functions/Map/Value/TextMarshaler/V1"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v encoding.TextMarshaler) ([]byte, error) { + _ = *v.(*nocaseString) // must be a non-nil *nocaseString + return []byte(`"called"`), nil + })), + }, + in: map[string]nocaseString{"hello": "world"}, + want: `{"hello":"called"}`, + }, { + name: jsontest.Name("Functions/Map/Value/NoCaseString/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v nocaseString) error { + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: map[string]nocaseString{"hello": "world"}, + want: `{"hello":"called"}`, + }, { + name: jsontest.Name("Functions/Map/Value/PointerNoCaseString/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *nocaseString) error { + _ = *v // must be a non-nil pointer + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: map[string]nocaseString{"hello": "world"}, + want: `{"hello":"called"}`, + }, { + name: jsontest.Name("Functions/Map/Value/TextMarshaler/V2"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v encoding.TextMarshaler) error { + _ = *v.(*nocaseString) // must be a non-nil *nocaseString + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: map[string]nocaseString{"hello": "world"}, + want: `{"hello":"called"}`, + }, { + name: jsontest.Name("Funtions/Struct/Fields"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalFunc(func(v bool) ([]byte, error) { + return []byte(`"called1"`), nil + }), + MarshalFunc(func(v *string) ([]byte, error) { + return []byte(`"called2"`), nil + }), + MarshalToFunc(func(enc *jsontext.Encoder, v []byte) error { + return enc.WriteValue([]byte(`"called3"`)) + }), + MarshalToFunc(func(enc *jsontext.Encoder, v *int64) error { + return enc.WriteValue([]byte(`"called4"`)) + }), + )), + }, + in: structScalars{}, + want: `{"Bool":"called1","String":"called2","Bytes":"called3","Int":"called4","Uint":0,"Float":0}`, + }, { + name: jsontest.Name("Functions/Struct/OmitEmpty"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalFunc(func(v bool) ([]byte, error) { + return []byte(`null`), nil + }), + MarshalFunc(func(v string) ([]byte, error) { + return []byte(`"called1"`), nil + }), + MarshalFunc(func(v *stringMarshalNonEmpty) ([]byte, error) { + return []byte(`""`), nil + }), + MarshalToFunc(func(enc *jsontext.Encoder, v bytesMarshalNonEmpty) error { + return enc.WriteValue([]byte(`{}`)) + }), + MarshalToFunc(func(enc *jsontext.Encoder, v *float64) error { + return enc.WriteValue([]byte(`[]`)) + }), + MarshalFunc(func(v mapMarshalNonEmpty) ([]byte, error) { + return []byte(`"called2"`), nil + }), + MarshalFunc(func(v []string) ([]byte, error) { + return []byte(`"called3"`), nil + }), + MarshalToFunc(func(enc *jsontext.Encoder, v *sliceMarshalNonEmpty) error { + return enc.WriteValue([]byte(`"called4"`)) + }), + )), + }, + in: structOmitEmptyAll{}, + want: `{"String":"called1","MapNonEmpty":"called2","Slice":"called3","SliceNonEmpty":"called4"}`, + }, { + name: jsontest.Name("Functions/Struct/OmitZero"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalFunc(func(v bool) ([]byte, error) { + panic("should not be called") + }), + MarshalFunc(func(v *string) ([]byte, error) { + panic("should not be called") + }), + MarshalToFunc(func(enc *jsontext.Encoder, v []byte) error { + panic("should not be called") + }), + MarshalToFunc(func(enc *jsontext.Encoder, v *int64) error { + panic("should not be called") + }), + )), + }, + in: structOmitZeroAll{}, + want: `{}`, + }, { + name: jsontest.Name("Functions/Struct/Inlined"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalFunc(func(v structInlinedL1) ([]byte, error) { + panic("should not be called") + }), + MarshalToFunc(func(enc *jsontext.Encoder, v *StructEmbed2) error { + panic("should not be called") + }), + )), + }, + in: structInlined{}, + want: `{"D":""}`, + }, { + name: jsontest.Name("Functions/Slice/Elem"), + opts: []Options{ + WithMarshalers(MarshalFunc(func(v bool) ([]byte, error) { + return []byte(`"` + strconv.FormatBool(v) + `"`), nil + })), + }, + in: []bool{true, false}, + want: `["true","false"]`, + }, { + name: jsontest.Name("Functions/Array/Elem"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { + return enc.WriteValue([]byte(`"` + strconv.FormatBool(*v) + `"`)) + })), + }, + in: [2]bool{true, false}, + want: `["true","false"]`, + }, { + name: jsontest.Name("Functions/Pointer/Nil"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { + panic("should not be called") + })), + }, + in: struct{ X *bool }{nil}, + want: `{"X":null}`, + }, { + name: jsontest.Name("Functions/Pointer/NonNil"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *bool) error { + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: struct{ X *bool }{addr(false)}, + want: `{"X":"called"}`, + }, { + name: jsontest.Name("Functions/Interface/Nil"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v fmt.Stringer) error { + panic("should not be called") + })), + }, + in: struct{ X fmt.Stringer }{nil}, + want: `{"X":null}`, + }, { + name: jsontest.Name("Functions/Interface/NonNil/MatchInterface"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v fmt.Stringer) error { + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: struct{ X fmt.Stringer }{valueStringer{}}, + want: `{"X":"called"}`, + }, { + name: jsontest.Name("Functions/Interface/NonNil/MatchConcrete"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v valueStringer) error { + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: struct{ X fmt.Stringer }{valueStringer{}}, + want: `{"X":"called"}`, + }, { + name: jsontest.Name("Functions/Interface/NonNil/MatchPointer"), + opts: []Options{ + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, v *valueStringer) error { + return enc.WriteValue([]byte(`"called"`)) + })), + }, + in: struct{ X fmt.Stringer }{valueStringer{}}, + want: `{"X":"called"}`, + }, { + name: jsontest.Name("Functions/Interface/Any"), + in: []any{ + nil, // nil + valueStringer{}, // T + (*valueStringer)(nil), // *T + addr(valueStringer{}), // *T + (**valueStringer)(nil), // **T + addr((*valueStringer)(nil)), // **T + addr(addr(valueStringer{})), // **T + pointerStringer{}, // T + (*pointerStringer)(nil), // *T + addr(pointerStringer{}), // *T + (**pointerStringer)(nil), // **T + addr((*pointerStringer)(nil)), // **T + addr(addr(pointerStringer{})), // **T + "LAST", + }, + want: `[null,{},null,{},null,null,{},{},null,{},null,null,{},"LAST"]`, + opts: []Options{ + WithMarshalers(func() *Marshalers { + type P struct { + D int + N int64 + } + type PV struct { + P P + V any + } + + var lastChecks []func() error + checkLast := func() error { + for _, fn := range lastChecks { + if err := fn(); err != nil { + return err + } + } + return SkipFunc + } + makeValueChecker := func(name string, want []PV) func(e *jsontext.Encoder, v any) error { + checkNext := func(e *jsontext.Encoder, v any) error { + xe := export.Encoder(e) + p := P{len(xe.Tokens.Stack), xe.Tokens.Last.Length()} + rv := reflect.ValueOf(v) + pv := PV{p, v} + switch { + case len(want) == 0: + return fmt.Errorf("%s: %v: got more values than expected", name, p) + case !rv.IsValid() || rv.Kind() != reflect.Pointer || rv.IsNil(): + return fmt.Errorf("%s: %v: got %#v, want non-nil pointer type", name, p, v) + case !reflect.DeepEqual(pv, want[0]): + return fmt.Errorf("%s:\n\tgot %#v\n\twant %#v", name, pv, want[0]) + default: + want = want[1:] + return SkipFunc + } + } + lastChecks = append(lastChecks, func() error { + if len(want) > 0 { + return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) + } + return nil + }) + return checkNext + } + makePositionChecker := func(name string, want []P) func(e *jsontext.Encoder, v any) error { + checkNext := func(e *jsontext.Encoder, v any) error { + xe := export.Encoder(e) + p := P{len(xe.Tokens.Stack), xe.Tokens.Last.Length()} + switch { + case len(want) == 0: + return fmt.Errorf("%s: %v: got more values than wanted", name, p) + case p != want[0]: + return fmt.Errorf("%s: got %v, want %v", name, p, want[0]) + default: + want = want[1:] + return SkipFunc + } + } + lastChecks = append(lastChecks, func() error { + if len(want) > 0 { + return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) + } + return nil + }) + return checkNext + } + + wantAny := []PV{ + {P{0, 0}, addr([]any{ + nil, + valueStringer{}, + (*valueStringer)(nil), + addr(valueStringer{}), + (**valueStringer)(nil), + addr((*valueStringer)(nil)), + addr(addr(valueStringer{})), + pointerStringer{}, + (*pointerStringer)(nil), + addr(pointerStringer{}), + (**pointerStringer)(nil), + addr((*pointerStringer)(nil)), + addr(addr(pointerStringer{})), + "LAST", + })}, + {P{1, 0}, addr(any(nil))}, + {P{1, 1}, addr(any(valueStringer{}))}, + {P{1, 1}, addr(valueStringer{})}, + {P{1, 2}, addr(any((*valueStringer)(nil)))}, + {P{1, 2}, addr((*valueStringer)(nil))}, + {P{1, 3}, addr(any(addr(valueStringer{})))}, + {P{1, 3}, addr(addr(valueStringer{}))}, + {P{1, 3}, addr(valueStringer{})}, + {P{1, 4}, addr(any((**valueStringer)(nil)))}, + {P{1, 4}, addr((**valueStringer)(nil))}, + {P{1, 5}, addr(any(addr((*valueStringer)(nil))))}, + {P{1, 5}, addr(addr((*valueStringer)(nil)))}, + {P{1, 5}, addr((*valueStringer)(nil))}, + {P{1, 6}, addr(any(addr(addr(valueStringer{}))))}, + {P{1, 6}, addr(addr(addr(valueStringer{})))}, + {P{1, 6}, addr(addr(valueStringer{}))}, + {P{1, 6}, addr(valueStringer{})}, + {P{1, 7}, addr(any(pointerStringer{}))}, + {P{1, 7}, addr(pointerStringer{})}, + {P{1, 8}, addr(any((*pointerStringer)(nil)))}, + {P{1, 8}, addr((*pointerStringer)(nil))}, + {P{1, 9}, addr(any(addr(pointerStringer{})))}, + {P{1, 9}, addr(addr(pointerStringer{}))}, + {P{1, 9}, addr(pointerStringer{})}, + {P{1, 10}, addr(any((**pointerStringer)(nil)))}, + {P{1, 10}, addr((**pointerStringer)(nil))}, + {P{1, 11}, addr(any(addr((*pointerStringer)(nil))))}, + {P{1, 11}, addr(addr((*pointerStringer)(nil)))}, + {P{1, 11}, addr((*pointerStringer)(nil))}, + {P{1, 12}, addr(any(addr(addr(pointerStringer{}))))}, + {P{1, 12}, addr(addr(addr(pointerStringer{})))}, + {P{1, 12}, addr(addr(pointerStringer{}))}, + {P{1, 12}, addr(pointerStringer{})}, + {P{1, 13}, addr(any("LAST"))}, + {P{1, 13}, addr("LAST")}, + } + checkAny := makeValueChecker("any", wantAny) + anyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v any) error { + return checkAny(enc, v) + }) + + var wantPointerAny []PV + for _, v := range wantAny { + if _, ok := v.V.(*any); ok { + wantPointerAny = append(wantPointerAny, v) + } + } + checkPointerAny := makeValueChecker("*any", wantPointerAny) + pointerAnyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *any) error { + return checkPointerAny(enc, v) + }) + + checkNamedAny := makeValueChecker("namedAny", wantAny) + namedAnyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v namedAny) error { + return checkNamedAny(enc, v) + }) + + checkPointerNamedAny := makeValueChecker("*namedAny", nil) + pointerNamedAnyMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *namedAny) error { + return checkPointerNamedAny(enc, v) + }) + + type stringer = fmt.Stringer + var wantStringer []PV + for _, v := range wantAny { + if _, ok := v.V.(stringer); ok { + wantStringer = append(wantStringer, v) + } + } + checkStringer := makeValueChecker("stringer", wantStringer) + stringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v stringer) error { + return checkStringer(enc, v) + }) + + checkPointerStringer := makeValueChecker("*stringer", nil) + pointerStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *stringer) error { + return checkPointerStringer(enc, v) + }) + + wantValueStringer := []P{{1, 1}, {1, 3}, {1, 6}} + checkValueValueStringer := makePositionChecker("valueStringer", wantValueStringer) + valueValueStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v valueStringer) error { + return checkValueValueStringer(enc, v) + }) + + checkPointerValueStringer := makePositionChecker("*valueStringer", wantValueStringer) + pointerValueStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *valueStringer) error { + return checkPointerValueStringer(enc, v) + }) + + wantPointerStringer := []P{{1, 7}, {1, 9}, {1, 12}} + checkValuePointerStringer := makePositionChecker("pointerStringer", wantPointerStringer) + valuePointerStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v pointerStringer) error { + return checkValuePointerStringer(enc, v) + }) + + checkPointerPointerStringer := makePositionChecker("*pointerStringer", wantPointerStringer) + pointerPointerStringerMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v *pointerStringer) error { + return checkPointerPointerStringer(enc, v) + }) + + lastMarshaler := MarshalToFunc(func(enc *jsontext.Encoder, v string) error { + return checkLast() + }) + + return JoinMarshalers( + anyMarshaler, + pointerAnyMarshaler, + namedAnyMarshaler, + pointerNamedAnyMarshaler, // never called + stringerMarshaler, + pointerStringerMarshaler, // never called + valueValueStringerMarshaler, + pointerValueStringerMarshaler, + valuePointerStringerMarshaler, + pointerPointerStringerMarshaler, + lastMarshaler, + ) + }()), + }, + }, { + name: jsontest.Name("Functions/Precedence/V1First"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalFunc(func(bool) ([]byte, error) { + return []byte(`"called"`), nil + }), + MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + panic("should not be called") + }), + )), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Precedence/V2First"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return enc.WriteToken(jsontext.String("called")) + }), + MarshalFunc(func(bool) ([]byte, error) { + panic("should not be called") + }), + )), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Precedence/V2Skipped"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalToFunc(func(enc *jsontext.Encoder, v bool) error { + return SkipFunc + }), + MarshalFunc(func(bool) ([]byte, error) { + return []byte(`"called"`), nil + }), + )), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Precedence/NestedFirst"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + JoinMarshalers( + MarshalFunc(func(bool) ([]byte, error) { + return []byte(`"called"`), nil + }), + ), + MarshalFunc(func(bool) ([]byte, error) { + panic("should not be called") + }), + )), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Functions/Precedence/NestedLast"), + opts: []Options{ + WithMarshalers(JoinMarshalers( + MarshalFunc(func(bool) ([]byte, error) { + return []byte(`"called"`), nil + }), + JoinMarshalers( + MarshalFunc(func(bool) ([]byte, error) { + panic("should not be called") + }), + ), + )), + }, + in: true, + want: `"called"`, + }, { + name: jsontest.Name("Duration/Zero"), + in: struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{0, 0}, + want: `{"D1":"0s","D2":0}`, + }, { + name: jsontest.Name("Duration/Positive"), + in: struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{ + 123456789123456789, + 123456789123456789, + }, + want: `{"D1":"34293h33m9.123456789s","D2":123456789123456789}`, + }, { + name: jsontest.Name("Duration/Negative"), + in: struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{ + -123456789123456789, + -123456789123456789, + }, + want: `{"D1":"-34293h33m9.123456789s","D2":-123456789123456789}`, + }, { + name: jsontest.Name("Duration/Nanos/String"), + in: struct { + D1 time.Duration `json:",string,format:nano"` + D2 time.Duration `json:",string,format:nano"` + D3 time.Duration `json:",string,format:nano"` + }{ + math.MinInt64, + 0, + math.MaxInt64, + }, + want: `{"D1":"-9223372036854775808","D2":"0","D3":"9223372036854775807"}`, + }, { + name: jsontest.Name("Duration/Format/Invalid"), + in: struct { + D time.Duration `json:",format:invalid"` + }{}, + want: `{"D"`, + wantErr: EM(errInvalidFormatFlag).withPos(`{"D":`, "/D").withType(0, T[time.Duration]()), + }, { + name: jsontest.Name("Duration/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: time.Duration(0), + want: `"0s"`, + }, { + name: jsontest.Name("Duration/Format"), + opts: []Options{jsontext.Multiline(true)}, + in: structDurationFormat{ + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + }, + want: `{ + "D1": "12h34m56.078090012s", + "D2": "12h34m56.078090012s", + "D3": 45296.078090012, + "D4": "45296.078090012", + "D5": 45296078.090012, + "D6": "45296078.090012", + "D7": 45296078090.012, + "D8": "45296078090.012", + "D9": 45296078090012, + "D10": "45296078090012" +}`, + }, { + name: jsontest.Name("Duration/Format/Legacy"), + opts: []Options{jsonflags.FormatTimeWithLegacySemantics | 1}, + in: structDurationFormat{ + D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + }, + want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0"}`, + }, { + name: jsontest.Name("Duration/MapKey"), + in: map[time.Duration]string{time.Second: ""}, + want: `{"1s":""}`, + }, { + name: jsontest.Name("Duration/MapKey/Legacy"), + opts: []Options{jsonflags.FormatTimeWithLegacySemantics | 1}, + in: map[time.Duration]string{time.Second: ""}, + want: `{"1000000000":""}`, + }, { + name: jsontest.Name("Time/Zero"), + in: struct { + T1 time.Time + T2 time.Time `json:",format:RFC822"` + T3 time.Time `json:",format:'2006-01-02'"` + T4 time.Time `json:",omitzero"` + T5 time.Time `json:",omitempty"` + }{ + time.Time{}, + time.Time{}, + time.Time{}, + // This is zero according to time.Time.IsZero, + // but non-zero according to reflect.Value.IsZero. + time.Date(1, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC", 0)), + time.Time{}, + }, + want: `{"T1":"0001-01-01T00:00:00Z","T2":"01 Jan 01 00:00 UTC","T3":"0001-01-01","T5":"0001-01-01T00:00:00Z"}`, + }, { + name: jsontest.Name("Time/Format"), + opts: []Options{jsontext.Multiline(true)}, + in: structTimeFormat{ + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + time.Date(1234, 1, 2, 3, 4, 5, 6, time.UTC), + }, + want: `{ + "T1": "1234-01-02T03:04:05.000000006Z", + "T2": "Mon Jan 2 03:04:05 1234", + "T3": "Mon Jan 2 03:04:05 UTC 1234", + "T4": "Mon Jan 02 03:04:05 +0000 1234", + "T5": "02 Jan 34 03:04 UTC", + "T6": "02 Jan 34 03:04 +0000", + "T7": "Monday, 02-Jan-34 03:04:05 UTC", + "T8": "Mon, 02 Jan 1234 03:04:05 UTC", + "T9": "Mon, 02 Jan 1234 03:04:05 +0000", + "T10": "1234-01-02T03:04:05Z", + "T11": "1234-01-02T03:04:05.000000006Z", + "T12": "3:04AM", + "T13": "Jan 2 03:04:05", + "T14": "Jan 2 03:04:05.000", + "T15": "Jan 2 03:04:05.000000", + "T16": "Jan 2 03:04:05.000000006", + "T17": "1234-01-02 03:04:05", + "T18": "1234-01-02", + "T19": "03:04:05", + "T20": "1234-01-02", + "T21": "\"weird\"1234", + "T22": -23225777754.999999994, + "T23": "-23225777754.999999994", + "T24": -23225777754999.999994, + "T25": "-23225777754999.999994", + "T26": -23225777754999999.994, + "T27": "-23225777754999999.994", + "T28": -23225777754999999994, + "T29": "-23225777754999999994" +}`, + }, { + name: jsontest.Name("Time/Format/Invalid"), + in: struct { + T time.Time `json:",format:UndefinedConstant"` + }{}, + want: `{"T"`, + wantErr: EM(errors.New(`invalid format flag "UndefinedConstant"`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/Format/YearOverflow"), + in: struct { + T1 time.Time + T2 time.Time + }{ + time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), + time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: `{"T1":"9999-12-31T23:59:59Z","T2"`, + wantErr: EM(errors.New(`year outside of range [0,9999]`)).withPos(`{"T1":"9999-12-31T23:59:59Z","T2":`, "/T2").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/Format/YearUnderflow"), + in: struct { + T1 time.Time + T2 time.Time + }{ + time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC), + time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), + }, + want: `{"T1":"0000-01-01T00:00:00Z","T2"`, + wantErr: EM(errors.New(`year outside of range [0,9999]`)).withPos(`{"T1":"0000-01-01T00:00:00Z","T2":`, "/T2").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/Format/YearUnderflow"), + in: struct{ T time.Time }{time.Date(-998, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second)}, + want: `{"T"`, + wantErr: EM(errors.New(`year outside of range [0,9999]`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/Format/ZoneExact"), + in: struct{ T time.Time }{time.Date(2020, 1, 1, 0, 0, 0, 0, time.FixedZone("", 23*60*60+59*60))}, + want: `{"T":"2020-01-01T00:00:00+23:59"}`, + }, { + name: jsontest.Name("Time/Format/ZoneHourOverflow"), + in: struct{ T time.Time }{time.Date(2020, 1, 1, 0, 0, 0, 0, time.FixedZone("", 24*60*60))}, + want: `{"T"`, + wantErr: EM(errors.New(`timezone hour outside of range [0,23]`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/Format/ZoneHourOverflow"), + in: struct{ T time.Time }{time.Date(2020, 1, 1, 0, 0, 0, 0, time.FixedZone("", 123*60*60))}, + want: `{"T"`, + wantErr: EM(errors.New(`timezone hour outside of range [0,23]`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + in: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + want: `"2000-01-01T00:00:00Z"`, + }} + + for _, tt := range tests { + t.Run(tt.name.Name, func(t *testing.T) { + var got []byte + var gotErr error + if tt.useWriter { + bb := new(struct{ bytes.Buffer }) // avoid optimizations with bytes.Buffer + gotErr = MarshalWrite(bb, tt.in, tt.opts...) + got = bb.Bytes() + } else { + got, gotErr = Marshal(tt.in, tt.opts...) + } + if tt.canonicalize { + (*jsontext.Value)(&got).Canonicalize() + } + if string(got) != tt.want { + t.Errorf("%s: Marshal output mismatch:\ngot %s\nwant %s", tt.name.Where, got, tt.want) + } + if !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("%s: Marshal error mismatch:\ngot %v\nwant %v", tt.name.Where, gotErr, tt.wantErr) + } + }) + } +} + +func TestUnmarshal(t *testing.T) { + tests := []struct { + name jsontest.CaseName + opts []Options + inBuf string + inVal any + want any + wantErr error + }{{ + name: jsontest.Name("Nil"), + inBuf: `null`, + wantErr: EU(internal.ErrNonNilReference), + }, { + name: jsontest.Name("NilPointer"), + inBuf: `null`, + inVal: (*string)(nil), + want: (*string)(nil), + wantErr: EU(internal.ErrNonNilReference).withType(0, T[*string]()), + }, { + name: jsontest.Name("NonPointer"), + inBuf: `null`, + inVal: "unchanged", + want: "unchanged", + wantErr: EU(internal.ErrNonNilReference).withType(0, T[string]()), + }, { + name: jsontest.Name("Bools/TrailingJunk"), + inBuf: `falsetrue`, + inVal: addr(true), + want: addr(false), + wantErr: newInvalidCharacterError("t", "after top-level value", len64(`false`), ""), + }, { + name: jsontest.Name("Bools/Null"), + inBuf: `null`, + inVal: addr(true), + want: addr(false), + }, { + name: jsontest.Name("Bools"), + inBuf: `[null,false,true]`, + inVal: new([]bool), + want: addr([]bool{false, false, true}), + }, { + name: jsontest.Name("Bools/Named"), + inBuf: `[null,false,true]`, + inVal: new([]namedBool), + want: addr([]namedBool{false, false, true}), + }, { + name: jsontest.Name("Bools/Invalid/StringifiedFalse"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"false"`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('"', boolType), + }, { + name: jsontest.Name("Bools/Invalid/StringifiedTrue"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"true"`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('"', boolType), + }, { + name: jsontest.Name("Bools/StringifiedBool/True"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `"true"`, + inVal: addr(false), + want: addr(true), + }, { + name: jsontest.Name("Bools/StringifiedBool/False"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `"false"`, + inVal: addr(true), + want: addr(false), + }, { + name: jsontest.Name("Bools/StringifiedBool/InvalidWhitespace"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `"false "`, + inVal: addr(true), + want: addr(true), + wantErr: EU(strconv.ErrSyntax).withVal(`"false "`).withType('"', boolType), + }, { + name: jsontest.Name("Bools/StringifiedBool/InvalidBool"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `false`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('f', boolType), + }, { + name: jsontest.Name("Bools/Invalid/Number"), + inBuf: `0`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('0', boolType), + }, { + name: jsontest.Name("Bools/Invalid/String"), + inBuf: `""`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('"', boolType), + }, { + name: jsontest.Name("Bools/Invalid/Object"), + inBuf: `{}`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('{', boolType), + }, { + name: jsontest.Name("Bools/Invalid/Array"), + inBuf: `[]`, + inVal: addr(true), + want: addr(true), + wantErr: EU(nil).withType('[', boolType), + }, { + name: jsontest.Name("Bools/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `false`, + inVal: addr(true), + want: addr(false), + }, { + name: jsontest.Name("Strings/Null"), + inBuf: `null`, + inVal: addr("something"), + want: addr(""), + }, { + name: jsontest.Name("Strings"), + inBuf: `[null,"","hello","世界"]`, + inVal: new([]string), + want: addr([]string{"", "", "hello", "世界"}), + }, { + name: jsontest.Name("Strings/Escaped"), + inBuf: `[null,"","\u0068\u0065\u006c\u006c\u006f","\u4e16\u754c"]`, + inVal: new([]string), + want: addr([]string{"", "", "hello", "世界"}), + }, { + name: jsontest.Name("Strings/Named"), + inBuf: `[null,"","hello","世界"]`, + inVal: new([]namedString), + want: addr([]namedString{"", "", "hello", "世界"}), + }, { + name: jsontest.Name("Strings/Invalid/False"), + inBuf: `false`, + inVal: addr("nochange"), + want: addr("nochange"), + wantErr: EU(nil).withType('f', stringType), + }, { + name: jsontest.Name("Strings/Invalid/True"), + inBuf: `true`, + inVal: addr("nochange"), + want: addr("nochange"), + wantErr: EU(nil).withType('t', stringType), + }, { + name: jsontest.Name("Strings/Invalid/Object"), + inBuf: `{}`, + inVal: addr("nochange"), + want: addr("nochange"), + wantErr: EU(nil).withType('{', stringType), + }, { + name: jsontest.Name("Strings/Invalid/Array"), + inBuf: `[]`, + inVal: addr("nochange"), + want: addr("nochange"), + wantErr: EU(nil).withType('[', stringType), + }, { + name: jsontest.Name("Strings/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `"hello"`, + inVal: addr("goodbye"), + want: addr("hello"), + }, { + name: jsontest.Name("Strings/StringifiedString"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `"\"foo\""`, + inVal: new(string), + want: addr("foo"), + }, { + name: jsontest.Name("Strings/StringifiedString/InvalidWhitespace"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `"\"foo\" "`, + inVal: new(string), + want: new(string), + wantErr: EU(newInvalidCharacterError(" ", "after string value", 0, "")).withType('"', stringType), + }, { + name: jsontest.Name("Strings/StringifiedString/InvalidString"), + opts: []Options{jsonflags.StringifyBoolsAndStrings | 1}, + inBuf: `""`, + inVal: new(string), + want: new(string), + wantErr: EU(&jsontext.SyntacticError{Err: io.ErrUnexpectedEOF}).withType('"', stringType), + }, { + name: jsontest.Name("Bytes/Null"), + inBuf: `null`, + inVal: addr([]byte("something")), + want: addr([]byte(nil)), + }, { + name: jsontest.Name("Bytes"), + inBuf: `[null,"","AQ==","AQI=","AQID"]`, + inVal: new([][]byte), + want: addr([][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), + }, { + name: jsontest.Name("Bytes/Large"), + inBuf: `"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wZWQgb3ZlciB0aGUgbGF6eSBkb2cgYW5kIGF0ZSB0aGUgaG9tZXdvcmsgdGhhdCBJIHNwZW50IHNvIG11Y2ggdGltZSBvbi4="`, + inVal: new([]byte), + want: addr([]byte("the quick brown fox jumped over the lazy dog and ate the homework that I spent so much time on.")), + }, { + name: jsontest.Name("Bytes/Reuse"), + inBuf: `"AQID"`, + inVal: addr([]byte("changed")), + want: addr([]byte{1, 2, 3}), + }, { + name: jsontest.Name("Bytes/Escaped"), + inBuf: `[null,"","\u0041\u0051\u003d\u003d","\u0041\u0051\u0049\u003d","\u0041\u0051\u0049\u0044"]`, + inVal: new([][]byte), + want: addr([][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), + }, { + name: jsontest.Name("Bytes/Named"), + inBuf: `[null,"","AQ==","AQI=","AQID"]`, + inVal: new([]namedBytes), + want: addr([]namedBytes{nil, {}, {1}, {1, 2}, {1, 2, 3}}), + }, { + name: jsontest.Name("Bytes/NotStringified"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `[null,"","AQ==","AQI=","AQID"]`, + inVal: new([][]byte), + want: addr([][]byte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), + }, { + // NOTE: []namedByte is not assignable to []byte, + // so the following should be treated as a slice of uints. + name: jsontest.Name("Bytes/Invariant"), + inBuf: `[null,[],[1],[1,2],[1,2,3]]`, + inVal: new([][]namedByte), + want: addr([][]namedByte{nil, {}, {1}, {1, 2}, {1, 2, 3}}), + }, { + // NOTE: This differs in behavior from v1, + // but keeps the representation of slices and arrays more consistent. + name: jsontest.Name("Bytes/ByteArray"), + inBuf: `"aGVsbG8="`, + inVal: new([5]byte), + want: addr([5]byte{'h', 'e', 'l', 'l', 'o'}), + }, { + name: jsontest.Name("Bytes/ByteArray0/Valid"), + inBuf: `""`, + inVal: new([0]byte), + want: addr([0]byte{}), + }, { + name: jsontest.Name("Bytes/ByteArray0/Invalid"), + inBuf: `"A"`, + inVal: new([0]byte), + want: addr([0]byte{}), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 0), []byte("A")) + return err + }()).withType('"', T[[0]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray0/Overflow"), + inBuf: `"AA=="`, + inVal: new([0]byte), + want: addr([0]byte{}), + wantErr: EU(errors.New("decoded length of 1 mismatches array length of 0")).withType('"', T[[0]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray1/Valid"), + inBuf: `"AQ=="`, + inVal: new([1]byte), + want: addr([1]byte{1}), + }, { + name: jsontest.Name("Bytes/ByteArray1/Invalid"), + inBuf: `"$$=="`, + inVal: new([1]byte), + want: addr([1]byte{}), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 1), []byte("$$==")) + return err + }()).withType('"', T[[1]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray1/Underflow"), + inBuf: `""`, + inVal: new([1]byte), + want: addr([1]byte{}), + wantErr: EU(errors.New("decoded length of 0 mismatches array length of 1")).withType('"', T[[1]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray1/Overflow"), + inBuf: `"AQI="`, + inVal: new([1]byte), + want: addr([1]byte{1}), + wantErr: EU(errors.New("decoded length of 2 mismatches array length of 1")).withType('"', T[[1]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray2/Valid"), + inBuf: `"AQI="`, + inVal: new([2]byte), + want: addr([2]byte{1, 2}), + }, { + name: jsontest.Name("Bytes/ByteArray2/Invalid"), + inBuf: `"$$$="`, + inVal: new([2]byte), + want: addr([2]byte{}), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 2), []byte("$$$=")) + return err + }()).withType('"', T[[2]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray2/Underflow"), + inBuf: `"AQ=="`, + inVal: new([2]byte), + want: addr([2]byte{1, 0}), + wantErr: EU(errors.New("decoded length of 1 mismatches array length of 2")).withType('"', T[[2]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray2/Underflow/Allowed"), + opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, + inBuf: `"AQ=="`, + inVal: new([2]byte), + want: addr([2]byte{1, 0}), + }, { + name: jsontest.Name("Bytes/ByteArray2/Overflow"), + inBuf: `"AQID"`, + inVal: new([2]byte), + want: addr([2]byte{1, 2}), + wantErr: EU(errors.New("decoded length of 3 mismatches array length of 2")).withType('"', T[[2]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray2/Overflow/Allowed"), + opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, + inBuf: `"AQID"`, + inVal: new([2]byte), + want: addr([2]byte{1, 2}), + }, { + name: jsontest.Name("Bytes/ByteArray3/Valid"), + inBuf: `"AQID"`, + inVal: new([3]byte), + want: addr([3]byte{1, 2, 3}), + }, { + name: jsontest.Name("Bytes/ByteArray3/Invalid"), + inBuf: `"$$$$"`, + inVal: new([3]byte), + want: addr([3]byte{}), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 3), []byte("$$$$")) + return err + }()).withType('"', T[[3]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray3/Underflow"), + inBuf: `"AQI="`, + inVal: addr([3]byte{0xff, 0xff, 0xff}), + want: addr([3]byte{1, 2, 0}), + wantErr: EU(errors.New("decoded length of 2 mismatches array length of 3")).withType('"', T[[3]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray3/Overflow"), + inBuf: `"AQIDAQ=="`, + inVal: new([3]byte), + want: addr([3]byte{1, 2, 3}), + wantErr: EU(errors.New("decoded length of 4 mismatches array length of 3")).withType('"', T[[3]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray4/Valid"), + inBuf: `"AQIDBA=="`, + inVal: new([4]byte), + want: addr([4]byte{1, 2, 3, 4}), + }, { + name: jsontest.Name("Bytes/ByteArray4/Invalid"), + inBuf: `"$$$$$$=="`, + inVal: new([4]byte), + want: addr([4]byte{}), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 4), []byte("$$$$$$==")) + return err + }()).withType('"', T[[4]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray4/Underflow"), + inBuf: `"AQID"`, + inVal: new([4]byte), + want: addr([4]byte{1, 2, 3, 0}), + wantErr: EU(errors.New("decoded length of 3 mismatches array length of 4")).withType('"', T[[4]byte]()), + }, { + name: jsontest.Name("Bytes/ByteArray4/Overflow"), + inBuf: `"AQIDBAU="`, + inVal: new([4]byte), + want: addr([4]byte{1, 2, 3, 4}), + wantErr: EU(errors.New("decoded length of 5 mismatches array length of 4")).withType('"', T[[4]byte]()), + }, { + // NOTE: []namedByte is not assignable to []byte, + // so the following should be treated as a array of uints. + name: jsontest.Name("Bytes/NamedByteArray"), + inBuf: `[104,101,108,108,111]`, + inVal: new([5]namedByte), + want: addr([5]namedByte{'h', 'e', 'l', 'l', 'o'}), + }, { + name: jsontest.Name("Bytes/Valid/Denormalized"), + inBuf: `"AR=="`, + inVal: new([]byte), + want: addr([]byte{1}), + }, { + name: jsontest.Name("Bytes/Invalid/Unpadded1"), + inBuf: `"AQ="`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 0), []byte("AQ=")) + return err + }()).withType('"', bytesType), + }, { + name: jsontest.Name("Bytes/Invalid/Unpadded2"), + inBuf: `"AQ"`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 0), []byte("AQ")) + return err + }()).withType('"', bytesType), + }, { + name: jsontest.Name("Bytes/Invalid/Character"), + inBuf: `"@@@@"`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 3), []byte("@@@@")) + return err + }()).withType('"', bytesType), + }, { + name: jsontest.Name("Bytes/Invalid/Bool"), + inBuf: `true`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(nil).withType('t', bytesType), + }, { + name: jsontest.Name("Bytes/Invalid/Number"), + inBuf: `0`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(nil).withType('0', bytesType), + }, { + name: jsontest.Name("Bytes/Invalid/Object"), + inBuf: `{}`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(nil).withType('{', bytesType), + }, { + name: jsontest.Name("Bytes/Invalid/Array"), + inBuf: `[]`, + inVal: addr([]byte("nochange")), + want: addr([]byte("nochange")), + wantErr: EU(nil).withType('[', bytesType), + }, { + name: jsontest.Name("Bytes/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `"aGVsbG8="`, + inVal: new([]byte), + want: addr([]byte("hello")), + }, { + name: jsontest.Name("Ints/Null"), + inBuf: `null`, + inVal: addr(int(1)), + want: addr(int(0)), + }, { + name: jsontest.Name("Ints/Int"), + inBuf: `1`, + inVal: addr(int(0)), + want: addr(int(1)), + }, { + name: jsontest.Name("Ints/Int8/MinOverflow"), + inBuf: `-129`, + inVal: addr(int8(-1)), + want: addr(int8(-1)), + wantErr: EU(strconv.ErrRange).withVal(`-129`).withType('0', T[int8]()), + }, { + name: jsontest.Name("Ints/Int8/Min"), + inBuf: `-128`, + inVal: addr(int8(0)), + want: addr(int8(-128)), + }, { + name: jsontest.Name("Ints/Int8/Max"), + inBuf: `127`, + inVal: addr(int8(0)), + want: addr(int8(127)), + }, { + name: jsontest.Name("Ints/Int8/MaxOverflow"), + inBuf: `128`, + inVal: addr(int8(-1)), + want: addr(int8(-1)), + wantErr: EU(strconv.ErrRange).withVal(`128`).withType('0', T[int8]()), + }, { + name: jsontest.Name("Ints/Int16/MinOverflow"), + inBuf: `-32769`, + inVal: addr(int16(-1)), + want: addr(int16(-1)), + wantErr: EU(strconv.ErrRange).withVal(`-32769`).withType('0', T[int16]()), + }, { + name: jsontest.Name("Ints/Int16/Min"), + inBuf: `-32768`, + inVal: addr(int16(0)), + want: addr(int16(-32768)), + }, { + name: jsontest.Name("Ints/Int16/Max"), + inBuf: `32767`, + inVal: addr(int16(0)), + want: addr(int16(32767)), + }, { + name: jsontest.Name("Ints/Int16/MaxOverflow"), + inBuf: `32768`, + inVal: addr(int16(-1)), + want: addr(int16(-1)), + wantErr: EU(strconv.ErrRange).withVal(`32768`).withType('0', T[int16]()), + }, { + name: jsontest.Name("Ints/Int32/MinOverflow"), + inBuf: `-2147483649`, + inVal: addr(int32(-1)), + want: addr(int32(-1)), + wantErr: EU(strconv.ErrRange).withVal(`-2147483649`).withType('0', T[int32]()), + }, { + name: jsontest.Name("Ints/Int32/Min"), + inBuf: `-2147483648`, + inVal: addr(int32(0)), + want: addr(int32(-2147483648)), + }, { + name: jsontest.Name("Ints/Int32/Max"), + inBuf: `2147483647`, + inVal: addr(int32(0)), + want: addr(int32(2147483647)), + }, { + name: jsontest.Name("Ints/Int32/MaxOverflow"), + inBuf: `2147483648`, + inVal: addr(int32(-1)), + want: addr(int32(-1)), + wantErr: EU(strconv.ErrRange).withVal(`2147483648`).withType('0', T[int32]()), + }, { + name: jsontest.Name("Ints/Int64/MinOverflow"), + inBuf: `-9223372036854775809`, + inVal: addr(int64(-1)), + want: addr(int64(-1)), + wantErr: EU(strconv.ErrRange).withVal(`-9223372036854775809`).withType('0', T[int64]()), + }, { + name: jsontest.Name("Ints/Int64/Min"), + inBuf: `-9223372036854775808`, + inVal: addr(int64(0)), + want: addr(int64(-9223372036854775808)), + }, { + name: jsontest.Name("Ints/Int64/Max"), + inBuf: `9223372036854775807`, + inVal: addr(int64(0)), + want: addr(int64(9223372036854775807)), + }, { + name: jsontest.Name("Ints/Int64/MaxOverflow"), + inBuf: `9223372036854775808`, + inVal: addr(int64(-1)), + want: addr(int64(-1)), + wantErr: EU(strconv.ErrRange).withVal(`9223372036854775808`).withType('0', T[int64]()), + }, { + name: jsontest.Name("Ints/Named"), + inBuf: `-6464`, + inVal: addr(namedInt64(0)), + want: addr(namedInt64(-6464)), + }, { + name: jsontest.Name("Ints/Stringified"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"-6464"`, + inVal: new(int), + want: addr(int(-6464)), + }, { + name: jsontest.Name("Ints/Stringified/Invalid"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `-6464`, + inVal: new(int), + want: new(int), + wantErr: EU(nil).withType('0', T[int]()), + }, { + name: jsontest.Name("Ints/Stringified/LeadingZero"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"00"`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"00"`).withType('"', T[int]()), + }, { + name: jsontest.Name("Ints/Escaped"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"\u002d\u0036\u0034\u0036\u0034"`, + inVal: new(int), + want: addr(int(-6464)), + }, { + name: jsontest.Name("Ints/Valid/NegativeZero"), + inBuf: `-0`, + inVal: addr(int(1)), + want: addr(int(0)), + }, { + name: jsontest.Name("Ints/Invalid/Fraction"), + inBuf: `1.0`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`1.0`).withType('0', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/Exponent"), + inBuf: `1e0`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`1e0`).withType('0', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/StringifiedFraction"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"1.0"`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"1.0"`).withType('"', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/StringifiedExponent"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"1e0"`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"1e0"`).withType('"', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/Overflow"), + inBuf: `100000000000000000000000000000`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrRange).withVal(`100000000000000000000000000000`).withType('0', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/OverflowSyntax"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"100000000000000000000000000000x"`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"100000000000000000000000000000x"`).withType('"', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/Whitespace"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"0 "`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"0 "`).withType('"', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/Bool"), + inBuf: `true`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(nil).withType('t', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/String"), + inBuf: `"0"`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(nil).withType('"', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/Object"), + inBuf: `{}`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(nil).withType('{', T[int]()), + }, { + name: jsontest.Name("Ints/Invalid/Array"), + inBuf: `[]`, + inVal: addr(int(-1)), + want: addr(int(-1)), + wantErr: EU(nil).withType('[', T[int]()), + }, { + name: jsontest.Name("Ints/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `1`, + inVal: addr(int(0)), + want: addr(int(1)), + }, { + name: jsontest.Name("Uints/Null"), + inBuf: `null`, + inVal: addr(uint(1)), + want: addr(uint(0)), + }, { + name: jsontest.Name("Uints/Uint"), + inBuf: `1`, + inVal: addr(uint(0)), + want: addr(uint(1)), + }, { + name: jsontest.Name("Uints/Uint8/Min"), + inBuf: `0`, + inVal: addr(uint8(1)), + want: addr(uint8(0)), + }, { + name: jsontest.Name("Uints/Uint8/Max"), + inBuf: `255`, + inVal: addr(uint8(0)), + want: addr(uint8(255)), + }, { + name: jsontest.Name("Uints/Uint8/MaxOverflow"), + inBuf: `256`, + inVal: addr(uint8(1)), + want: addr(uint8(1)), + wantErr: EU(strconv.ErrRange).withVal(`256`).withType('0', T[uint8]()), + }, { + name: jsontest.Name("Uints/Uint16/Min"), + inBuf: `0`, + inVal: addr(uint16(1)), + want: addr(uint16(0)), + }, { + name: jsontest.Name("Uints/Uint16/Max"), + inBuf: `65535`, + inVal: addr(uint16(0)), + want: addr(uint16(65535)), + }, { + name: jsontest.Name("Uints/Uint16/MaxOverflow"), + inBuf: `65536`, + inVal: addr(uint16(1)), + want: addr(uint16(1)), + wantErr: EU(strconv.ErrRange).withVal(`65536`).withType('0', T[uint16]()), + }, { + name: jsontest.Name("Uints/Uint32/Min"), + inBuf: `0`, + inVal: addr(uint32(1)), + want: addr(uint32(0)), + }, { + name: jsontest.Name("Uints/Uint32/Max"), + inBuf: `4294967295`, + inVal: addr(uint32(0)), + want: addr(uint32(4294967295)), + }, { + name: jsontest.Name("Uints/Uint32/MaxOverflow"), + inBuf: `4294967296`, + inVal: addr(uint32(1)), + want: addr(uint32(1)), + wantErr: EU(strconv.ErrRange).withVal(`4294967296`).withType('0', T[uint32]()), + }, { + name: jsontest.Name("Uints/Uint64/Min"), + inBuf: `0`, + inVal: addr(uint64(1)), + want: addr(uint64(0)), + }, { + name: jsontest.Name("Uints/Uint64/Max"), + inBuf: `18446744073709551615`, + inVal: addr(uint64(0)), + want: addr(uint64(18446744073709551615)), + }, { + name: jsontest.Name("Uints/Uint64/MaxOverflow"), + inBuf: `18446744073709551616`, + inVal: addr(uint64(1)), + want: addr(uint64(1)), + wantErr: EU(strconv.ErrRange).withVal(`18446744073709551616`).withType('0', T[uint64]()), + }, { + name: jsontest.Name("Uints/Uintptr"), + inBuf: `1`, + inVal: addr(uintptr(0)), + want: addr(uintptr(1)), + }, { + name: jsontest.Name("Uints/Named"), + inBuf: `6464`, + inVal: addr(namedUint64(0)), + want: addr(namedUint64(6464)), + }, { + name: jsontest.Name("Uints/Stringified"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"6464"`, + inVal: new(uint), + want: addr(uint(6464)), + }, { + name: jsontest.Name("Uints/Stringified/Invalid"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `6464`, + inVal: new(uint), + want: new(uint), + wantErr: EU(nil).withType('0', T[uint]()), + }, { + name: jsontest.Name("Uints/Stringified/LeadingZero"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"00"`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"00"`).withType('"', T[uint]()), + }, { + name: jsontest.Name("Uints/Escaped"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"\u0036\u0034\u0036\u0034"`, + inVal: new(uint), + want: addr(uint(6464)), + }, { + name: jsontest.Name("Uints/Invalid/NegativeOne"), + inBuf: `-1`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(strconv.ErrSyntax).withVal(`-1`).withType('0', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/NegativeZero"), + inBuf: `-0`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(strconv.ErrSyntax).withVal(`-0`).withType('0', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Fraction"), + inBuf: `1.0`, + inVal: addr(uint(10)), + want: addr(uint(10)), + wantErr: EU(strconv.ErrSyntax).withVal(`1.0`).withType('0', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Exponent"), + inBuf: `1e0`, + inVal: addr(uint(10)), + want: addr(uint(10)), + wantErr: EU(strconv.ErrSyntax).withVal(`1e0`).withType('0', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/StringifiedFraction"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"1.0"`, + inVal: addr(uint(10)), + want: addr(uint(10)), + wantErr: EU(strconv.ErrSyntax).withVal(`"1.0"`).withType('"', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/StringifiedExponent"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"1e0"`, + inVal: addr(uint(10)), + want: addr(uint(10)), + wantErr: EU(strconv.ErrSyntax).withVal(`"1e0"`).withType('"', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Overflow"), + inBuf: `100000000000000000000000000000`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(strconv.ErrRange).withVal(`100000000000000000000000000000`).withType('0', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/OverflowSyntax"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"100000000000000000000000000000x"`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"100000000000000000000000000000x"`).withType('"', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Whitespace"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"0 "`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(strconv.ErrSyntax).withVal(`"0 "`).withType('"', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Bool"), + inBuf: `true`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(nil).withType('t', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/String"), + inBuf: `"0"`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(nil).withType('"', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Object"), + inBuf: `{}`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(nil).withType('{', T[uint]()), + }, { + name: jsontest.Name("Uints/Invalid/Array"), + inBuf: `[]`, + inVal: addr(uint(1)), + want: addr(uint(1)), + wantErr: EU(nil).withType('[', T[uint]()), + }, { + name: jsontest.Name("Uints/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `1`, + inVal: addr(uint(0)), + want: addr(uint(1)), + }, { + name: jsontest.Name("Floats/Null"), + inBuf: `null`, + inVal: addr(float64(64.64)), + want: addr(float64(0)), + }, { + name: jsontest.Name("Floats/Float32/Pi"), + inBuf: `3.14159265358979323846264338327950288419716939937510582097494459`, + inVal: addr(float32(32.32)), + want: addr(float32(math.Pi)), + }, { + name: jsontest.Name("Floats/Float32/Underflow"), + inBuf: `1e-1000`, + inVal: addr(float32(32.32)), + want: addr(float32(0)), + }, { + name: jsontest.Name("Floats/Float32/Overflow"), + inBuf: `-1e1000`, + inVal: addr(float32(32.32)), + want: addr(float32(-math.MaxFloat32)), + wantErr: EU(strconv.ErrRange).withVal(`-1e1000`).withType('0', T[float32]()), + }, { + name: jsontest.Name("Floats/Float64/Pi"), + inBuf: `3.14159265358979323846264338327950288419716939937510582097494459`, + inVal: addr(float64(64.64)), + want: addr(float64(math.Pi)), + }, { + name: jsontest.Name("Floats/Float64/Underflow"), + inBuf: `1e-1000`, + inVal: addr(float64(64.64)), + want: addr(float64(0)), + }, { + name: jsontest.Name("Floats/Float64/Overflow"), + inBuf: `-1e1000`, + inVal: addr(float64(64.64)), + want: addr(float64(-math.MaxFloat64)), + wantErr: EU(strconv.ErrRange).withVal(`-1e1000`).withType('0', T[float64]()), + }, { + name: jsontest.Name("Floats/Any/Overflow"), + inBuf: `1e1000`, + inVal: new(any), + want: addr(any(float64(math.MaxFloat64))), + wantErr: EU(strconv.ErrRange).withVal(`1e1000`).withType('0', T[float64]()), + }, { + name: jsontest.Name("Floats/Named"), + inBuf: `64.64`, + inVal: addr(namedFloat64(0)), + want: addr(namedFloat64(64.64)), + }, { + name: jsontest.Name("Floats/Stringified"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"64.64"`, + inVal: new(float64), + want: addr(float64(64.64)), + }, { + name: jsontest.Name("Floats/Stringified/Invalid"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `64.64`, + inVal: new(float64), + want: new(float64), + wantErr: EU(nil).withType('0', T[float64]()), + }, { + name: jsontest.Name("Floats/Escaped"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"\u0036\u0034\u002e\u0036\u0034"`, + inVal: new(float64), + want: addr(float64(64.64)), + }, { + name: jsontest.Name("Floats/Invalid/NaN"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"NaN"`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(strconv.ErrSyntax).withVal(`"NaN"`).withType('"', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/Infinity"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"Infinity"`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(strconv.ErrSyntax).withVal(`"Infinity"`).withType('"', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/Whitespace"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"1 "`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(strconv.ErrSyntax).withVal(`"1 "`).withType('"', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/GoSyntax"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `"1p-2"`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(strconv.ErrSyntax).withVal(`"1p-2"`).withType('"', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/Bool"), + inBuf: `true`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(nil).withType('t', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/String"), + inBuf: `"0"`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(nil).withType('"', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/Object"), + inBuf: `{}`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(nil).withType('{', float64Type), + }, { + name: jsontest.Name("Floats/Invalid/Array"), + inBuf: `[]`, + inVal: addr(float64(64.64)), + want: addr(float64(64.64)), + wantErr: EU(nil).withType('[', float64Type), + }, { + name: jsontest.Name("Floats/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `1`, + inVal: addr(float64(0)), + want: addr(float64(1)), + }, { + name: jsontest.Name("Maps/Null"), + inBuf: `null`, + inVal: addr(map[string]string{"key": "value"}), + want: new(map[string]string), + }, { + name: jsontest.Name("Maps/InvalidKey/Bool"), + inBuf: `{"true":"false"}`, + inVal: new(map[bool]bool), + want: addr(make(map[bool]bool)), + wantErr: EU(nil).withPos(`{`, "/true").withType('"', boolType), + }, { + name: jsontest.Name("Maps/InvalidKey/NamedBool"), + inBuf: `{"true":"false"}`, + inVal: new(map[namedBool]bool), + want: addr(make(map[namedBool]bool)), + wantErr: EU(nil).withPos(`{`, "/true").withType('"', T[namedBool]()), + }, { + name: jsontest.Name("Maps/InvalidKey/Array"), + inBuf: `{"key":"value"}`, + inVal: new(map[[1]string]string), + want: addr(make(map[[1]string]string)), + wantErr: EU(nil).withPos(`{`, "/key").withType('"', T[[1]string]()), + }, { + name: jsontest.Name("Maps/InvalidKey/Channel"), + inBuf: `{"key":"value"}`, + inVal: new(map[chan string]string), + want: addr(make(map[chan string]string)), + wantErr: EU(nil).withPos(`{`, "").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Maps/ValidKey/Int"), + inBuf: `{"0":0,"-1":1,"2":2,"-3":3}`, + inVal: new(map[int]int), + want: addr(map[int]int{0: 0, -1: 1, 2: 2, -3: 3}), + }, { + name: jsontest.Name("Maps/ValidKey/NamedInt"), + inBuf: `{"0":0,"-1":1,"2":2,"-3":3}`, + inVal: new(map[namedInt64]int), + want: addr(map[namedInt64]int{0: 0, -1: 1, 2: 2, -3: 3}), + }, { + name: jsontest.Name("Maps/ValidKey/Uint"), + inBuf: `{"0":0,"1":1,"2":2,"3":3}`, + inVal: new(map[uint]uint), + want: addr(map[uint]uint{0: 0, 1: 1, 2: 2, 3: 3}), + }, { + name: jsontest.Name("Maps/ValidKey/NamedUint"), + inBuf: `{"0":0,"1":1,"2":2,"3":3}`, + inVal: new(map[namedUint64]uint), + want: addr(map[namedUint64]uint{0: 0, 1: 1, 2: 2, 3: 3}), + }, { + name: jsontest.Name("Maps/ValidKey/Float"), + inBuf: `{"1.234":1.234,"12.34":12.34,"123.4":123.4}`, + inVal: new(map[float64]float64), + want: addr(map[float64]float64{1.234: 1.234, 12.34: 12.34, 123.4: 123.4}), + }, { + name: jsontest.Name("Maps/DuplicateName/Int"), + inBuf: `{"0":1,"-0":-1}`, + inVal: new(map[int]int), + want: addr(map[int]int{0: 1}), + wantErr: newDuplicateNameError("", []byte(`"-0"`), len64(`{"0":1,`)), + }, { + name: jsontest.Name("Maps/DuplicateName/Int/MergeWithLegacySemantics"), + opts: []Options{jsonflags.MergeWithLegacySemantics | 1}, + inBuf: `{"0":1,"-0":-1}`, + inVal: new(map[int]int), + want: addr(map[int]int{0: 1}), + wantErr: newDuplicateNameError("", []byte(`"-0"`), len64(`{"0":1,`)), + }, { + name: jsontest.Name("Maps/DuplicateName/Int/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"0":1,"-0":-1}`, + inVal: new(map[int]int), + want: addr(map[int]int{0: -1}), // latter takes precedence + }, { + name: jsontest.Name("Maps/DuplicateName/Int/OverwriteExisting"), + inBuf: `{"-0":-1}`, + inVal: addr(map[int]int{0: 1}), + want: addr(map[int]int{0: -1}), + }, { + name: jsontest.Name("Maps/DuplicateName/Float"), + inBuf: `{"1.0":"1.0","1":"1","1e0":"1e0"}`, + inVal: new(map[float64]string), + want: addr(map[float64]string{1: "1.0"}), + wantErr: newDuplicateNameError("", []byte(`"1"`), len64(`{"1.0":"1.0",`)), + }, { + name: jsontest.Name("Maps/DuplicateName/Float/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"1.0":"1.0","1":"1","1e0":"1e0"}`, + inVal: new(map[float64]string), + want: addr(map[float64]string{1: "1e0"}), // latter takes precedence + }, { + name: jsontest.Name("Maps/DuplicateName/Float/OverwriteExisting"), + inBuf: `{"1.0":"1.0"}`, + inVal: addr(map[float64]string{1: "1"}), + want: addr(map[float64]string{1: "1.0"}), + }, { + name: jsontest.Name("Maps/DuplicateName/NoCaseString"), + inBuf: `{"hello":"hello","HELLO":"HELLO"}`, + inVal: new(map[nocaseString]string), + want: addr(map[nocaseString]string{"hello": "hello"}), + wantErr: newDuplicateNameError("", []byte(`"HELLO"`), len64(`{"hello":"hello",`)), + }, { + name: jsontest.Name("Maps/DuplicateName/NoCaseString/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"hello":"hello","HELLO":"HELLO"}`, + inVal: new(map[nocaseString]string), + want: addr(map[nocaseString]string{"hello": "HELLO"}), // latter takes precedence + }, { + name: jsontest.Name("Maps/DuplicateName/NoCaseString/OverwriteExisting"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"HELLO":"HELLO"}`, + inVal: addr(map[nocaseString]string{"hello": "hello"}), + want: addr(map[nocaseString]string{"hello": "HELLO"}), + }, { + name: jsontest.Name("Maps/ValidKey/Interface"), + inBuf: `{"false":"false","true":"true","string":"string","0":"0","[]":"[]","{}":"{}"}`, + inVal: new(map[any]string), + want: addr(map[any]string{ + "false": "false", + "true": "true", + "string": "string", + "0": "0", + "[]": "[]", + "{}": "{}", + }), + }, { + name: jsontest.Name("Maps/InvalidValue/Channel"), + inBuf: `{"key":"value"}`, + inVal: new(map[string]chan string), + want: addr(map[string]chan string{ + "key": nil, + }), + wantErr: EU(nil).withPos(`{"key":`, "/key").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Maps/RecursiveMap"), + inBuf: `{"buzz":{},"fizz":{"bar":{},"foo":{}}}`, + inVal: new(recursiveMap), + want: addr(recursiveMap{ + "fizz": { + "foo": {}, + "bar": {}, + }, + "buzz": {}, + }), + }, { + // NOTE: The semantics differs from v1, + // where existing map entries were not merged into. + // See https://go.dev/issue/31924. + name: jsontest.Name("Maps/Merge"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"k1":{"k2":"v2"},"k2":{"k1":"v1"},"k2":{"k2":"v2"}}`, + inVal: addr(map[string]map[string]string{ + "k1": {"k1": "v1"}, + }), + want: addr(map[string]map[string]string{ + "k1": {"k1": "v1", "k2": "v2"}, + "k2": {"k1": "v1", "k2": "v2"}, + }), + }, { + name: jsontest.Name("Maps/Invalid/Bool"), + inBuf: `true`, + inVal: addr(map[string]string{"key": "value"}), + want: addr(map[string]string{"key": "value"}), + wantErr: EU(nil).withType('t', T[map[string]string]()), + }, { + name: jsontest.Name("Maps/Invalid/String"), + inBuf: `""`, + inVal: addr(map[string]string{"key": "value"}), + want: addr(map[string]string{"key": "value"}), + wantErr: EU(nil).withType('"', T[map[string]string]()), + }, { + name: jsontest.Name("Maps/Invalid/Number"), + inBuf: `0`, + inVal: addr(map[string]string{"key": "value"}), + want: addr(map[string]string{"key": "value"}), + wantErr: EU(nil).withType('0', T[map[string]string]()), + }, { + name: jsontest.Name("Maps/Invalid/Array"), + inBuf: `[]`, + inVal: addr(map[string]string{"key": "value"}), + want: addr(map[string]string{"key": "value"}), + wantErr: EU(nil).withType('[', T[map[string]string]()), + }, { + name: jsontest.Name("Maps/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `{"hello":"goodbye"}`, + inVal: addr(map[string]string{}), + want: addr(map[string]string{"hello": "goodbye"}), + }, { + name: jsontest.Name("Structs/Null"), + inBuf: `null`, + inVal: addr(structAll{String: "something"}), + want: addr(structAll{}), + }, { + name: jsontest.Name("Structs/Empty"), + inBuf: `{}`, + inVal: addr(structAll{ + String: "hello", + Map: map[string]string{}, + Slice: []string{}, + }), + want: addr(structAll{ + String: "hello", + Map: map[string]string{}, + Slice: []string{}, + }), + }, { + name: jsontest.Name("Structs/Normal"), + inBuf: `{ + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64, + "Uint": 64, + "Float": 3.14159, + "Map": {"key": "value"}, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64, + "Uint": 64, + "Float": 3.14159 + }, + "StructMaps": { + "MapBool": {"": true}, + "MapString": {"": "hello"}, + "MapBytes": {"": "AQID"}, + "MapInt": {"": -64}, + "MapUint": {"": 64}, + "MapFloat": {"": 3.14159} + }, + "StructSlices": { + "SliceBool": [true], + "SliceString": ["hello"], + "SliceBytes": ["AQID"], + "SliceInt": [-64], + "SliceUint": [64], + "SliceFloat": [3.14159] + }, + "Slice": ["fizz","buzz"], + "Array": ["goodbye"], + "Pointer": {}, + "Interface": null +}`, + inVal: new(structAll), + want: addr(structAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, + MapUint: map[string]uint64{"": +64}, + MapFloat: map[string]float64{"": 3.14159}, + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, + SliceUint: []uint64{+64}, + SliceFloat: []float64{3.14159}, + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structAll), + }), + }, { + name: jsontest.Name("Structs/Merge"), + inBuf: `{ + "Bool": false, + "String": "goodbye", + "Int": -64, + "Float": 3.14159, + "Map": {"k2": "v2"}, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64 + }, + "StructMaps": { + "MapBool": {"": true}, + "MapString": {"": "hello"}, + "MapBytes": {"": "AQID"}, + "MapInt": {"": -64}, + "MapUint": {"": 64}, + "MapFloat": {"": 3.14159} + }, + "StructSlices": { + "SliceString": ["hello"], + "SliceBytes": ["AQID"], + "SliceInt": [-64], + "SliceUint": [64] + }, + "Slice": ["fizz","buzz"], + "Array": ["goodbye"], + "Pointer": {}, + "Interface": {"k2":"v2"} +}`, + inVal: addr(structAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Uint: +64, + Float: math.NaN(), + Map: map[string]string{"k1": "v1"}, + StructScalars: structScalars{ + String: "hello", + Bytes: make([]byte, 2, 4), + Uint: +64, + Float: 3.14159, + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": false}, + MapBytes: map[string][]byte{"": {}}, + MapInt: map[string]int64{"": 123}, + MapFloat: map[string]float64{"": math.Inf(+1)}, + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceBytes: [][]byte{nil, nil}, + SliceInt: []int64{-123}, + SliceUint: []uint64{+123}, + SliceFloat: []float64{3.14159}, + }, + Slice: []string{"buzz", "fizz", "gizz"}, + Array: [1]string{"hello"}, + Pointer: new(structAll), + Interface: map[string]string{"k1": "v1"}, + }), + want: addr(structAll{ + Bool: false, + String: "goodbye", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + Map: map[string]string{"k1": "v1", "k2": "v2"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, + MapUint: map[string]uint64{"": +64}, + MapFloat: map[string]float64{"": 3.14159}, + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, + SliceUint: []uint64{+64}, + SliceFloat: []float64{3.14159}, + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structAll), + Interface: map[string]string{"k1": "v1", "k2": "v2"}, + }), + }, { + name: jsontest.Name("Structs/Stringified/Normal"), + inBuf: `{ + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159", + "Map": {"key": "value"}, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159" + }, + "StructMaps": { + "MapBool": {"": true}, + "MapString": {"": "hello"}, + "MapBytes": {"": "AQID"}, + "MapInt": {"": "-64"}, + "MapUint": {"": "64"}, + "MapFloat": {"": "3.14159"} + }, + "StructSlices": { + "SliceBool": [true], + "SliceString": ["hello"], + "SliceBytes": ["AQID"], + "SliceInt": ["-64"], + "SliceUint": ["64"], + "SliceFloat": ["3.14159"] + }, + "Slice": ["fizz","buzz"], + "Array": ["goodbye"], + "Pointer": {}, + "Interface": null +}`, + inVal: new(structStringifiedAll), + want: addr(structStringifiedAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, // may be stringified + Uint: +64, // may be stringified + Float: 3.14159, // may be stringified + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, // may be stringified + Uint: +64, // may be stringified + Float: 3.14159, // may be stringified + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, // may be stringified + MapUint: map[string]uint64{"": +64}, // may be stringified + MapFloat: map[string]float64{"": 3.14159}, // may be stringified + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, // may be stringified + SliceUint: []uint64{+64}, // may be stringified + SliceFloat: []float64{3.14159}, // may be stringified + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structStringifiedAll), // may be stringified + }), + }, { + name: jsontest.Name("Structs/Stringified/String"), + inBuf: `{ + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159", + "Map": {"key": "value"}, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159" + }, + "StructMaps": { + "MapBool": {"": true}, + "MapString": {"": "hello"}, + "MapBytes": {"": "AQID"}, + "MapInt": {"": "-64"}, + "MapUint": {"": "64"}, + "MapFloat": {"": "3.14159"} + }, + "StructSlices": { + "SliceBool": [true], + "SliceString": ["hello"], + "SliceBytes": ["AQID"], + "SliceInt": ["-64"], + "SliceUint": ["64"], + "SliceFloat": ["3.14159"] + }, + "Slice": ["fizz","buzz"], + "Array": ["goodbye"], + "Pointer": {}, + "Interface": null +}`, + inVal: new(structStringifiedAll), + want: addr(structStringifiedAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, // may be stringified + Uint: +64, // may be stringified + Float: 3.14159, // may be stringified + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, // may be stringified + Uint: +64, // may be stringified + Float: 3.14159, // may be stringified + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, // may be stringified + MapUint: map[string]uint64{"": +64}, // may be stringified + MapFloat: map[string]float64{"": 3.14159}, // may be stringified + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, // may be stringified + SliceUint: []uint64{+64}, // may be stringified + SliceFloat: []float64{3.14159}, // may be stringified + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + Pointer: new(structStringifiedAll), // may be stringified + }), + }, { + name: jsontest.Name("Structs/Stringified/InvalidEmpty"), + inBuf: `{"Int":""}`, + inVal: new(structStringifiedAll), + want: new(structStringifiedAll), + wantErr: EU(strconv.ErrSyntax).withVal(`""`).withPos(`{"Int":`, "/Int").withType('"', T[int64]()), + }, { + name: jsontest.Name("Structs/LegacyStringified"), + opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, + inBuf: `{ + "Bool": "true", + "String": "\"hello\"", + "Bytes": "AQID", + "Int": "-64", + "Uint": "64", + "Float": "3.14159", + "Map": {"key": "value"}, + "StructScalars": { + "Bool": true, + "String": "hello", + "Bytes": "AQID", + "Int": -64, + "Uint": 64, + "Float": 3.14159 + }, + "StructMaps": { + "MapBool": {"": true}, + "MapString": {"": "hello"}, + "MapBytes": {"": "AQID"}, + "MapInt": {"": -64}, + "MapUint": {"": 64}, + "MapFloat": {"": 3.14159} + }, + "StructSlices": { + "SliceBool": [true], + "SliceString": ["hello"], + "SliceBytes": ["AQID"], + "SliceInt": [-64], + "SliceUint": [64], + "SliceFloat": [3.14159] + }, + "Slice": ["fizz", "buzz"], + "Array": ["goodbye"] +}`, + inVal: new(structStringifiedAll), + want: addr(structStringifiedAll{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + Map: map[string]string{"key": "value"}, + StructScalars: structScalars{ + Bool: true, + String: "hello", + Bytes: []byte{1, 2, 3}, + Int: -64, + Uint: +64, + Float: 3.14159, + }, + StructMaps: structMaps{ + MapBool: map[string]bool{"": true}, + MapString: map[string]string{"": "hello"}, + MapBytes: map[string][]byte{"": {1, 2, 3}}, + MapInt: map[string]int64{"": -64}, + MapUint: map[string]uint64{"": +64}, + MapFloat: map[string]float64{"": 3.14159}, + }, + StructSlices: structSlices{ + SliceBool: []bool{true}, + SliceString: []string{"hello"}, + SliceBytes: [][]byte{{1, 2, 3}}, + SliceInt: []int64{-64}, + SliceUint: []uint64{+64}, + SliceFloat: []float64{3.14159}, + }, + Slice: []string{"fizz", "buzz"}, + Array: [1]string{"goodbye"}, + }), + }, { + name: jsontest.Name("Structs/LegacyStringified/InvalidBool"), + opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, + inBuf: `{"Bool": true}`, + inVal: new(structStringifiedAll), + wantErr: EU(nil).withPos(`{"Bool": `, "/Bool").withType('t', T[bool]()), + }, { + name: jsontest.Name("Structs/LegacyStringified/InvalidString"), + opts: []Options{jsonflags.StringifyWithLegacySemantics | 1}, + inBuf: `{"String": "string"}`, + inVal: new(structStringifiedAll), + wantErr: EU(newInvalidCharacterError("s", "at start of string (expecting '\"')", 0, "")). + withPos(`{"String": `, "/String").withType('"', T[string]()), + }, { + name: jsontest.Name("Structs/Format/Bytes"), + inBuf: `{ + "Base16": "0123456789abcdef", + "Base32": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", + "Base32Hex": "0123456789ABCDEFGHIJKLMNOPQRSTUV", + "Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", + "Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + "Array": [1, 2, 3, 4] +}`, + inVal: new(structFormatBytes), + want: addr(structFormatBytes{ + Base16: []byte("\x01\x23\x45\x67\x89\xab\xcd\xef"), + Base32: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), + Base32Hex: []byte("\x00D2\x14\xc7BT\xb65τe:V\xd7\xc6u\xbew\xdf"), + Base64: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2Û¯\xc3\x1c\xb3\xd3]\xb7㞻\xf3ß¿"), + Base64URL: []byte("\x00\x10\x83\x10Q\x87 \x92\x8b0ӏA\x14\x93QU\x97a\x96\x9bqן\x82\x18\xa3\x92Y\xa7\xa2\x9a\xab\xb2Û¯\xc3\x1c\xb3\xd3]\xb7㞻\xf3ß¿"), + Array: []byte{1, 2, 3, 4}, + }), + }, { + name: jsontest.Name("Structs/Format/ArrayBytes"), + inBuf: `{ + "Base16": "01020304", + "Base32": "AEBAGBA=", + "Base32Hex": "0410610=", + "Base64": "AQIDBA==", + "Base64URL": "AQIDBA==", + "Array": [1, 2, 3, 4], + "Default": "AQIDBA==" +}`, + inVal: new(structFormatArrayBytes), + want: addr(structFormatArrayBytes{ + Base16: [4]byte{1, 2, 3, 4}, + Base32: [4]byte{1, 2, 3, 4}, + Base32Hex: [4]byte{1, 2, 3, 4}, + Base64: [4]byte{1, 2, 3, 4}, + Base64URL: [4]byte{1, 2, 3, 4}, + Array: [4]byte{1, 2, 3, 4}, + Default: [4]byte{1, 2, 3, 4}, + }), + }, { + name: jsontest.Name("Structs/Format/ArrayBytes/Legacy"), + opts: []Options{jsonflags.FormatBytesWithLegacySemantics | 1}, + inBuf: `{ + "Base16": "01020304", + "Base32": "AEBAGBA=", + "Base32Hex": "0410610=", + "Base64": "AQIDBA==", + "Base64URL": "AQIDBA==", + "Array": [1, 2, 3, 4], + "Default": [1, 2, 3, 4] +}`, + inVal: new(structFormatArrayBytes), + want: addr(structFormatArrayBytes{ + Base16: [4]byte{1, 2, 3, 4}, + Base32: [4]byte{1, 2, 3, 4}, + Base32Hex: [4]byte{1, 2, 3, 4}, + Base64: [4]byte{1, 2, 3, 4}, + Base64URL: [4]byte{1, 2, 3, 4}, + Array: [4]byte{1, 2, 3, 4}, + Default: [4]byte{1, 2, 3, 4}, + }), + }, { + name: jsontest.Name("Structs/Format/Bytes/Array"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *byte) error { + if string(b) == "true" { + *v = 1 + } else { + *v = 0 + } + return nil + })), + }, + inBuf: `{"Array":[false,true,false,true,false,true]}`, + inVal: new(struct { + Array []byte `json:",format:array"` + }), + want: addr(struct { + Array []byte `json:",format:array"` + }{ + Array: []byte{0, 1, 0, 1, 0, 1}, + }), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/WrongKind"), + inBuf: `{"Base16": [1,2,3,4]}`, + inVal: new(structFormatBytes), + wantErr: EU(nil).withPos(`{"Base16": `, "/Base16").withType('[', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/AllPadding"), + inBuf: `{"Base16": "===="}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := hex.Decode(make([]byte, 2), []byte("=====")) + return err + }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/EvenPadding"), + inBuf: `{"Base16": "0123456789abcdef="}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := hex.Decode(make([]byte, 8), []byte("0123456789abcdef=")) + return err + }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/OddPadding"), + inBuf: `{"Base16": "0123456789abcdef0="}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := hex.Decode(make([]byte, 9), []byte("0123456789abcdef0=")) + return err + }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/LineFeed"), + inBuf: `{"Base16": "aa\naa"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := hex.Decode(make([]byte, 9), []byte("aa\naa")) + return err + }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/CarriageReturn"), + inBuf: `{"Base16": "aa\raa"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := hex.Decode(make([]byte, 9), []byte("aa\raa")) + return err + }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/Space"), + inBuf: `{"Base16": "aa aa"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := hex.Decode(make([]byte, 9), []byte("aa aa")) + return err + }()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/Padding"), + inBuf: `[ + {"Base32": "NA======"}, + {"Base32": "NBSQ===="}, + {"Base32": "NBSWY==="}, + {"Base32": "NBSWY3A="}, + {"Base32": "NBSWY3DP"} + ]`, + inVal: new([]structFormatBytes), + want: addr([]structFormatBytes{ + {Base32: []byte("h")}, + {Base32: []byte("he")}, + {Base32: []byte("hel")}, + {Base32: []byte("hell")}, + {Base32: []byte("hello")}, + }), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/Invalid/NoPadding"), + inBuf: `[ + {"Base32": "NA"}, + {"Base32": "NBSQ"}, + {"Base32": "NBSWY"}, + {"Base32": "NBSWY3A"}, + {"Base32": "NBSWY3DP"} + ]`, + inVal: new([]structFormatBytes), + wantErr: EU(func() error { + _, err := base32.StdEncoding.Decode(make([]byte, 1), []byte("NA")) + return err + }()).withPos(`[`+"\n\t\t\t\t"+`{"Base32": `, "/0/Base32").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/WrongAlphabet"), + inBuf: `{"Base32": "0123456789ABCDEFGHIJKLMNOPQRSTUV"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := base32.StdEncoding.Decode(make([]byte, 20), []byte("0123456789ABCDEFGHIJKLMNOPQRSTUV")) + return err + }()).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32Hex/WrongAlphabet"), + inBuf: `{"Base32Hex": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := base32.HexEncoding.Decode(make([]byte, 20), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")) + return err + }()).withPos(`{"Base32Hex": `, "/Base32Hex").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/NonAlphabet/LineFeed"), + inBuf: `{"Base32": "AAAA\nAAAA"}`, + inVal: new(structFormatBytes), + wantErr: EU(errors.New("illegal character '\\n' at offset 4")).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/NonAlphabet/CarriageReturn"), + inBuf: `{"Base32": "AAAA\rAAAA"}`, + inVal: new(structFormatBytes), + wantErr: EU(errors.New("illegal character '\\r' at offset 4")).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/NonAlphabet/Space"), + inBuf: `{"Base32": "AAAA AAAA"}`, + inVal: new(structFormatBytes), + wantErr: EU(base32.CorruptInputError(4)).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/WrongAlphabet"), + inBuf: `{"Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := base64.StdEncoding.Decode(make([]byte, 48), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")) + return err + }()).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64URL/WrongAlphabet"), + inBuf: `{"Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}`, + inVal: new(structFormatBytes), + wantErr: EU(func() error { + _, err := base64.URLEncoding.Decode(make([]byte, 48), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")) + return err + }()).withPos(`{"Base64URL": `, "/Base64URL").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/NonAlphabet/LineFeed"), + inBuf: `{"Base64": "aa=\n="}`, + inVal: new(structFormatBytes), + wantErr: EU(errors.New("illegal character '\\n' at offset 3")).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/NonAlphabet/CarriageReturn"), + inBuf: `{"Base64": "aa=\r="}`, + inVal: new(structFormatBytes), + wantErr: EU(errors.New("illegal character '\\r' at offset 3")).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Bytes/Base64/NonAlphabet/Ignored"), + opts: []Options{jsonflags.FormatBytesWithLegacySemantics | 1}, + inBuf: `{"Base64": "aa=\r\n="}`, + inVal: new(structFormatBytes), + want: &structFormatBytes{Base64: []byte{105}}, + }, { + name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/NonAlphabet/Space"), + inBuf: `{"Base64": "aa= ="}`, + inVal: new(structFormatBytes), + wantErr: EU(base64.CorruptInputError(2)).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Floats"), + inBuf: `[ + {"NonFinite": 3.141592653589793, "PointerNonFinite": 3.141592653589793}, + {"NonFinite": "-Infinity", "PointerNonFinite": "-Infinity"}, + {"NonFinite": "Infinity", "PointerNonFinite": "Infinity"} +]`, + inVal: new([]structFormatFloats), + want: addr([]structFormatFloats{ + {NonFinite: math.Pi, PointerNonFinite: addr(math.Pi)}, + {NonFinite: math.Inf(-1), PointerNonFinite: addr(math.Inf(-1))}, + {NonFinite: math.Inf(+1), PointerNonFinite: addr(math.Inf(+1))}, + }), + }, { + name: jsontest.Name("Structs/Format/Floats/NaN"), + inBuf: `{"NonFinite": "NaN"}`, + inVal: new(structFormatFloats), + // Avoid checking want since reflect.DeepEqual fails for NaNs. + }, { + name: jsontest.Name("Structs/Format/Floats/Invalid/NaN"), + inBuf: `{"NonFinite": "nan"}`, + inVal: new(structFormatFloats), + wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), + }, { + name: jsontest.Name("Structs/Format/Floats/Invalid/PositiveInfinity"), + inBuf: `{"NonFinite": "+Infinity"}`, + inVal: new(structFormatFloats), + wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), + }, { + name: jsontest.Name("Structs/Format/Floats/Invalid/NegativeInfinitySpace"), + inBuf: `{"NonFinite": "-Infinity "}`, + inVal: new(structFormatFloats), + wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()), + }, { + name: jsontest.Name("Structs/Format/Maps"), + inBuf: `[ + {"EmitNull": null, "PointerEmitNull": null, "EmitEmpty": null, "PointerEmitEmpty": null, "EmitDefault": null, "PointerEmitDefault": null}, + {"EmitNull": {}, "PointerEmitNull": {}, "EmitEmpty": {}, "PointerEmitEmpty": {}, "EmitDefault": {}, "PointerEmitDefault": {}}, + {"EmitNull": {"k": "v"}, "PointerEmitNull": {"k": "v"}, "EmitEmpty": {"k": "v"}, "PointerEmitEmpty": {"k": "v"}, "EmitDefault": {"k": "v"}, "PointerEmitDefault": {"k": "v"}} +]`, + inVal: new([]structFormatMaps), + want: addr([]structFormatMaps{{ + EmitNull: map[string]string(nil), PointerEmitNull: (*map[string]string)(nil), + EmitEmpty: map[string]string(nil), PointerEmitEmpty: (*map[string]string)(nil), + EmitDefault: map[string]string(nil), PointerEmitDefault: (*map[string]string)(nil), + }, { + EmitNull: map[string]string{}, PointerEmitNull: addr(map[string]string{}), + EmitEmpty: map[string]string{}, PointerEmitEmpty: addr(map[string]string{}), + EmitDefault: map[string]string{}, PointerEmitDefault: addr(map[string]string{}), + }, { + EmitNull: map[string]string{"k": "v"}, PointerEmitNull: addr(map[string]string{"k": "v"}), + EmitEmpty: map[string]string{"k": "v"}, PointerEmitEmpty: addr(map[string]string{"k": "v"}), + EmitDefault: map[string]string{"k": "v"}, PointerEmitDefault: addr(map[string]string{"k": "v"}), + }}), + }, { + name: jsontest.Name("Structs/Format/Slices"), + inBuf: `[ + {"EmitNull": null, "PointerEmitNull": null, "EmitEmpty": null, "PointerEmitEmpty": null, "EmitDefault": null, "PointerEmitDefault": null}, + {"EmitNull": [], "PointerEmitNull": [], "EmitEmpty": [], "PointerEmitEmpty": [], "EmitDefault": [], "PointerEmitDefault": []}, + {"EmitNull": ["v"], "PointerEmitNull": ["v"], "EmitEmpty": ["v"], "PointerEmitEmpty": ["v"], "EmitDefault": ["v"], "PointerEmitDefault": ["v"]} +]`, + inVal: new([]structFormatSlices), + want: addr([]structFormatSlices{{ + EmitNull: []string(nil), PointerEmitNull: (*[]string)(nil), + EmitEmpty: []string(nil), PointerEmitEmpty: (*[]string)(nil), + EmitDefault: []string(nil), PointerEmitDefault: (*[]string)(nil), + }, { + EmitNull: []string{}, PointerEmitNull: addr([]string{}), + EmitEmpty: []string{}, PointerEmitEmpty: addr([]string{}), + EmitDefault: []string{}, PointerEmitDefault: addr([]string{}), + }, { + EmitNull: []string{"v"}, PointerEmitNull: addr([]string{"v"}), + EmitEmpty: []string{"v"}, PointerEmitEmpty: addr([]string{"v"}), + EmitDefault: []string{"v"}, PointerEmitDefault: addr([]string{"v"}), + }}), + }, { + name: jsontest.Name("Structs/Format/Invalid/Bool"), + inBuf: `{"Bool":true}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, T[bool]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/String"), + inBuf: `{"String": "string"}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"String": `, "/String").withType(0, T[string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Bytes"), + inBuf: `{"Bytes": "bytes"}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Bytes": `, "/Bytes").withType(0, T[[]byte]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Int"), + inBuf: `{"Int": 1}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Int": `, "/Int").withType(0, T[int64]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Uint"), + inBuf: `{"Uint": 1}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Uint": `, "/Uint").withType(0, T[uint64]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Float"), + inBuf: `{"Float" : 1}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Float" : `, "/Float").withType(0, T[float64]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Map"), + inBuf: `{"Map":{}}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Map":`, "/Map").withType(0, T[map[string]string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Struct"), + inBuf: `{"Struct": {}}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Struct": `, "/Struct").withType(0, T[structAll]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Slice"), + inBuf: `{"Slice": {}}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Slice": `, "/Slice").withType(0, T[[]string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Array"), + inBuf: `{"Array": []}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Array": `, "/Array").withType(0, T[[1]string]()), + }, { + name: jsontest.Name("Structs/Format/Invalid/Interface"), + inBuf: `{"Interface": "anything"}`, + inVal: new(structFormatInvalid), + wantErr: EU(errInvalidFormatFlag).withPos(`{"Interface": `, "/Interface").withType(0, T[any]()), + }, { + name: jsontest.Name("Structs/Inline/Zero"), + inBuf: `{"D":""}`, + inVal: new(structInlined), + want: new(structInlined), + }, { + name: jsontest.Name("Structs/Inline/Alloc"), + inBuf: `{"E":"","F":"","G":"","A":"","B":"","D":""}`, + inVal: new(structInlined), + want: addr(structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{}, + StructEmbed1: StructEmbed1{}, + }, + StructEmbed2: &StructEmbed2{}, + }), + }, { + name: jsontest.Name("Structs/Inline/NonZero"), + inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, + inVal: new(structInlined), + want: addr(structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{A: "A1", B: "B1" /* C: "C1" */}, + StructEmbed1: StructEmbed1{ /* C: "C2" */ D: "D2" /* E: "E2" */}, + }, + StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, + }), + }, { + name: jsontest.Name("Structs/Inline/Merge"), + inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, + inVal: addr(structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{B: "##", C: "C1"}, + StructEmbed1: StructEmbed1{C: "C2", E: "E2"}, + }, + StructEmbed2: &StructEmbed2{E: "##", G: "G3"}, + }), + want: addr(structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{A: "A1", B: "B1", C: "C1"}, + StructEmbed1: StructEmbed1{C: "C2", D: "D2", E: "E2"}, + }, + StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Noop"), + inBuf: `{"A":1,"B":2}`, + inVal: new(structInlineTextValue), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(nil), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Nil"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structInlineTextValue), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Empty"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineTextValue{X: jsontext.Value{}}), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Whitespace"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineTextValue{X: jsontext.Value("\n\r\t ")}), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value("")}), + wantErr: EU(errRawInlinedNotObject).withPos(`{"A":1,`, "/fizz").withType('"', T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/Null"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineTextValue{X: jsontext.Value("null")}), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value("null")}), + wantErr: EU(errRawInlinedNotObject).withPos(`{"A":1,`, "/fizz").withType('"', T[jsontext.Value]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN1/ObjectN0"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineTextValue{X: jsontext.Value(` { } `)}), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(` {"fizz":"buzz"}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeN2/ObjectN1"), + inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, + inVal: addr(structInlineTextValue{X: jsontext.Value(` { "fizz" : "buzz" } `)}), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(` { "fizz" : "buzz","fizz":"buzz","foo":[ 1 , 2 , 3 ]}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Merge/EndObject"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineTextValue{X: jsontext.Value(` } `)}), + // NOTE: This produces invalid output, + // but the value being merged into is already invalid. + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`,"fizz":"buzz"}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/MergeInvalidValue"), + inBuf: `{"A":1,"fizz":nil,"B":2}`, + inVal: new(structInlineTextValue), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":`)}), + wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/CaseSensitive"), + inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, + inVal: new(structInlineTextValue), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz","a":3}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/RejectDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(false)}, + inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, + inVal: new(structInlineTextValue), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`), B: 2}), + wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, + inVal: new(structInlineTextValue), + want: addr(structInlineTextValue{A: 1, X: jsontext.Value(`{"fizz":"buzz","fizz":"buzz"}`), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Noop"), + inBuf: `{}`, + inVal: new(structInlinePointerInlineTextValue), + want: new(structInlinePointerInlineTextValue), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Alloc"), + inBuf: `{"A":1,"fizz":"buzz"}`, + inVal: new(structInlinePointerInlineTextValue), + want: addr(structInlinePointerInlineTextValue{ + X: &struct { + A int + X jsontext.Value `json:",inline"` + }{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`)}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/TextValue/Nested/Merge"), + inBuf: `{"fizz":"buzz"}`, + inVal: addr(structInlinePointerInlineTextValue{ + X: &struct { + A int + X jsontext.Value `json:",inline"` + }{A: 1}, + }), + want: addr(structInlinePointerInlineTextValue{ + X: &struct { + A int + X jsontext.Value `json:",inline"` + }{A: 1, X: jsontext.Value(`{"fizz":"buzz"}`)}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Noop"), + inBuf: `{"A":1,"B":2}`, + inVal: new(structInlinePointerTextValue), + want: addr(structInlinePointerTextValue{A: 1, X: nil, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Alloc"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structInlinePointerTextValue), + want: addr(structInlinePointerTextValue{A: 1, X: addr(jsontext.Value(`{"fizz":"buzz"}`)), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Merge"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlinePointerTextValue{X: addr(jsontext.Value(`{"fizz":"buzz"}`))}), + want: addr(structInlinePointerTextValue{A: 1, X: addr(jsontext.Value(`{"fizz":"buzz","fizz":"buzz"}`)), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerTextValue/Nested/Nil"), + inBuf: `{"fizz":"buzz"}`, + inVal: new(structInlineInlinePointerTextValue), + want: addr(structInlineInlinePointerTextValue{ + X: struct { + X *jsontext.Value `json:",inline"` + }{X: addr(jsontext.Value(`{"fizz":"buzz"}`))}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Noop"), + inBuf: `{"A":1,"B":2}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{A: 1, X: nil, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN1/Nil"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN1/Empty"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineMapStringAny{X: jsonObject{}}), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN1/ObjectN1"), + inBuf: `{"A":1,"fizz":{"charlie":"DELTA","echo":"foxtrot"},"B":2}`, + inVal: addr(structInlineMapStringAny{X: jsonObject{"fizz": jsonObject{ + "alpha": "bravo", + "charlie": "delta", + }}}), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": jsonObject{ + "alpha": "bravo", + "charlie": "DELTA", + "echo": "foxtrot", + }}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeN2/ObjectN1"), + inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, + inVal: addr(structInlineMapStringAny{X: jsonObject{"fizz": "wuzz"}}), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz", "foo": jsonArray{1.0, 2.0, 3.0}}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeInvalidValue"), + inBuf: `{"A":1,"fizz":nil,"B":2}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": nil}}), + wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/MergeInvalidValue/Existing"), + inBuf: `{"A":1,"fizz":nil,"B":2}`, + inVal: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": true}}), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": true}}), + wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/CaseSensitive"), + inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz", "a": 3.0}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/RejectDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(false)}, + inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": "buzz"}, B: 2}), + wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"A":1,"fizz":{"one":1,"two":-2},"B":2,"fizz":{"two":2,"three":3}}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{A: 1, X: jsonObject{"fizz": jsonObject{"one": 1.0, "two": 2.0, "three": 3.0}}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Noop"), + inBuf: `{}`, + inVal: new(structInlinePointerInlineMapStringAny), + want: new(structInlinePointerInlineMapStringAny), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Alloc"), + inBuf: `{"A":1,"fizz":"buzz"}`, + inVal: new(structInlinePointerInlineMapStringAny), + want: addr(structInlinePointerInlineMapStringAny{ + X: &struct { + A int + X jsonObject `json:",inline"` + }{A: 1, X: jsonObject{"fizz": "buzz"}}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringAny/Nested/Merge"), + inBuf: `{"fizz":"buzz"}`, + inVal: addr(structInlinePointerInlineMapStringAny{ + X: &struct { + A int + X jsonObject `json:",inline"` + }{A: 1}, + }), + want: addr(structInlinePointerInlineMapStringAny{ + X: &struct { + A int + X jsonObject `json:",inline"` + }{A: 1, X: jsonObject{"fizz": "buzz"}}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/UnmarshalFunc"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *any) error { + var err error + *v, err = strconv.ParseFloat(string(bytes.Trim(b, `"`)), 64) + return err + })), + }, + inBuf: `{"D":"1.1","E":"2.2","F":"3.3"}`, + inVal: new(structInlineMapStringAny), + want: addr(structInlineMapStringAny{X: jsonObject{"D": 1.1, "E": 2.2, "F": 3.3}}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Noop"), + inBuf: `{"A":1,"B":2}`, + inVal: new(structInlinePointerMapStringAny), + want: addr(structInlinePointerMapStringAny{A: 1, X: nil, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Alloc"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structInlinePointerMapStringAny), + want: addr(structInlinePointerMapStringAny{A: 1, X: addr(jsonObject{"fizz": "buzz"}), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Merge"), + inBuf: `{"A":1,"fizz":"wuzz","B":2}`, + inVal: addr(structInlinePointerMapStringAny{X: addr(jsonObject{"fizz": "buzz"})}), + want: addr(structInlinePointerMapStringAny{A: 1, X: addr(jsonObject{"fizz": "wuzz"}), B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/PointerMapStringAny/Nested/Nil"), + inBuf: `{"fizz":"buzz"}`, + inVal: new(structInlineInlinePointerMapStringAny), + want: addr(structInlineInlinePointerMapStringAny{ + X: struct { + X *jsonObject `json:",inline"` + }{X: addr(jsonObject{"fizz": "buzz"})}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt"), + inBuf: `{"zero": 0, "one": 1, "two": 2}`, + inVal: new(structInlineMapStringInt), + want: addr(structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Null"), + inBuf: `{"zero": 0, "one": null, "two": 2}`, + inVal: new(structInlineMapStringInt), + want: addr(structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 0, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/Invalid"), + inBuf: `{"zero": 0, "one": {}, "two": 2}`, + inVal: new(structInlineMapStringInt), + want: addr(structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 0}, + }), + wantErr: EU(nil).withPos(`{"zero": 0, "one": `, "/one").withType('{', T[int]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/StringifiedNumbers"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `{"zero": "0", "one": "1", "two": "2"}`, + inVal: new(structInlineMapStringInt), + want: addr(structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapStringInt/UnmarshalFunc"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *int) error { + i, err := strconv.ParseInt(string(bytes.Trim(b, `"`)), 10, 64) + if err != nil { + return err + } + *v = int(i) + return nil + })), + }, + inBuf: `{"zero": "0", "one": "1", "two": "2"}`, + inVal: new(structInlineMapStringInt), + want: addr(structInlineMapStringInt{ + X: map[string]int{"zero": 0, "one": 1, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt"), + inBuf: `{"zero": 0, "one": 1, "two": 2}`, + inVal: new(structInlineMapNamedStringInt), + want: addr(structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/Null"), + inBuf: `{"zero": 0, "one": null, "two": 2}`, + inVal: new(structInlineMapNamedStringInt), + want: addr(structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 0, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/Invalid"), + inBuf: `{"zero": 0, "one": {}, "two": 2}`, + inVal: new(structInlineMapNamedStringInt), + want: addr(structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 0}, + }), + wantErr: EU(nil).withPos(`{"zero": 0, "one": `, "/one").withType('{', T[int]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/StringifiedNumbers"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `{"zero": "0", "one": 1, "two": "2"}`, + inVal: new(structInlineMapNamedStringInt), + want: addr(structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 0}, + }), + wantErr: EU(nil).withPos(`{"zero": "0", "one": `, "/one").withType('0', T[int]()), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringInt/UnmarshalFunc"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *int) error { + i, err := strconv.ParseInt(string(bytes.Trim(b, `"`)), 10, 64) + if err != nil { + return err + } + *v = int(i) + return nil + })), + }, + inBuf: `{"zero": "0", "one": "1", "two": "2"}`, + inVal: new(structInlineMapNamedStringInt), + want: addr(structInlineMapNamedStringInt{ + X: map[namedString]int{"zero": 0, "one": 1, "two": 2}, + }), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/Noop"), + inBuf: `{"A":1,"B":2}`, + inVal: new(structInlineMapNamedStringAny), + want: addr(structInlineMapNamedStringAny{A: 1, X: nil, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN1/Nil"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structInlineMapNamedStringAny), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN1/Empty"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: addr(structInlineMapNamedStringAny{X: map[namedString]any{}}), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN1/ObjectN1"), + inBuf: `{"A":1,"fizz":{"charlie":"DELTA","echo":"foxtrot"},"B":2}`, + inVal: addr(structInlineMapNamedStringAny{X: map[namedString]any{"fizz": jsonObject{ + "alpha": "bravo", + "charlie": "delta", + }}}), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": jsonObject{ + "alpha": "bravo", + "charlie": "DELTA", + "echo": "foxtrot", + }}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeN2/ObjectN1"), + inBuf: `{"A":1,"fizz":"buzz","B":2,"foo": [ 1 , 2 , 3 ]}`, + inVal: addr(structInlineMapNamedStringAny{X: map[namedString]any{"fizz": "wuzz"}}), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz", "foo": jsonArray{1.0, 2.0, 3.0}}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeInvalidValue"), + inBuf: `{"A":1,"fizz":nil,"B":2}`, + inVal: new(structInlineMapNamedStringAny), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": nil}}), + wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/MergeInvalidValue/Existing"), + inBuf: `{"A":1,"fizz":nil,"B":2}`, + inVal: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": true}}), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": true}}), + wantErr: newInvalidCharacterError("i", "in literal null (expecting 'u')", len64(`{"A":1,"fizz":n`), "/fizz"), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/CaseSensitive"), + inBuf: `{"A":1,"fizz":"buzz","B":2,"a":3}`, + inVal: new(structInlineMapNamedStringAny), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz", "a": 3.0}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/RejectDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(false)}, + inBuf: `{"A":1,"fizz":"buzz","B":2,"fizz":"buzz"}`, + inVal: new(structInlineMapNamedStringAny), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": "buzz"}, B: 2}), + wantErr: newDuplicateNameError("", []byte(`"fizz"`), len64(`{"A":1,"fizz":"buzz","B":2,`)), + }, { + name: jsontest.Name("Structs/InlinedFallback/MapNamedStringAny/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"A":1,"fizz":{"one":1,"two":-2},"B":2,"fizz":{"two":2,"three":3}}`, + inVal: new(structInlineMapNamedStringAny), + want: addr(structInlineMapNamedStringAny{A: 1, X: map[namedString]any{"fizz": map[string]any{"one": 1.0, "two": 2.0, "three": 3.0}}, B: 2}), + }, { + name: jsontest.Name("Structs/InlinedFallback/RejectUnknownMembers"), + opts: []Options{RejectUnknownMembers(true)}, + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structInlineTextValue), + // NOTE: DiscardUnknownMembers has no effect since this is "inline". + want: addr(structInlineTextValue{ + A: 1, + X: jsontext.Value(`{"fizz":"buzz"}`), + B: 2, + }), + }, { + name: jsontest.Name("Structs/UnknownFallback/RejectUnknownMembers"), + opts: []Options{RejectUnknownMembers(true)}, + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structUnknownTextValue), + want: addr(structUnknownTextValue{A: 1}), + wantErr: EU(ErrUnknownName).withPos(`{"A":1,`, "/fizz").withType('"', T[structUnknownTextValue]()), + }, { + name: jsontest.Name("Structs/UnknownFallback"), + inBuf: `{"A":1,"fizz":"buzz","B":2}`, + inVal: new(structUnknownTextValue), + want: addr(structUnknownTextValue{ + A: 1, + X: jsontext.Value(`{"fizz":"buzz"}`), + B: 2, + }), + }, { + name: jsontest.Name("Structs/UnknownIgnored"), + opts: []Options{RejectUnknownMembers(false)}, + inBuf: `{"unknown":"fizzbuzz"}`, + inVal: new(structAll), + want: new(structAll), + }, { + name: jsontest.Name("Structs/RejectUnknownMembers"), + opts: []Options{RejectUnknownMembers(true)}, + inBuf: `{"unknown":"fizzbuzz"}`, + inVal: new(structAll), + want: new(structAll), + wantErr: EU(ErrUnknownName).withPos(`{`, "/unknown").withType('"', T[structAll]()), + }, { + name: jsontest.Name("Structs/UnexportedIgnored"), + inBuf: `{"ignored":"unused"}`, + inVal: new(structUnexportedIgnored), + want: new(structUnexportedIgnored), + }, { + name: jsontest.Name("Structs/IgnoredUnexportedEmbedded"), + inBuf: `{"namedString":"unused"}`, + inVal: new(structIgnoredUnexportedEmbedded), + want: new(structIgnoredUnexportedEmbedded), + }, { + name: jsontest.Name("Structs/WeirdNames"), + inBuf: `{"":"empty",",":"comma","\"":"quote"}`, + inVal: new(structWeirdNames), + want: addr(structWeirdNames{Empty: "empty", Comma: "comma", Quote: "quote"}), + }, { + name: jsontest.Name("Structs/NoCase/Exact"), + inBuf: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`, + inVal: new(structNoCase), + want: addr(structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"}), + }, { + name: jsontest.Name("Structs/NoCase/CaseInsensitiveDefault"), + inBuf: `{"aa_a":"aa_a"}`, + inVal: new(structNoCase), + want: addr(structNoCase{AaA: "aa_a"}), + }, { + name: jsontest.Name("Structs/NoCase/MatchCaseSensitiveDelimiter"), + opts: []Options{jsonflags.MatchCaseSensitiveDelimiter | 1}, + inBuf: `{"aa_a":"aa_a"}`, + inVal: new(structNoCase), + want: addr(structNoCase{}), + }, { + name: jsontest.Name("Structs/NoCase/MatchCaseInsensitiveNames+MatchCaseSensitiveDelimiter"), + opts: []Options{MatchCaseInsensitiveNames(true), jsonflags.MatchCaseSensitiveDelimiter | 1}, + inBuf: `{"aa_a":"aa_a"}`, + inVal: new(structNoCase), + want: addr(structNoCase{AA_A: "aa_a"}), + }, { + name: jsontest.Name("Structs/NoCase/Merge/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"AaA":"AaA","aaa":"aaa","aAa":"aAa"}`, + inVal: new(structNoCase), + want: addr(structNoCase{AaA: "aAa"}), + }, { + name: jsontest.Name("Structs/NoCase/Merge/RejectDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(false)}, + inBuf: `{"AaA":"AaA","aaa":"aaa"}`, + inVal: new(structNoCase), + want: addr(structNoCase{AaA: "AaA"}), + wantErr: newDuplicateNameError("", []byte(`"aaa"`), len64(`{"AaA":"AaA",`)), + }, { + name: jsontest.Name("Structs/CaseSensitive"), + inBuf: `{"BOOL": true, "STRING": "hello", "BYTES": "AQID", "INT": -64, "UINT": 64, "FLOAT": 3.14159}`, + inVal: new(structScalars), + want: addr(structScalars{}), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCase/ExactDifferent"), + inBuf: `{"AAA":"AAA","AaA":"AaA","AAa":"AAa","Aaa":"Aaa"}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{AAA: "AAA", AaA: "AaA", AAa: "AAa", Aaa: "Aaa"}), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCase/ExactConflict"), + inBuf: `{"AAA":"AAA","AAA":"AAA"}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{AAA: "AAA"}), + wantErr: newDuplicateNameError("", []byte(`"AAA"`), len64(`{"AAA":"AAA",`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCase/OverwriteExact"), + inBuf: `{"AAA":"after"}`, + inVal: addr(structNoCaseInlineTextValue{AAA: "before"}), + want: addr(structNoCaseInlineTextValue{AAA: "after"}), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCase/NoCaseConflict"), + inBuf: `{"aaa":"aaa","aaA":"aaA"}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{AaA: "aaa"}), + wantErr: newDuplicateNameError("", []byte(`"aaA"`), len64(`{"aaa":"aaa",`)), + }, { + name: jsontest.Name("Structs/DuplicateName/NoCase/OverwriteNoCase"), + inBuf: `{"aaa":"aaa","aaA":"aaA"}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{AaA: "aaa"}), + wantErr: newDuplicateNameError("", []byte(`"aaA"`), len64(`{"aaa":"aaa",`)), + }, { + name: jsontest.Name("Structs/DuplicateName/Inline/Unknown"), + inBuf: `{"unknown":""}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"unknown":""}`)}), + }, { + name: jsontest.Name("Structs/DuplicateName/Inline/UnknownMerge"), + inBuf: `{"unknown":""}`, + inVal: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"unknown":""}`)}), + want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"unknown":"","unknown":""}`)}), + }, { + name: jsontest.Name("Structs/DuplicateName/Inline/NoCaseOkay"), + inBuf: `{"b":"","B":""}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"b":"","B":""}`)}), + }, { + name: jsontest.Name("Structs/DuplicateName/Inline/ExactConflict"), + inBuf: `{"b":"","b":""}`, + inVal: addr(structNoCaseInlineTextValue{}), + want: addr(structNoCaseInlineTextValue{X: jsontext.Value(`{"b":""}`)}), + wantErr: newDuplicateNameError("", []byte(`"b"`), len64(`{"b":"",`)), + }, { + name: jsontest.Name("Structs/Invalid/ErrUnexpectedEOF"), + inBuf: ``, + inVal: addr(structAll{}), + want: addr(structAll{}), + wantErr: io.ErrUnexpectedEOF, + }, { + name: jsontest.Name("Structs/Invalid/NestedErrUnexpectedEOF"), + inBuf: `{"Pointer":`, + inVal: addr(structAll{}), + want: addr(structAll{Pointer: new(structAll)}), + wantErr: &jsontext.SyntacticError{ByteOffset: len64(`{"Pointer":`), JSONPointer: "/Pointer", Err: io.ErrUnexpectedEOF}, + }, { + name: jsontest.Name("Structs/Invalid/Conflicting"), + inBuf: `{}`, + inVal: addr(structConflicting{}), + want: addr(structConflicting{}), + wantErr: EU(errors.New(`Go struct fields A and B conflict over JSON object name "conflict"`)).withType('{', T[structConflicting]()), + }, { + name: jsontest.Name("Structs/Invalid/NoneExported"), + inBuf: ` {}`, + inVal: addr(structNoneExported{}), + want: addr(structNoneExported{}), + wantErr: EU(errNoExportedFields).withPos(` `, "").withType('{', T[structNoneExported]()), + }, { + name: jsontest.Name("Structs/Invalid/MalformedTag"), + inBuf: `{}`, + inVal: addr(structMalformedTag{}), + want: addr(structMalformedTag{}), + wantErr: EU(errors.New("Go struct field Malformed has malformed `json` tag: invalid character '\"' at start of option (expecting Unicode letter or single quote)")).withType('{', T[structMalformedTag]()), + }, { + name: jsontest.Name("Structs/Invalid/UnexportedTag"), + inBuf: `{}`, + inVal: addr(structUnexportedTag{}), + want: addr(structUnexportedTag{}), + wantErr: EU(errors.New("unexported Go struct field unexported cannot have non-ignored `json:\"name\"` tag")).withType('{', T[structUnexportedTag]()), + }, { + name: jsontest.Name("Structs/Invalid/ExportedEmbedded"), + inBuf: `{"NamedString":"hello"}`, + inVal: addr(structExportedEmbedded{}), + want: addr(structExportedEmbedded{}), + wantErr: EU(errors.New("embedded Go struct field NamedString of non-struct type must be explicitly given a JSON name")).withType('{', T[structExportedEmbedded]()), + }, { + name: jsontest.Name("Structs/Valid/ExportedEmbedded"), + opts: []Options{jsonflags.ReportErrorsWithLegacySemantics | 1}, + inBuf: `{"NamedString":"hello"}`, + inVal: addr(structExportedEmbedded{}), + want: addr(structExportedEmbedded{"hello"}), + }, { + name: jsontest.Name("Structs/Valid/ExportedEmbeddedTag"), + inBuf: `{"name":"hello"}`, + inVal: addr(structExportedEmbeddedTag{}), + want: addr(structExportedEmbeddedTag{"hello"}), + }, { + name: jsontest.Name("Structs/Invalid/UnexportedEmbedded"), + inBuf: `{}`, + inVal: addr(structUnexportedEmbedded{}), + want: addr(structUnexportedEmbedded{}), + wantErr: EU(errors.New("embedded Go struct field namedString of non-struct type must be explicitly given a JSON name")).withType('{', T[structUnexportedEmbedded]()), + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStruct"), + inBuf: `{"Bool":true,"FizzBuzz":5,"Addr":"192.168.0.1"}`, + inVal: addr(structUnexportedEmbeddedStruct{}), + want: addr(structUnexportedEmbeddedStruct{structOmitZeroAll{Bool: true}, 5, structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}), + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), + inBuf: `{"Bool":true,"FizzBuzz":5}`, + inVal: addr(structUnexportedEmbeddedStructPointer{}), + wantErr: EU(errNilField).withPos(`{"Bool":`, "/Bool").withType(0, T[structUnexportedEmbeddedStructPointer]()), + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), + inBuf: `{"FizzBuzz":5,"Addr":"192.168.0.1"}`, + inVal: addr(structUnexportedEmbeddedStructPointer{}), + wantErr: EU(errNilField).withPos(`{"FizzBuzz":5,"Addr":`, "/Addr").withType(0, T[structUnexportedEmbeddedStructPointer]()), + }, { + name: jsontest.Name("Structs/UnexportedEmbeddedStructPointer/Nil"), + inBuf: `{"Bool":true,"FizzBuzz":10,"Addr":"192.168.0.1"}`, + inVal: addr(structUnexportedEmbeddedStructPointer{&structOmitZeroAll{Int: 5}, 5, &structNestedAddr{netip.AddrFrom4([4]byte{127, 0, 0, 1})}}), + want: addr(structUnexportedEmbeddedStructPointer{&structOmitZeroAll{Bool: true, Int: 5}, 10, &structNestedAddr{netip.AddrFrom4([4]byte{192, 168, 0, 1})}}), + }, { + name: jsontest.Name("Structs/Unknown"), + inBuf: `{ + "object0": {}, + "object1": {"key1": "value"}, + "object2": {"key1": "value", "key2": "value"}, + "objects": {"":{"":{"":{}}}}, + "array0": [], + "array1": ["value1"], + "array2": ["value1", "value2"], + "array": [[[]]], + "scalars": [null, false, true, "string", 12.345] +}`, + inVal: addr(struct{}{}), + want: addr(struct{}{}), + }, { + name: jsontest.Name("Structs/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `{"Field":"Value"}`, + inVal: addr(struct{ Field string }{}), + want: addr(struct{ Field string }{"Value"}), + }, { + name: jsontest.Name("Slices/Null"), + inBuf: `null`, + inVal: addr([]string{"something"}), + want: addr([]string(nil)), + }, { + name: jsontest.Name("Slices/Bool"), + inBuf: `[true,false]`, + inVal: new([]bool), + want: addr([]bool{true, false}), + }, { + name: jsontest.Name("Slices/String"), + inBuf: `["hello","goodbye"]`, + inVal: new([]string), + want: addr([]string{"hello", "goodbye"}), + }, { + name: jsontest.Name("Slices/Bytes"), + inBuf: `["aGVsbG8=","Z29vZGJ5ZQ=="]`, + inVal: new([][]byte), + want: addr([][]byte{[]byte("hello"), []byte("goodbye")}), + }, { + name: jsontest.Name("Slices/Int"), + inBuf: `[-2,-1,0,1,2]`, + inVal: new([]int), + want: addr([]int{-2, -1, 0, 1, 2}), + }, { + name: jsontest.Name("Slices/Uint"), + inBuf: `[0,1,2,3,4]`, + inVal: new([]uint), + want: addr([]uint{0, 1, 2, 3, 4}), + }, { + name: jsontest.Name("Slices/Float"), + inBuf: `[3.14159,12.34]`, + inVal: new([]float64), + want: addr([]float64{3.14159, 12.34}), + }, { + // NOTE: The semantics differs from v1, where the slice length is reset + // and new elements are appended to the end. + // See https://go.dev/issue/21092. + name: jsontest.Name("Slices/Merge"), + inBuf: `[{"k3":"v3"},{"k4":"v4"}]`, + inVal: addr([]map[string]string{{"k1": "v1"}, {"k2": "v2"}}[:1]), + want: addr([]map[string]string{{"k3": "v3"}, {"k4": "v4"}}), + }, { + name: jsontest.Name("Slices/Invalid/Channel"), + inBuf: `["hello"]`, + inVal: new([]chan string), + want: addr([]chan string{nil}), + wantErr: EU(nil).withPos(`[`, "/0").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Slices/RecursiveSlice"), + inBuf: `[[],[],[[]],[[],[]]]`, + inVal: new(recursiveSlice), + want: addr(recursiveSlice{ + {}, + {}, + {{}}, + {{}, {}}, + }), + }, { + name: jsontest.Name("Slices/Invalid/Bool"), + inBuf: `true`, + inVal: addr([]string{"nochange"}), + want: addr([]string{"nochange"}), + wantErr: EU(nil).withType('t', T[[]string]()), + }, { + name: jsontest.Name("Slices/Invalid/String"), + inBuf: `""`, + inVal: addr([]string{"nochange"}), + want: addr([]string{"nochange"}), + wantErr: EU(nil).withType('"', T[[]string]()), + }, { + name: jsontest.Name("Slices/Invalid/Number"), + inBuf: `0`, + inVal: addr([]string{"nochange"}), + want: addr([]string{"nochange"}), + wantErr: EU(nil).withType('0', T[[]string]()), + }, { + name: jsontest.Name("Slices/Invalid/Object"), + inBuf: `{}`, + inVal: addr([]string{"nochange"}), + want: addr([]string{"nochange"}), + wantErr: EU(nil).withType('{', T[[]string]()), + }, { + name: jsontest.Name("Slices/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `[false,true]`, + inVal: addr([]bool{true, false}), + want: addr([]bool{false, true}), + }, { + name: jsontest.Name("Arrays/Null"), + inBuf: `null`, + inVal: addr([1]string{"something"}), + want: addr([1]string{}), + }, { + name: jsontest.Name("Arrays/Bool"), + inBuf: `[true,false]`, + inVal: new([2]bool), + want: addr([2]bool{true, false}), + }, { + name: jsontest.Name("Arrays/String"), + inBuf: `["hello","goodbye"]`, + inVal: new([2]string), + want: addr([2]string{"hello", "goodbye"}), + }, { + name: jsontest.Name("Arrays/Bytes"), + inBuf: `["aGVsbG8=","Z29vZGJ5ZQ=="]`, + inVal: new([2][]byte), + want: addr([2][]byte{[]byte("hello"), []byte("goodbye")}), + }, { + name: jsontest.Name("Arrays/Int"), + inBuf: `[-2,-1,0,1,2]`, + inVal: new([5]int), + want: addr([5]int{-2, -1, 0, 1, 2}), + }, { + name: jsontest.Name("Arrays/Uint"), + inBuf: `[0,1,2,3,4]`, + inVal: new([5]uint), + want: addr([5]uint{0, 1, 2, 3, 4}), + }, { + name: jsontest.Name("Arrays/Float"), + inBuf: `[3.14159,12.34]`, + inVal: new([2]float64), + want: addr([2]float64{3.14159, 12.34}), + }, { + // NOTE: The semantics differs from v1, where elements are not merged. + // This is to maintain consistent merge semantics with slices. + name: jsontest.Name("Arrays/Merge"), + inBuf: `[{"k3":"v3"},{"k4":"v4"}]`, + inVal: addr([2]map[string]string{{"k1": "v1"}, {"k2": "v2"}}), + want: addr([2]map[string]string{{"k3": "v3"}, {"k4": "v4"}}), + }, { + name: jsontest.Name("Arrays/Invalid/Channel"), + inBuf: `["hello"]`, + inVal: new([1]chan string), + want: new([1]chan string), + wantErr: EU(nil).withPos(`[`, "/0").withType(0, T[chan string]()), + }, { + name: jsontest.Name("Arrays/Invalid/Underflow"), + inBuf: `{"F":[ ]}`, + inVal: new(struct{ F [1]string }), + want: addr(struct{ F [1]string }{}), + wantErr: EU(errArrayUnderflow).withPos(`{"F":[ `, "/F").withType(']', T[[1]string]()), + }, { + name: jsontest.Name("Arrays/Invalid/Underflow/UnmarshalArrayFromAnyLength"), + opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, + inBuf: `[-1,-2]`, + inVal: addr([4]int{1, 2, 3, 4}), + want: addr([4]int{-1, -2, 0, 0}), + }, { + name: jsontest.Name("Arrays/Invalid/Overflow"), + inBuf: `["1","2"]`, + inVal: new([1]string), + want: addr([1]string{"1"}), + wantErr: EU(errArrayOverflow).withPos(`["1","2"`, "").withType(']', T[[1]string]()), + }, { + name: jsontest.Name("Arrays/Invalid/Overflow/UnmarshalArrayFromAnyLength"), + opts: []Options{jsonflags.UnmarshalArrayFromAnyLength | 1}, + inBuf: `[-1,-2,-3,-4,-5,-6]`, + inVal: addr([4]int{1, 2, 3, 4}), + want: addr([4]int{-1, -2, -3, -4}), + }, { + name: jsontest.Name("Arrays/Invalid/Bool"), + inBuf: `true`, + inVal: addr([1]string{"nochange"}), + want: addr([1]string{"nochange"}), + wantErr: EU(nil).withType('t', T[[1]string]()), + }, { + name: jsontest.Name("Arrays/Invalid/String"), + inBuf: `""`, + inVal: addr([1]string{"nochange"}), + want: addr([1]string{"nochange"}), + wantErr: EU(nil).withType('"', T[[1]string]()), + }, { + name: jsontest.Name("Arrays/Invalid/Number"), + inBuf: `0`, + inVal: addr([1]string{"nochange"}), + want: addr([1]string{"nochange"}), + wantErr: EU(nil).withType('0', T[[1]string]()), + }, { + name: jsontest.Name("Arrays/Invalid/Object"), + inBuf: `{}`, + inVal: addr([1]string{"nochange"}), + want: addr([1]string{"nochange"}), + wantErr: EU(nil).withType('{', T[[1]string]()), + }, { + name: jsontest.Name("Arrays/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `[false,true]`, + inVal: addr([2]bool{true, false}), + want: addr([2]bool{false, true}), + }, { + name: jsontest.Name("Pointers/NullL0"), + inBuf: `null`, + inVal: new(*string), + want: addr((*string)(nil)), + }, { + name: jsontest.Name("Pointers/NullL1"), + inBuf: `null`, + inVal: addr(new(*string)), + want: addr((**string)(nil)), + }, { + name: jsontest.Name("Pointers/Bool"), + inBuf: `true`, + inVal: addr(new(bool)), + want: addr(addr(true)), + }, { + name: jsontest.Name("Pointers/String"), + inBuf: `"hello"`, + inVal: addr(new(string)), + want: addr(addr("hello")), + }, { + name: jsontest.Name("Pointers/Bytes"), + inBuf: `"aGVsbG8="`, + inVal: addr(new([]byte)), + want: addr(addr([]byte("hello"))), + }, { + name: jsontest.Name("Pointers/Int"), + inBuf: `-123`, + inVal: addr(new(int)), + want: addr(addr(int(-123))), + }, { + name: jsontest.Name("Pointers/Uint"), + inBuf: `123`, + inVal: addr(new(int)), + want: addr(addr(int(123))), + }, { + name: jsontest.Name("Pointers/Float"), + inBuf: `123.456`, + inVal: addr(new(float64)), + want: addr(addr(float64(123.456))), + }, { + name: jsontest.Name("Pointers/Allocate"), + inBuf: `"hello"`, + inVal: addr((*string)(nil)), + want: addr(addr("hello")), + }, { + name: jsontest.Name("Points/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `true`, + inVal: addr(new(bool)), + want: addr(addr(true)), + }, { + name: jsontest.Name("Interfaces/Empty/Null"), + inBuf: `null`, + inVal: new(any), + want: new(any), + }, { + name: jsontest.Name("Interfaces/NonEmpty/Null"), + inBuf: `null`, + inVal: new(io.Reader), + want: new(io.Reader), + }, { + name: jsontest.Name("Interfaces/NonEmpty/Invalid"), + inBuf: `"hello"`, + inVal: new(io.Reader), + want: new(io.Reader), + wantErr: EU(errNilInterface).withType(0, T[io.Reader]()), + }, { + name: jsontest.Name("Interfaces/Empty/False"), + inBuf: `false`, + inVal: new(any), + want: func() any { + var vi any = false + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Empty/True"), + inBuf: `true`, + inVal: new(any), + want: func() any { + var vi any = true + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Empty/String"), + inBuf: `"string"`, + inVal: new(any), + want: func() any { + var vi any = "string" + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Empty/Number"), + inBuf: `3.14159`, + inVal: new(any), + want: func() any { + var vi any = 3.14159 + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Empty/Object"), + inBuf: `{"k":"v"}`, + inVal: new(any), + want: func() any { + var vi any = map[string]any{"k": "v"} + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Empty/Array"), + inBuf: `["v"]`, + inVal: new(any), + want: func() any { + var vi any = []any{"v"} + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/NamedAny/String"), + inBuf: `"string"`, + inVal: new(namedAny), + want: func() namedAny { + var vi namedAny = "string" + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Invalid"), + inBuf: `]`, + inVal: new(any), + want: new(any), + wantErr: newInvalidCharacterError("]", "at start of value", 0, ""), + }, { + // NOTE: The semantics differs from v1, + // where existing map entries were not merged into. + // See https://go.dev/issue/26946. + // See https://go.dev/issue/33993. + name: jsontest.Name("Interfaces/Merge/Map"), + inBuf: `{"k2":"v2"}`, + inVal: func() any { + var vi any = map[string]string{"k1": "v1"} + return &vi + }(), + want: func() any { + var vi any = map[string]string{"k1": "v1", "k2": "v2"} + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Merge/Struct"), + inBuf: `{"Array":["goodbye"]}`, + inVal: func() any { + var vi any = structAll{String: "hello"} + return &vi + }(), + want: func() any { + var vi any = structAll{String: "hello", Array: [1]string{"goodbye"}} + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Merge/NamedInt"), + inBuf: `64`, + inVal: func() any { + var vi any = namedInt64(-64) + return &vi + }(), + want: func() any { + var vi any = namedInt64(+64) + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `true`, + inVal: new(any), + want: func() any { + var vi any = true + return &vi + }(), + }, { + name: jsontest.Name("Interfaces/Any"), + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{nil, false, true, "", 0.0, map[string]any{}, []any{}}}), + }, { + name: jsontest.Name("Interfaces/Any/Named"), + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X namedAny }), + want: addr(struct{ X namedAny }{[]any{nil, false, true, "", 0.0, map[string]any{}, []any{}}}), + }, { + name: jsontest.Name("Interfaces/Any/Stringified"), + opts: []Options{StringifyNumbers(true)}, + inBuf: `{"X":"0"}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{"0"}), + }, { + name: jsontest.Name("Interfaces/Any/UnmarshalFunc/Any"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *any) error { + *v = "called" + return nil + })), + }, + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{"called"}), + }, { + name: jsontest.Name("Interfaces/Any/UnmarshalFunc/Bool"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *bool) error { + *v = string(b) != "true" + return nil + })), + }, + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{nil, true, false, "", 0.0, map[string]any{}, []any{}}}), + }, { + name: jsontest.Name("Interfaces/Any/UnmarshalFunc/String"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { + *v = "called" + return nil + })), + }, + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{nil, false, true, "called", 0.0, map[string]any{}, []any{}}}), + }, { + name: jsontest.Name("Interfaces/Any/UnmarshalFunc/Float64"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *float64) error { + *v = 3.14159 + return nil + })), + }, + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{nil, false, true, "", 3.14159, map[string]any{}, []any{}}}), + }, { + name: jsontest.Name("Interfaces/Any/UnmarshalFunc/MapStringAny"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *map[string]any) error { + *v = map[string]any{"called": nil} + return nil + })), + }, + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{nil, false, true, "", 0.0, map[string]any{"called": nil}, []any{}}}), + }, { + name: jsontest.Name("Interfaces/Any/UnmarshalFunc/SliceAny"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *[]any) error { + *v = []any{"called"} + return nil + })), + }, + inBuf: `{"X":[null,false,true,"",0,{},[]]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{"called"}}), + }, { + name: jsontest.Name("Interfaces/Any/Maps/NonEmpty"), + inBuf: `{"X":{"fizz":"buzz"}}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{map[string]any{"fizz": "buzz"}}), + }, { + name: jsontest.Name("Interfaces/Any/Maps/RejectDuplicateNames"), + inBuf: `{"X":{"fizz":"buzz","fizz":true}}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{map[string]any{"fizz": "buzz"}}), + wantErr: newDuplicateNameError("/X", []byte(`"fizz"`), len64(`{"X":{"fizz":"buzz",`)), + }, { + name: jsontest.Name("Interfaces/Any/Maps/AllowDuplicateNames"), + opts: []Options{jsontext.AllowDuplicateNames(true)}, + inBuf: `{"X":{"fizz":"buzz","fizz":true}}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{map[string]any{"fizz": "buzz"}}), + wantErr: EU(nil).withPos(`{"X":{"fizz":"buzz","fizz":`, "/X/fizz").withType('t', T[string]()), + }, { + name: jsontest.Name("Interfaces/Any/Slices/NonEmpty"), + inBuf: `{"X":["fizz","buzz"]}`, + inVal: new(struct{ X any }), + want: addr(struct{ X any }{[]any{"fizz", "buzz"}}), + }, { + name: jsontest.Name("Methods/NilPointer/Null"), + inBuf: `{"X":null}`, + inVal: addr(struct{ X *allMethods }{X: (*allMethods)(nil)}), + want: addr(struct{ X *allMethods }{X: (*allMethods)(nil)}), // method should not be called + }, { + name: jsontest.Name("Methods/NilPointer/Value"), + inBuf: `{"X":"value"}`, + inVal: addr(struct{ X *allMethods }{X: (*allMethods)(nil)}), + want: addr(struct{ X *allMethods }{X: &allMethods{method: "UnmarshalJSONFrom", value: []byte(`"value"`)}}), + }, { + name: jsontest.Name("Methods/NilInterface/Null"), + inBuf: `{"X":null}`, + inVal: addr(struct{ X MarshalerTo }{X: (*allMethods)(nil)}), + want: addr(struct{ X MarshalerTo }{X: nil}), // interface value itself is nil'd out + }, { + name: jsontest.Name("Methods/NilInterface/Value"), + inBuf: `{"X":"value"}`, + inVal: addr(struct{ X MarshalerTo }{X: (*allMethods)(nil)}), + want: addr(struct{ X MarshalerTo }{X: &allMethods{method: "UnmarshalJSONFrom", value: []byte(`"value"`)}}), + }, { + name: jsontest.Name("Methods/AllMethods"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *allMethods }), + want: addr(struct{ X *allMethods }{X: &allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}), + }, { + name: jsontest.Name("Methods/AllMethodsExceptJSONv2"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *allMethodsExceptJSONv2 }), + want: addr(struct{ X *allMethodsExceptJSONv2 }{X: &allMethodsExceptJSONv2{allMethods: allMethods{method: "UnmarshalJSON", value: []byte(`"hello"`)}}}), + }, { + name: jsontest.Name("Methods/AllMethodsExceptJSONv1"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *allMethodsExceptJSONv1 }), + want: addr(struct{ X *allMethodsExceptJSONv1 }{X: &allMethodsExceptJSONv1{allMethods: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), + }, { + name: jsontest.Name("Methods/AllMethodsExceptText"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *allMethodsExceptText }), + want: addr(struct{ X *allMethodsExceptText }{X: &allMethodsExceptText{allMethods: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), + }, { + name: jsontest.Name("Methods/OnlyMethodJSONv2"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *onlyMethodJSONv2 }), + want: addr(struct{ X *onlyMethodJSONv2 }{X: &onlyMethodJSONv2{allMethods: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), + }, { + name: jsontest.Name("Methods/OnlyMethodJSONv1"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *onlyMethodJSONv1 }), + want: addr(struct{ X *onlyMethodJSONv1 }{X: &onlyMethodJSONv1{allMethods: allMethods{method: "UnmarshalJSON", value: []byte(`"hello"`)}}}), + }, { + name: jsontest.Name("Methods/OnlyMethodText"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X *onlyMethodText }), + want: addr(struct{ X *onlyMethodText }{X: &onlyMethodText{allMethods: allMethods{method: "UnmarshalText", value: []byte(`hello`)}}}), + }, { + name: jsontest.Name("Methods/Text/Null"), + inBuf: `{"X":null}`, + inVal: addr(struct{ X unmarshalTextFunc }{unmarshalTextFunc(func(b []byte) error { + return errMustNotCall + })}), + want: addr(struct{ X unmarshalTextFunc }{nil}), + }, { + name: jsontest.Name("Methods/IP"), + inBuf: `"192.168.0.100"`, + inVal: new(net.IP), + want: addr(net.IPv4(192, 168, 0, 100)), + }, { + // NOTE: Fixes https://go.dev/issue/46516. + name: jsontest.Name("Methods/Anonymous"), + inBuf: `{"X":"hello"}`, + inVal: new(struct{ X struct{ allMethods } }), + want: addr(struct{ X struct{ allMethods } }{X: struct{ allMethods }{allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}}), + }, { + // NOTE: Fixes https://go.dev/issue/22967. + name: jsontest.Name("Methods/Addressable"), + inBuf: `{"V":"hello","M":{"K":"hello"},"I":"hello"}`, + inVal: addr(struct { + V allMethods + M map[string]allMethods + I any + }{ + I: allMethods{}, // need to initialize with concrete value + }), + want: addr(struct { + V allMethods + M map[string]allMethods + I any + }{ + V: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}, + M: map[string]allMethods{"K": {method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}}, + I: allMethods{method: "UnmarshalJSONFrom", value: []byte(`"hello"`)}, + }), + }, { + // NOTE: Fixes https://go.dev/issue/29732. + name: jsontest.Name("Methods/MapKey/JSONv2"), + inBuf: `{"k1":"v1b","k2":"v2"}`, + inVal: addr(map[structMethodJSONv2]string{{"k1"}: "v1a", {"k3"}: "v3"}), + want: addr(map[structMethodJSONv2]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), + }, { + // NOTE: Fixes https://go.dev/issue/29732. + name: jsontest.Name("Methods/MapKey/JSONv1"), + inBuf: `{"k1":"v1b","k2":"v2"}`, + inVal: addr(map[structMethodJSONv1]string{{"k1"}: "v1a", {"k3"}: "v3"}), + want: addr(map[structMethodJSONv1]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), + }, { + name: jsontest.Name("Methods/MapKey/Text"), + inBuf: `{"k1":"v1b","k2":"v2"}`, + inVal: addr(map[structMethodText]string{{"k1"}: "v1a", {"k3"}: "v3"}), + want: addr(map[structMethodText]string{{"k1"}: "v1b", {"k2"}: "v2", {"k3"}: "v3"}), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/Error"), + inBuf: `{}`, + inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { + return errSomeError + })), + wantErr: EU(errSomeError).withType(0, T[unmarshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/TooFew"), + inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { + return nil // do nothing + })), + wantErr: EU(errNonSingularValue).withType(0, T[unmarshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/TooMany"), + inBuf: `{}{}`, + inVal: addr(unmarshalJSONv2Func(func(dec *jsontext.Decoder) error { + dec.ReadValue() + dec.ReadValue() + return nil + })), + wantErr: EU(errNonSingularValue).withPos(`{}`, "").withType(0, T[unmarshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv2/SkipFunc"), + inBuf: `{}`, + inVal: addr(unmarshalJSONv2Func(func(*jsontext.Decoder) error { + return SkipFunc + })), + wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).withType(0, T[unmarshalJSONv2Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv1/Error"), + inBuf: `{}`, + inVal: addr(unmarshalJSONv1Func(func([]byte) error { + return errSomeError + })), + wantErr: EU(errSomeError).withType('{', T[unmarshalJSONv1Func]()), + }, { + name: jsontest.Name("Methods/Invalid/JSONv1/SkipFunc"), + inBuf: `{}`, + inVal: addr(unmarshalJSONv1Func(func([]byte) error { + return SkipFunc + })), + wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).withType('{', T[unmarshalJSONv1Func]()), + }, { + name: jsontest.Name("Methods/Invalid/Text/Error"), + inBuf: `"value"`, + inVal: addr(unmarshalTextFunc(func([]byte) error { + return errSomeError + })), + wantErr: EU(errSomeError).withType('"', T[unmarshalTextFunc]()), + }, { + name: jsontest.Name("Methods/Invalid/Text/Syntax"), + inBuf: `{}`, + inVal: addr(unmarshalTextFunc(func([]byte) error { + panic("should not be called") + })), + wantErr: EU(errNonStringValue).withType('{', T[unmarshalTextFunc]()), + }, { + name: jsontest.Name("Methods/Invalid/Text/SkipFunc"), + inBuf: `"value"`, + inVal: addr(unmarshalTextFunc(func([]byte) error { + return SkipFunc + })), + wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal method")).withType('"', T[unmarshalTextFunc]()), + }, { + name: jsontest.Name("Functions/String/V1"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { + if string(b) != `""` { + return fmt.Errorf("got %s, want %s", b, `""`) + } + *v = "called" + return nil + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Functions/String/Empty"), + opts: []Options{WithUnmarshalers(nil)}, + inBuf: `"hello"`, + inVal: addr(""), + want: addr("hello"), + }, { + name: jsontest.Name("Functions/NamedString/V1/NoMatch"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *namedString) error { + panic("should not be called") + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + }, { + name: jsontest.Name("Functions/NamedString/V1/Match"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *namedString) error { + if string(b) != `""` { + return fmt.Errorf("got %s, want %s", b, `""`) + } + *v = "called" + return nil + })), + }, + inBuf: `""`, + inVal: addr(namedString("")), + want: addr(namedString("called")), + }, { + name: jsontest.Name("Functions/String/V2"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + switch b, err := dec.ReadValue(); { + case err != nil: + return err + case string(b) != `""`: + return fmt.Errorf("got %s, want %s", b, `""`) + } + *v = "called" + return nil + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Functions/NamedString/V2/NoMatch"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *namedString) error { + panic("should not be called") + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + }, { + name: jsontest.Name("Functions/NamedString/V2/Match"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *namedString) error { + switch t, err := dec.ReadToken(); { + case err != nil: + return err + case t.String() != ``: + return fmt.Errorf("got %q, want %q", t, ``) + } + *v = "called" + return nil + })), + }, + inBuf: `""`, + inVal: addr(namedString("")), + want: addr(namedString("called")), + }, { + name: jsontest.Name("Functions/String/Empty1/NoMatch"), + opts: []Options{ + WithUnmarshalers(new(Unmarshalers)), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + }, { + name: jsontest.Name("Functions/String/Empty2/NoMatch"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers()), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + }, { + name: jsontest.Name("Functions/String/V1/DirectError"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func([]byte, *string) error { + return errSomeError + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + wantErr: EU(errSomeError).withType('"', reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/String/V1/SkipError"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func([]byte, *string) error { + return SkipFunc + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + wantErr: EU(wrapSkipFunc(SkipFunc, "unmarshal function of type func([]byte, T) error")).withType('"', reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/String/V2/DirectError"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + return errSomeError + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + wantErr: EU(errSomeError).withType(0, reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/String/V2/TooFew"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + return nil + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + wantErr: EU(errNonSingularValue).withType(0, reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/String/V2/TooMany"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + if _, err := dec.ReadValue(); err != nil { + return err + } + if _, err := dec.ReadValue(); err != nil { + return err + } + return nil + })), + }, + inBuf: `{"X":["",""]}`, + inVal: addr(struct{ X []string }{}), + want: addr(struct{ X []string }{[]string{""}}), + wantErr: EU(errNonSingularValue).withPos(`{"X":["",`, "/X").withType(0, reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/String/V2/Skipped"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + return SkipFunc + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + }, { + name: jsontest.Name("Functions/String/V2/ProcessBeforeSkip"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + if _, err := dec.ReadValue(); err != nil { + return err + } + return SkipFunc + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + wantErr: EU(errSkipMutation).withType(0, reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/String/V2/WrappedSkipError"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + return fmt.Errorf("wrap: %w", SkipFunc) + })), + }, + inBuf: `""`, + inVal: addr(""), + want: addr(""), + wantErr: EU(fmt.Errorf("wrap: %w", SkipFunc)).withType(0, reflect.PointerTo(stringType)), + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V1"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *nocaseString) error { + if string(b) != `"hello"` { + return fmt.Errorf("got %s, want %s", b, `"hello"`) + } + *v = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[nocaseString]string{}), + want: addr(map[nocaseString]string{"called": "world"}), + }, { + name: jsontest.Name("Functions/Map/Key/TextMarshaler/V1"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v encoding.TextMarshaler) error { + if string(b) != `"hello"` { + return fmt.Errorf("got %s, want %s", b, `"hello"`) + } + *v.(*nocaseString) = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[nocaseString]string{}), + want: addr(map[nocaseString]string{"called": "world"}), + }, { + name: jsontest.Name("Functions/Map/Key/NoCaseString/V2"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *nocaseString) error { + switch t, err := dec.ReadToken(); { + case err != nil: + return err + case t.String() != "hello": + return fmt.Errorf("got %q, want %q", t, "hello") + } + *v = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[nocaseString]string{}), + want: addr(map[nocaseString]string{"called": "world"}), + }, { + name: jsontest.Name("Functions/Map/Key/TextMarshaler/V2"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v encoding.TextMarshaler) error { + switch b, err := dec.ReadValue(); { + case err != nil: + return err + case string(b) != `"hello"`: + return fmt.Errorf("got %s, want %s", b, `"hello"`) + } + *v.(*nocaseString) = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[nocaseString]string{}), + want: addr(map[nocaseString]string{"called": "world"}), + }, { + name: jsontest.Name("Functions/Map/Key/String/V1/DuplicateName"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + if _, err := dec.ReadValue(); err != nil { + return err + } + xd := export.Decoder(dec) + *v = fmt.Sprintf("%d-%d", len(xd.Tokens.Stack), xd.Tokens.Last.Length()) + return nil + })), + }, + inBuf: `{"name":"value","name":"value"}`, + inVal: addr(map[string]string{}), + want: addr(map[string]string{"1-1": "1-2"}), + wantErr: newDuplicateNameError("", []byte(`"name"`), len64(`{"name":"value",`)), + }, { + name: jsontest.Name("Functions/Map/Value/NoCaseString/V1"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *nocaseString) error { + if string(b) != `"world"` { + return fmt.Errorf("got %s, want %s", b, `"world"`) + } + *v = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[string]nocaseString{}), + want: addr(map[string]nocaseString{"hello": "called"}), + }, { + name: jsontest.Name("Functions/Map/Value/TextMarshaler/V1"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v encoding.TextMarshaler) error { + if string(b) != `"world"` { + return fmt.Errorf("got %s, want %s", b, `"world"`) + } + *v.(*nocaseString) = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[string]nocaseString{}), + want: addr(map[string]nocaseString{"hello": "called"}), + }, { + name: jsontest.Name("Functions/Map/Value/NoCaseString/V2"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *nocaseString) error { + switch t, err := dec.ReadToken(); { + case err != nil: + return err + case t.String() != "world": + return fmt.Errorf("got %q, want %q", t, "world") + } + *v = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[string]nocaseString{}), + want: addr(map[string]nocaseString{"hello": "called"}), + }, { + name: jsontest.Name("Functions/Map/Value/TextMarshaler/V2"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v encoding.TextMarshaler) error { + switch b, err := dec.ReadValue(); { + case err != nil: + return err + case string(b) != `"world"`: + return fmt.Errorf("got %s, want %s", b, `"world"`) + } + *v.(*nocaseString) = "called" + return nil + })), + }, + inBuf: `{"hello":"world"}`, + inVal: addr(map[string]nocaseString{}), + want: addr(map[string]nocaseString{"hello": "called"}), + }, { + name: jsontest.Name("Funtions/Struct/Fields"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFunc(func(b []byte, v *bool) error { + if string(b) != `"called1"` { + return fmt.Errorf("got %s, want %s", b, `"called1"`) + } + *v = true + return nil + }), + UnmarshalFunc(func(b []byte, v *string) error { + if string(b) != `"called2"` { + return fmt.Errorf("got %s, want %s", b, `"called2"`) + } + *v = "called2" + return nil + }), + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *[]byte) error { + switch t, err := dec.ReadToken(); { + case err != nil: + return err + case t.String() != "called3": + return fmt.Errorf("got %q, want %q", t, "called3") + } + *v = []byte("called3") + return nil + }), + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *int64) error { + switch b, err := dec.ReadValue(); { + case err != nil: + return err + case string(b) != `"called4"`: + return fmt.Errorf("got %s, want %s", b, `"called4"`) + } + *v = 123 + return nil + }), + )), + }, + inBuf: `{"Bool":"called1","String":"called2","Bytes":"called3","Int":"called4","Uint":456,"Float":789}`, + inVal: addr(structScalars{}), + want: addr(structScalars{Bool: true, String: "called2", Bytes: []byte("called3"), Int: 123, Uint: 456, Float: 789}), + }, { + name: jsontest.Name("Functions/Struct/Inlined"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFunc(func([]byte, *structInlinedL1) error { + panic("should not be called") + }), + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *StructEmbed2) error { + panic("should not be called") + }), + )), + }, + inBuf: `{"E":"E3","F":"F3","G":"G3","A":"A1","B":"B1","D":"D2"}`, + inVal: new(structInlined), + want: addr(structInlined{ + X: structInlinedL1{ + X: &structInlinedL2{A: "A1", B: "B1" /* C: "C1" */}, + StructEmbed1: StructEmbed1{ /* C: "C2" */ D: "D2" /* E: "E2" */}, + }, + StructEmbed2: &StructEmbed2{E: "E3", F: "F3", G: "G3"}, + }), + }, { + name: jsontest.Name("Functions/Slice/Elem"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { + *v = strings.Trim(strings.ToUpper(string(b)), `"`) + return nil + })), + }, + inBuf: `["hello","World"]`, + inVal: addr([]string{}), + want: addr([]string{"HELLO", "WORLD"}), + }, { + name: jsontest.Name("Functions/Array/Elem"), + opts: []Options{ + WithUnmarshalers(UnmarshalFunc(func(b []byte, v *string) error { + *v = strings.Trim(strings.ToUpper(string(b)), `"`) + return nil + })), + }, + inBuf: `["hello","World"]`, + inVal: addr([2]string{}), + want: addr([2]string{"HELLO", "WORLD"}), + }, { + name: jsontest.Name("Functions/Pointer/Nil"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + t, err := dec.ReadToken() + *v = strings.ToUpper(t.String()) + return err + })), + }, + inBuf: `{"X":"hello"}`, + inVal: addr(struct{ X *string }{nil}), + want: addr(struct{ X *string }{addr("HELLO")}), + }, { + name: jsontest.Name("Functions/Pointer/NonNil"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + t, err := dec.ReadToken() + *v = strings.ToUpper(t.String()) + return err + })), + }, + inBuf: `{"X":"hello"}`, + inVal: addr(struct{ X *string }{addr("")}), + want: addr(struct{ X *string }{addr("HELLO")}), + }, { + name: jsontest.Name("Functions/Interface/Nil"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v fmt.Stringer) error { + panic("should not be called") + })), + }, + inBuf: `{"X":"hello"}`, + inVal: addr(struct{ X fmt.Stringer }{nil}), + want: addr(struct{ X fmt.Stringer }{nil}), + wantErr: EU(errNilInterface).withPos(`{"X":`, "/X").withType(0, T[fmt.Stringer]()), + }, { + name: jsontest.Name("Functions/Interface/NetIP"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { + *v = net.IP{} + return SkipFunc + })), + }, + inBuf: `{"X":"1.1.1.1"}`, + inVal: addr(struct{ X fmt.Stringer }{nil}), + want: addr(struct{ X fmt.Stringer }{net.IPv4(1, 1, 1, 1)}), + }, { + name: jsontest.Name("Functions/Interface/NewPointerNetIP"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { + *v = new(net.IP) + return SkipFunc + })), + }, + inBuf: `{"X":"1.1.1.1"}`, + inVal: addr(struct{ X fmt.Stringer }{nil}), + want: addr(struct{ X fmt.Stringer }{addr(net.IPv4(1, 1, 1, 1))}), + }, { + name: jsontest.Name("Functions/Interface/NilPointerNetIP"), + opts: []Options{ + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { + *v = (*net.IP)(nil) + return SkipFunc + })), + }, + inBuf: `{"X":"1.1.1.1"}`, + inVal: addr(struct{ X fmt.Stringer }{nil}), + want: addr(struct{ X fmt.Stringer }{addr(net.IPv4(1, 1, 1, 1))}), + }, { + name: jsontest.Name("Functions/Interface/NilPointerNetIP/Override"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *fmt.Stringer) error { + *v = (*net.IP)(nil) + return SkipFunc + }), + UnmarshalFunc(func(b []byte, v *net.IP) error { + b = bytes.ReplaceAll(b, []byte(`1`), []byte(`8`)) + return v.UnmarshalText(bytes.Trim(b, `"`)) + }), + )), + }, + inBuf: `{"X":"1.1.1.1"}`, + inVal: addr(struct{ X fmt.Stringer }{nil}), + want: addr(struct{ X fmt.Stringer }{addr(net.IPv4(8, 8, 8, 8))}), + }, { + name: jsontest.Name("Functions/Interface/Any"), + inBuf: `[null,{},{},{},{},{},{},{},{},{},{},{},{},"LAST"]`, + inVal: addr([...]any{ + nil, // nil + valueStringer{}, // T + (*valueStringer)(nil), // *T + addr(valueStringer{}), // *T + (**valueStringer)(nil), // **T + addr((*valueStringer)(nil)), // **T + addr(addr(valueStringer{})), // **T + pointerStringer{}, // T + (*pointerStringer)(nil), // *T + addr(pointerStringer{}), // *T + (**pointerStringer)(nil), // **T + addr((*pointerStringer)(nil)), // **T + addr(addr(pointerStringer{})), // **T + "LAST", + }), + opts: []Options{ + WithUnmarshalers(func() *Unmarshalers { + type P struct { + D int + N int64 + } + type PV struct { + P P + V any + } + + var lastChecks []func() error + checkLast := func() error { + for _, fn := range lastChecks { + if err := fn(); err != nil { + return err + } + } + return SkipFunc + } + makeValueChecker := func(name string, want []PV) func(d *jsontext.Decoder, v any) error { + checkNext := func(d *jsontext.Decoder, v any) error { + xd := export.Decoder(d) + p := P{len(xd.Tokens.Stack), xd.Tokens.Last.Length()} + rv := reflect.ValueOf(v) + pv := PV{p, v} + switch { + case len(want) == 0: + return fmt.Errorf("%s: %v: got more values than expected", name, p) + case !rv.IsValid() || rv.Kind() != reflect.Pointer || rv.IsNil(): + return fmt.Errorf("%s: %v: got %#v, want non-nil pointer type", name, p, v) + case !reflect.DeepEqual(pv, want[0]): + return fmt.Errorf("%s:\n\tgot %#v\n\twant %#v", name, pv, want[0]) + default: + want = want[1:] + return SkipFunc + } + } + lastChecks = append(lastChecks, func() error { + if len(want) > 0 { + return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) + } + return nil + }) + return checkNext + } + makePositionChecker := func(name string, want []P) func(d *jsontext.Decoder, v any) error { + checkNext := func(d *jsontext.Decoder, v any) error { + xd := export.Decoder(d) + p := P{len(xd.Tokens.Stack), xd.Tokens.Last.Length()} + switch { + case len(want) == 0: + return fmt.Errorf("%s: %v: got more values than wanted", name, p) + case p != want[0]: + return fmt.Errorf("%s: got %v, want %v", name, p, want[0]) + default: + want = want[1:] + return SkipFunc + } + } + lastChecks = append(lastChecks, func() error { + if len(want) > 0 { + return fmt.Errorf("%s: did not get enough values, want %d more", name, len(want)) + } + return nil + }) + return checkNext + } + + // In contrast to marshal, unmarshal automatically allocates for + // nil pointers, which causes unmarshal to visit more values. + wantAny := []PV{ + {P{1, 0}, addr(any(nil))}, + {P{1, 1}, addr(any(valueStringer{}))}, + {P{1, 1}, addr(valueStringer{})}, + {P{1, 2}, addr(any((*valueStringer)(nil)))}, + {P{1, 2}, addr((*valueStringer)(nil))}, + {P{1, 2}, addr(valueStringer{})}, + {P{1, 3}, addr(any(addr(valueStringer{})))}, + {P{1, 3}, addr(addr(valueStringer{}))}, + {P{1, 3}, addr(valueStringer{})}, + {P{1, 4}, addr(any((**valueStringer)(nil)))}, + {P{1, 4}, addr((**valueStringer)(nil))}, + {P{1, 4}, addr((*valueStringer)(nil))}, + {P{1, 4}, addr(valueStringer{})}, + {P{1, 5}, addr(any(addr((*valueStringer)(nil))))}, + {P{1, 5}, addr(addr((*valueStringer)(nil)))}, + {P{1, 5}, addr((*valueStringer)(nil))}, + {P{1, 5}, addr(valueStringer{})}, + {P{1, 6}, addr(any(addr(addr(valueStringer{}))))}, + {P{1, 6}, addr(addr(addr(valueStringer{})))}, + {P{1, 6}, addr(addr(valueStringer{}))}, + {P{1, 6}, addr(valueStringer{})}, + {P{1, 7}, addr(any(pointerStringer{}))}, + {P{1, 7}, addr(pointerStringer{})}, + {P{1, 8}, addr(any((*pointerStringer)(nil)))}, + {P{1, 8}, addr((*pointerStringer)(nil))}, + {P{1, 8}, addr(pointerStringer{})}, + {P{1, 9}, addr(any(addr(pointerStringer{})))}, + {P{1, 9}, addr(addr(pointerStringer{}))}, + {P{1, 9}, addr(pointerStringer{})}, + {P{1, 10}, addr(any((**pointerStringer)(nil)))}, + {P{1, 10}, addr((**pointerStringer)(nil))}, + {P{1, 10}, addr((*pointerStringer)(nil))}, + {P{1, 10}, addr(pointerStringer{})}, + {P{1, 11}, addr(any(addr((*pointerStringer)(nil))))}, + {P{1, 11}, addr(addr((*pointerStringer)(nil)))}, + {P{1, 11}, addr((*pointerStringer)(nil))}, + {P{1, 11}, addr(pointerStringer{})}, + {P{1, 12}, addr(any(addr(addr(pointerStringer{}))))}, + {P{1, 12}, addr(addr(addr(pointerStringer{})))}, + {P{1, 12}, addr(addr(pointerStringer{}))}, + {P{1, 12}, addr(pointerStringer{})}, + {P{1, 13}, addr(any("LAST"))}, + {P{1, 13}, addr("LAST")}, + } + checkAny := makeValueChecker("any", wantAny) + anyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v any) error { + return checkAny(dec, v) + }) + + var wantPointerAny []PV + for _, v := range wantAny { + if _, ok := v.V.(*any); ok { + wantPointerAny = append(wantPointerAny, v) + } + } + checkPointerAny := makeValueChecker("*any", wantPointerAny) + pointerAnyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *any) error { + return checkPointerAny(dec, v) + }) + + checkNamedAny := makeValueChecker("namedAny", wantAny) + namedAnyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v namedAny) error { + return checkNamedAny(dec, v) + }) + + checkPointerNamedAny := makeValueChecker("*namedAny", nil) + pointerNamedAnyUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *namedAny) error { + return checkPointerNamedAny(dec, v) + }) + + type stringer = fmt.Stringer + var wantStringer []PV + for _, v := range wantAny { + if _, ok := v.V.(stringer); ok { + wantStringer = append(wantStringer, v) + } + } + checkStringer := makeValueChecker("stringer", wantStringer) + stringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v stringer) error { + return checkStringer(dec, v) + }) + + checkPointerStringer := makeValueChecker("*stringer", nil) + pointerStringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *stringer) error { + return checkPointerStringer(dec, v) + }) + + wantValueStringer := []P{{1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}} + checkPointerValueStringer := makePositionChecker("*valueStringer", wantValueStringer) + pointerValueStringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *valueStringer) error { + return checkPointerValueStringer(dec, v) + }) + + wantPointerStringer := []P{{1, 7}, {1, 8}, {1, 9}, {1, 10}, {1, 11}, {1, 12}} + checkPointerPointerStringer := makePositionChecker("*pointerStringer", wantPointerStringer) + pointerPointerStringerUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *pointerStringer) error { + return checkPointerPointerStringer(dec, v) + }) + + lastUnmarshaler := UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + return checkLast() + }) + + return JoinUnmarshalers( + // This is just like unmarshaling into a Go array, + // but avoids zeroing the element before calling unmarshal. + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *[14]any) error { + if _, err := dec.ReadToken(); err != nil { + return err + } + for i := range len(*v) { + if err := UnmarshalDecode(dec, &(*v)[i]); err != nil { + return err + } + } + if _, err := dec.ReadToken(); err != nil { + return err + } + return nil + }), + + anyUnmarshaler, + pointerAnyUnmarshaler, + namedAnyUnmarshaler, + pointerNamedAnyUnmarshaler, // never called + stringerUnmarshaler, + pointerStringerUnmarshaler, // never called + pointerValueStringerUnmarshaler, + pointerPointerStringerUnmarshaler, + lastUnmarshaler, + ) + }()), + }, + }, { + name: jsontest.Name("Functions/Precedence/V1First"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFunc(func(b []byte, v *string) error { + if string(b) != `"called"` { + return fmt.Errorf("got %s, want %s", b, `"called"`) + } + *v = "called" + return nil + }), + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + panic("should not be called") + }), + )), + }, + inBuf: `"called"`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Functions/Precedence/V2First"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + switch t, err := dec.ReadToken(); { + case err != nil: + return err + case t.String() != "called": + return fmt.Errorf("got %q, want %q", t, "called") + } + *v = "called" + return nil + }), + UnmarshalFunc(func([]byte, *string) error { + panic("should not be called") + }), + )), + }, + inBuf: `"called"`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Functions/Precedence/V2Skipped"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFromFunc(func(dec *jsontext.Decoder, v *string) error { + return SkipFunc + }), + UnmarshalFunc(func(b []byte, v *string) error { + if string(b) != `"called"` { + return fmt.Errorf("got %s, want %s", b, `"called"`) + } + *v = "called" + return nil + }), + )), + }, + inBuf: `"called"`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Functions/Precedence/NestedFirst"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + JoinUnmarshalers( + UnmarshalFunc(func(b []byte, v *string) error { + if string(b) != `"called"` { + return fmt.Errorf("got %s, want %s", b, `"called"`) + } + *v = "called" + return nil + }), + ), + UnmarshalFunc(func([]byte, *string) error { + panic("should not be called") + }), + )), + }, + inBuf: `"called"`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Functions/Precedence/NestedLast"), + opts: []Options{ + WithUnmarshalers(JoinUnmarshalers( + UnmarshalFunc(func(b []byte, v *string) error { + if string(b) != `"called"` { + return fmt.Errorf("got %s, want %s", b, `"called"`) + } + *v = "called" + return nil + }), + JoinUnmarshalers( + UnmarshalFunc(func([]byte, *string) error { + panic("should not be called") + }), + ), + )), + }, + inBuf: `"called"`, + inVal: addr(""), + want: addr("called"), + }, { + name: jsontest.Name("Duration/Null"), + inBuf: `{"D1":null,"D2":null}`, + inVal: addr(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{1, 1}), + want: addr(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{0, 0}), + }, { + name: jsontest.Name("Duration/Zero"), + inBuf: `{"D1":"0s","D2":0}`, + inVal: addr(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{1, 1}), + want: addr(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{0, 0}), + }, { + name: jsontest.Name("Duration/Positive"), + inBuf: `{"D1":"34293h33m9.123456789s","D2":123456789123456789}`, + inVal: new(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }), + want: addr(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{ + 123456789123456789, + 123456789123456789, + }), + }, { + name: jsontest.Name("Duration/Negative"), + inBuf: `{"D1":"-34293h33m9.123456789s","D2":-123456789123456789}`, + inVal: new(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }), + want: addr(struct { + D1 time.Duration + D2 time.Duration `json:",format:nano"` + }{ + -123456789123456789, + -123456789123456789, + }), + }, { + name: jsontest.Name("Duration/Nanos/String"), + inBuf: `{"D":"12345"}`, + inVal: addr(struct { + D time.Duration `json:",string,format:nano"` + }{1}), + want: addr(struct { + D time.Duration `json:",string,format:nano"` + }{12345}), + }, { + name: jsontest.Name("Duration/Nanos/String/Invalid"), + inBuf: `{"D":"+12345"}`, + inVal: addr(struct { + D time.Duration `json:",string,format:nano"` + }{1}), + want: addr(struct { + D time.Duration `json:",string,format:nano"` + }{1}), + wantErr: EU(fmt.Errorf(`invalid duration "+12345": %w`, strconv.ErrSyntax)).withPos(`{"D":`, "/D").withType('"', timeDurationType), + }, { + name: jsontest.Name("Duration/Nanos/Mismatch"), + inBuf: `{"D":"34293h33m9.123456789s"}`, + inVal: addr(struct { + D time.Duration `json:",format:nano"` + }{1}), + want: addr(struct { + D time.Duration `json:",format:nano"` + }{1}), + wantErr: EU(nil).withPos(`{"D":`, "/D").withType('"', timeDurationType), + }, { + name: jsontest.Name("Duration/Nanos"), + inBuf: `{"D":1.324}`, + inVal: addr(struct { + D time.Duration `json:",format:nano"` + }{-1}), + want: addr(struct { + D time.Duration `json:",format:nano"` + }{1}), + }, { + name: jsontest.Name("Duration/String/Mismatch"), + inBuf: `{"D":-123456789123456789}`, + inVal: addr(struct { + D time.Duration + }{1}), + want: addr(struct { + D time.Duration + }{1}), + wantErr: EU(nil).withPos(`{"D":`, "/D").withType('0', timeDurationType), + }, { + name: jsontest.Name("Duration/String/Invalid"), + inBuf: `{"D":"5minkutes"}`, + inVal: addr(struct { + D time.Duration + }{1}), + want: addr(struct { + D time.Duration + }{1}), + wantErr: EU(func() error { + _, err := time.ParseDuration("5minkutes") + return err + }()).withPos(`{"D":`, "/D").withType('"', timeDurationType), + }, { + name: jsontest.Name("Duration/Syntax/Invalid"), + inBuf: `{"D":x}`, + inVal: addr(struct { + D time.Duration + }{1}), + want: addr(struct { + D time.Duration + }{1}), + wantErr: newInvalidCharacterError("x", "at start of value", len64(`{"D":`), "/D"), + }, { + name: jsontest.Name("Duration/Format/Invalid"), + inBuf: `{"D":"0s"}`, + inVal: addr(struct { + D time.Duration `json:",format:invalid"` + }{1}), + want: addr(struct { + D time.Duration `json:",format:invalid"` + }{1}), + wantErr: EU(errInvalidFormatFlag).withPos(`{"D":`, "/D").withType(0, timeDurationType), + }, { + name: jsontest.Name("Duration/Format/Legacy"), + inBuf: `{"D1":45296078090012,"D2":"12h34m56.078090012s"}`, + opts: []Options{jsonflags.FormatTimeWithLegacySemantics | 1}, + inVal: new(structDurationFormat), + want: addr(structDurationFormat{ + D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + }), + }, { + name: jsontest.Name("Duration/MapKey"), + inBuf: `{"1s":""}`, + inVal: new(map[time.Duration]string), + want: addr(map[time.Duration]string{time.Second: ""}), + }, { + name: jsontest.Name("Duration/MapKey/Legacy"), + opts: []Options{jsonflags.FormatTimeWithLegacySemantics | 1}, + inBuf: `{"1000000000":""}`, + inVal: new(map[time.Duration]string), + want: addr(map[time.Duration]string{time.Second: ""}), + }, { + name: jsontest.Name("Duration/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `"1s"`, + inVal: addr(time.Duration(0)), + want: addr(time.Second), + }, { + name: jsontest.Name("Time/Zero"), + inBuf: `{"T1":"0001-01-01T00:00:00Z","T2":"01 Jan 01 00:00 UTC","T3":"0001-01-01","T4":"0001-01-01T00:00:00Z","T5":"0001-01-01T00:00:00Z"}`, + inVal: new(struct { + T1 time.Time + T2 time.Time `json:",format:RFC822"` + T3 time.Time `json:",format:'2006-01-02'"` + T4 time.Time `json:",omitzero"` + T5 time.Time `json:",omitempty"` + }), + want: addr(struct { + T1 time.Time + T2 time.Time `json:",format:RFC822"` + T3 time.Time `json:",format:'2006-01-02'"` + T4 time.Time `json:",omitzero"` + T5 time.Time `json:",omitempty"` + }{ + mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"), + mustParseTime(time.RFC822, "01 Jan 01 00:00 UTC"), + mustParseTime("2006-01-02", "0001-01-01"), + mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"), + mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"), + }), + }, { + name: jsontest.Name("Time/Format"), + inBuf: `{ + "T1": "1234-01-02T03:04:05.000000006Z", + "T2": "Mon Jan 2 03:04:05 1234", + "T3": "Mon Jan 2 03:04:05 UTC 1234", + "T4": "Mon Jan 02 03:04:05 +0000 1234", + "T5": "02 Jan 34 03:04 UTC", + "T6": "02 Jan 34 03:04 +0000", + "T7": "Monday, 02-Jan-34 03:04:05 UTC", + "T8": "Mon, 02 Jan 1234 03:04:05 UTC", + "T9": "Mon, 02 Jan 1234 03:04:05 +0000", + "T10": "1234-01-02T03:04:05Z", + "T11": "1234-01-02T03:04:05.000000006Z", + "T12": "3:04AM", + "T13": "Jan 2 03:04:05", + "T14": "Jan 2 03:04:05.000", + "T15": "Jan 2 03:04:05.000000", + "T16": "Jan 2 03:04:05.000000006", + "T17": "1234-01-02 03:04:05", + "T18": "1234-01-02", + "T19": "03:04:05", + "T20": "1234-01-02", + "T21": "\"weird\"1234", + "T22": -23225777754.999999994, + "T23": "-23225777754.999999994", + "T24": -23225777754999.999994, + "T25": "-23225777754999.999994", + "T26": -23225777754999999.994, + "T27": "-23225777754999999.994", + "T28": -23225777754999999994, + "T29": "-23225777754999999994" + }`, + inVal: new(structTimeFormat), + want: addr(structTimeFormat{ + mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), + mustParseTime(time.ANSIC, "Mon Jan 2 03:04:05 1234"), + mustParseTime(time.UnixDate, "Mon Jan 2 03:04:05 UTC 1234"), + mustParseTime(time.RubyDate, "Mon Jan 02 03:04:05 +0000 1234"), + mustParseTime(time.RFC822, "02 Jan 34 03:04 UTC"), + mustParseTime(time.RFC822Z, "02 Jan 34 03:04 +0000"), + mustParseTime(time.RFC850, "Monday, 02-Jan-34 03:04:05 UTC"), + mustParseTime(time.RFC1123, "Mon, 02 Jan 1234 03:04:05 UTC"), + mustParseTime(time.RFC1123Z, "Mon, 02 Jan 1234 03:04:05 +0000"), + mustParseTime(time.RFC3339, "1234-01-02T03:04:05Z"), + mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), + mustParseTime(time.Kitchen, "3:04AM"), + mustParseTime(time.Stamp, "Jan 2 03:04:05"), + mustParseTime(time.StampMilli, "Jan 2 03:04:05.000"), + mustParseTime(time.StampMicro, "Jan 2 03:04:05.000000"), + mustParseTime(time.StampNano, "Jan 2 03:04:05.000000006"), + mustParseTime(time.DateTime, "1234-01-02 03:04:05"), + mustParseTime(time.DateOnly, "1234-01-02"), + mustParseTime(time.TimeOnly, "03:04:05"), + mustParseTime("2006-01-02", "1234-01-02"), + mustParseTime(`\"weird\"2006`, `\"weird\"1234`), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + }), + }, { + name: jsontest.Name("Time/Format/UnixString/InvalidNumber"), + inBuf: `{ + "T23": -23225777754.999999994, + "T25": -23225777754999.999994, + "T27": -23225777754999999.994, + "T29": -23225777754999999994 + }`, + inVal: new(structTimeFormat), + want: new(structTimeFormat), + wantErr: EU(nil).withPos(`{`+"\n\t\t\t"+`"T23": `, "/T23").withType('0', timeTimeType), + }, { + name: jsontest.Name("Time/Format/UnixString/InvalidString"), + inBuf: `{ + "T22": "-23225777754.999999994", + "T24": "-23225777754999.999994", + "T26": "-23225777754999999.994", + "T28": "-23225777754999999994" + }`, + inVal: new(structTimeFormat), + want: new(structTimeFormat), + wantErr: EU(nil).withPos(`{`+"\n\t\t\t"+`"T22": `, "/T22").withType('"', timeTimeType), + }, { + name: jsontest.Name("Time/Format/Null"), + inBuf: `{"T1":null,"T2":null,"T3":null,"T4":null,"T5":null,"T6":null,"T7":null,"T8":null,"T9":null,"T10":null,"T11":null,"T12":null,"T13":null,"T14":null,"T15":null,"T16":null,"T17":null,"T18":null,"T19":null,"T20":null,"T21":null,"T22":null,"T23":null,"T24":null,"T25":null,"T26":null,"T27":null,"T28":null,"T29":null}`, + inVal: addr(structTimeFormat{ + mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), + mustParseTime(time.ANSIC, "Mon Jan 2 03:04:05 1234"), + mustParseTime(time.UnixDate, "Mon Jan 2 03:04:05 UTC 1234"), + mustParseTime(time.RubyDate, "Mon Jan 02 03:04:05 +0000 1234"), + mustParseTime(time.RFC822, "02 Jan 34 03:04 UTC"), + mustParseTime(time.RFC822Z, "02 Jan 34 03:04 +0000"), + mustParseTime(time.RFC850, "Monday, 02-Jan-34 03:04:05 UTC"), + mustParseTime(time.RFC1123, "Mon, 02 Jan 1234 03:04:05 UTC"), + mustParseTime(time.RFC1123Z, "Mon, 02 Jan 1234 03:04:05 +0000"), + mustParseTime(time.RFC3339, "1234-01-02T03:04:05Z"), + mustParseTime(time.RFC3339Nano, "1234-01-02T03:04:05.000000006Z"), + mustParseTime(time.Kitchen, "3:04AM"), + mustParseTime(time.Stamp, "Jan 2 03:04:05"), + mustParseTime(time.StampMilli, "Jan 2 03:04:05.000"), + mustParseTime(time.StampMicro, "Jan 2 03:04:05.000000"), + mustParseTime(time.StampNano, "Jan 2 03:04:05.000000006"), + mustParseTime(time.DateTime, "1234-01-02 03:04:05"), + mustParseTime(time.DateOnly, "1234-01-02"), + mustParseTime(time.TimeOnly, "03:04:05"), + mustParseTime("2006-01-02", "1234-01-02"), + mustParseTime(`\"weird\"2006`, `\"weird\"1234`), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + time.Unix(-23225777755, 6).UTC(), + }), + want: new(structTimeFormat), + }, { + name: jsontest.Name("Time/RFC3339/Mismatch"), + inBuf: `{"T":1234}`, + inVal: new(struct { + T time.Time + }), + wantErr: EU(nil).withPos(`{"T":`, "/T").withType('0', timeTimeType), + }, { + name: jsontest.Name("Time/RFC3339/ParseError"), + inBuf: `{"T":"2021-09-29T12:44:52"}`, + inVal: new(struct { + T time.Time + }), + wantErr: EU(func() error { + _, err := time.Parse(time.RFC3339, "2021-09-29T12:44:52") + return err + }()).withPos(`{"T":`, "/T").withType('"', timeTimeType), + }, { + name: jsontest.Name("Time/Format/Invalid"), + inBuf: `{"T":""}`, + inVal: new(struct { + T time.Time `json:",format:UndefinedConstant"` + }), + wantErr: EU(errors.New(`invalid format flag "UndefinedConstant"`)).withPos(`{"T":`, "/T").withType(0, timeTimeType), + }, { + name: jsontest.Name("Time/Format/SingleDigitHour"), + inBuf: `{"T":"2000-01-01T1:12:34Z"}`, + inVal: new(struct{ T time.Time }), + wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T1:12:34Z", "15", "1", "")).withPos(`{"T":`, "/T").withType('"', timeTimeType), + }, { + name: jsontest.Name("Time/Format/SubsecondComma"), + inBuf: `{"T":"2000-01-01T00:00:00,000Z"}`, + inVal: new(struct{ T time.Time }), + wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T00:00:00,000Z", ".", ",", "")).withPos(`{"T":`, "/T").withType('"', timeTimeType), + }, { + name: jsontest.Name("Time/Format/TimezoneHourOverflow"), + inBuf: `{"T":"2000-01-01T00:00:00+24:00"}`, + inVal: new(struct{ T time.Time }), + wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T00:00:00+24:00", "Z07:00", "+24:00", ": timezone hour out of range")).withPos(`{"T":`, "/T").withType('"', timeTimeType), + }, { + name: jsontest.Name("Time/Format/TimezoneMinuteOverflow"), + inBuf: `{"T":"2000-01-01T00:00:00+00:60"}`, + inVal: new(struct{ T time.Time }), + wantErr: EU(newParseTimeError(time.RFC3339, "2000-01-01T00:00:00+00:60", "Z07:00", "+00:60", ": timezone minute out of range")).withPos(`{"T":`, "/T").withType('"', timeTimeType), + }, { + name: jsontest.Name("Time/Syntax/Invalid"), + inBuf: `{"T":x}`, + inVal: new(struct { + T time.Time + }), + wantErr: newInvalidCharacterError("x", "at start of value", len64(`{"T":`), "/T"), + }, { + name: jsontest.Name("Time/IgnoreInvalidFormat"), + opts: []Options{invalidFormatOption}, + inBuf: `"2000-01-01T00:00:00Z"`, + inVal: addr(time.Time{}), + want: addr(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)), + }} + + for _, tt := range tests { + t.Run(tt.name.Name, func(t *testing.T) { + got := tt.inVal + gotErr := Unmarshal([]byte(tt.inBuf), got, tt.opts...) + if !reflect.DeepEqual(got, tt.want) && tt.want != nil { + t.Errorf("%s: Unmarshal output mismatch:\ngot %v\nwant %v", tt.name.Where, got, tt.want) + } + if !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("%s: Unmarshal error mismatch:\ngot %v\nwant %v", tt.name.Where, gotErr, tt.wantErr) + } + }) + } +} + +func TestMarshalInvalidNamespace(t *testing.T) { + tests := []struct { + name jsontest.CaseName + val any + }{ + {jsontest.Name("Map"), map[string]string{"X": "\xde\xad\xbe\xef"}}, + {jsontest.Name("Struct"), struct{ X string }{"\xde\xad\xbe\xef"}}, + } + for _, tt := range tests { + t.Run(tt.name.Name, func(t *testing.T) { + enc := jsontext.NewEncoder(new(bytes.Buffer)) + if err := MarshalEncode(enc, tt.val); err == nil { + t.Fatalf("%s: MarshalEncode error is nil, want non-nil", tt.name.Where) + } + for _, tok := range []jsontext.Token{ + jsontext.Null, jsontext.String(""), jsontext.Int(0), jsontext.BeginObject, jsontext.EndObject, jsontext.BeginArray, jsontext.EndArray, + } { + if err := enc.WriteToken(tok); err == nil { + t.Fatalf("%s: WriteToken error is nil, want non-nil", tt.name.Where) + } + } + for _, val := range []string{`null`, `""`, `0`, `{}`, `[]`} { + if err := enc.WriteValue([]byte(val)); err == nil { + t.Fatalf("%s: WriteToken error is nil, want non-nil", tt.name.Where) + } + } + }) + } +} + +func TestUnmarshalInvalidNamespace(t *testing.T) { + tests := []struct { + name jsontest.CaseName + val any + }{ + {jsontest.Name("Map"), addr(map[string]int{})}, + {jsontest.Name("Struct"), addr(struct{ X int }{})}, + } + for _, tt := range tests { + t.Run(tt.name.Name, func(t *testing.T) { + dec := jsontext.NewDecoder(strings.NewReader(`{"X":""}`)) + if err := UnmarshalDecode(dec, tt.val); err == nil { + t.Fatalf("%s: UnmarshalDecode error is nil, want non-nil", tt.name.Where) + } + if _, err := dec.ReadToken(); err == nil { + t.Fatalf("%s: ReadToken error is nil, want non-nil", tt.name.Where) + } + if _, err := dec.ReadValue(); err == nil { + t.Fatalf("%s: ReadValue error is nil, want non-nil", tt.name.Where) + } + }) + } +} + +func TestUnmarshalReuse(t *testing.T) { + t.Run("Bytes", func(t *testing.T) { + in := make([]byte, 3) + want := &in[0] + if err := Unmarshal([]byte(`"AQID"`), &in); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + got := &in[0] + if got != want { + t.Errorf("input buffer was not reused") + } + }) + t.Run("Slices", func(t *testing.T) { + in := make([]int, 3) + want := &in[0] + if err := Unmarshal([]byte(`[0,1,2]`), &in); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + got := &in[0] + if got != want { + t.Errorf("input slice was not reused") + } + }) + t.Run("Maps", func(t *testing.T) { + in := make(map[string]string) + want := reflect.ValueOf(in).Pointer() + if err := Unmarshal([]byte(`{"key":"value"}`), &in); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + got := reflect.ValueOf(in).Pointer() + if got != want { + t.Errorf("input map was not reused") + } + }) + t.Run("Pointers", func(t *testing.T) { + in := addr(addr(addr("hello"))) + want := **in + if err := Unmarshal([]byte(`"goodbye"`), &in); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + got := **in + if got != want { + t.Errorf("input pointer was not reused") + } + }) +} + +type ReaderFunc func([]byte) (int, error) + +func (f ReaderFunc) Read(b []byte) (int, error) { return f(b) } + +type WriterFunc func([]byte) (int, error) + +func (f WriterFunc) Write(b []byte) (int, error) { return f(b) } + +func TestCoderBufferGrowth(t *testing.T) { + // The growth rate of the internal buffer should be exponential, + // but should not grow unbounded. + checkGrowth := func(ns []int) { + t.Helper() + var sumBytes, sumRates, numGrows float64 + prev := ns[0] + for i := 1; i < len(ns)-1; i++ { + n := ns[i] + if n != prev { + sumRates += float64(n) / float64(prev) + numGrows++ + prev = n + } + if n > 1<<20 { + t.Fatalf("single Read/Write too large: %d", n) + } + sumBytes += float64(n) + } + if mean := sumBytes / float64(len(ns)); mean < 1<<10 { + t.Fatalf("average Read/Write too small: %0.1f", mean) + } + switch mean := sumRates / numGrows; { + case mean < 1.25: + t.Fatalf("average growth rate too slow: %0.3f", mean) + case mean > 2.00: + t.Fatalf("average growth rate too fast: %0.3f", mean) + } + } + + // bb is identical to bytes.Buffer, + // but a different type to avoid any optimizations for bytes.Buffer. + bb := struct{ *bytes.Buffer }{new(bytes.Buffer)} + + var writeSizes []int + if err := MarshalWrite(WriterFunc(func(b []byte) (int, error) { + n, err := bb.Write(b) + writeSizes = append(writeSizes, n) + return n, err + }), make([]struct{}, 1e6)); err != nil { + t.Fatalf("MarshalWrite error: %v", err) + } + checkGrowth(writeSizes) + + var readSizes []int + if err := UnmarshalRead(ReaderFunc(func(b []byte) (int, error) { + n, err := bb.Read(b) + readSizes = append(readSizes, n) + return n, err + }), new([]struct{})); err != nil { + t.Fatalf("UnmarshalRead error: %v", err) + } + checkGrowth(readSizes) +} + +func TestUintSet(t *testing.T) { + type operation any // has | insert + type has struct { + in uint + want bool + } + type insert struct { + in uint + want bool + } + + // Sequence of operations to perform (order matters). + ops := []operation{ + has{0, false}, + has{63, false}, + has{64, false}, + has{1234, false}, + insert{3, true}, + has{2, false}, + has{3, true}, + has{4, false}, + has{63, false}, + insert{3, false}, + insert{63, true}, + has{63, true}, + insert{64, true}, + insert{64, false}, + has{64, true}, + insert{3264, true}, + has{3264, true}, + insert{3, false}, + has{3, true}, + } + + var us uintSet + for i, op := range ops { + switch op := op.(type) { + case has: + if got := us.has(op.in); got != op.want { + t.Fatalf("%d: uintSet.has(%v) = %v, want %v", i, op.in, got, op.want) + } + case insert: + if got := us.insert(op.in); got != op.want { + t.Fatalf("%d: uintSet.insert(%v) = %v, want %v", i, op.in, got, op.want) + } + default: + panic(fmt.Sprintf("unknown operation: %T", op)) + } + } +} + +func TestUnmarshalDecodeOptions(t *testing.T) { + var calledFuncs int + var calledOptions Options + in := strings.NewReader(strings.Repeat("\"\xde\xad\xbe\xef\"\n", 5)) + dec := jsontext.NewDecoder(in, + jsontext.AllowInvalidUTF8(true), // decoder-specific option + WithUnmarshalers(UnmarshalFromFunc(func(dec *jsontext.Decoder, _ any) error { + opts := dec.Options() + if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { + t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") + } + calledFuncs++ + calledOptions = opts + return SkipFunc + })), // unmarshal-specific option; only relevant for UnmarshalDecode + ) + + if err := UnmarshalDecode(dec, new(string)); err != nil { + t.Fatalf("UnmarshalDecode: %v", err) + } + if calledFuncs != 1 { + t.Fatalf("calledFuncs = %d, want 1", calledFuncs) + } + if err := UnmarshalDecode(dec, new(string), calledOptions); err != nil { + t.Fatalf("UnmarshalDecode: %v", err) + } + if calledFuncs != 2 { + t.Fatalf("calledFuncs = %d, want 2", calledFuncs) + } + if err := UnmarshalDecode(dec, new(string), + jsontext.AllowInvalidUTF8(false), // should be ignored + WithUnmarshalers(nil), // should override + ); err != nil { + t.Fatalf("UnmarshalDecode: %v", err) + } + if calledFuncs != 2 { + t.Fatalf("calledFuncs = %d, want 2", calledFuncs) + } + if err := UnmarshalDecode(dec, new(string)); err != nil { + t.Fatalf("UnmarshalDecode: %v", err) + } + if calledFuncs != 3 { + t.Fatalf("calledFuncs = %d, want 3", calledFuncs) + } + if err := UnmarshalDecode(dec, new(string), JoinOptions( + jsontext.AllowInvalidUTF8(false), // should be ignored + WithUnmarshalers(UnmarshalFromFunc(func(_ *jsontext.Decoder, _ any) error { + opts := dec.Options() + if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { + t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") + } + calledFuncs = math.MaxInt + return SkipFunc + })), // should override + )); err != nil { + t.Fatalf("UnmarshalDecode: %v", err) + } + if calledFuncs != math.MaxInt { + t.Fatalf("calledFuncs = %d, want %d", calledFuncs, math.MaxInt) + } + + // Reset with the decoder options as part of the arguments should not + // observe mutations to the options until after Reset is done. + opts := dec.Options() // AllowInvalidUTF8 is currently true + dec.Reset(in, jsontext.AllowInvalidUTF8(false), opts) // earlier AllowInvalidUTF8(false) should be overridden by latter AllowInvalidUTF8(true) in opts + if v, _ := GetOption(dec.Options(), jsontext.AllowInvalidUTF8); v == false { + t.Errorf("Options.AllowInvalidUTF8 = false, want true") + } +} + +// BenchmarkUnmarshalDecodeOptions is a minimal decode operation to measure +// the overhead options setup before the unmarshal operation. +func BenchmarkUnmarshalDecodeOptions(b *testing.B) { + var i int + in := new(bytes.Buffer) + dec := jsontext.NewDecoder(in) + makeBench := func(opts ...Options) func(*testing.B) { + return func(b *testing.B) { + for range b.N { + in.WriteString("0 ") + } + dec.Reset(in) + b.ResetTimer() + for range b.N { + UnmarshalDecode(dec, &i, opts...) + } + } + } + b.Run("None", makeBench()) + b.Run("Same", makeBench(&export.Decoder(dec).Struct)) + b.Run("New", makeBench(DefaultOptionsV2())) +} + +func TestMarshalEncodeOptions(t *testing.T) { + var calledFuncs int + var calledOptions Options + out := new(bytes.Buffer) + enc := jsontext.NewEncoder( + out, + jsontext.AllowInvalidUTF8(true), // encoder-specific option + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, _ any) error { + opts := enc.Options() + if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { + t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") + } + calledFuncs++ + calledOptions = opts + return SkipFunc + })), // marshal-specific option; only relevant for MarshalEncode + ) + + if err := MarshalEncode(enc, "\xde\xad\xbe\xef"); err != nil { + t.Fatalf("MarshalEncode: %v", err) + } + if calledFuncs != 1 { + t.Fatalf("calledFuncs = %d, want 1", calledFuncs) + } + if err := MarshalEncode(enc, "\xde\xad\xbe\xef", calledOptions); err != nil { + t.Fatalf("MarshalEncode: %v", err) + } + if calledFuncs != 2 { + t.Fatalf("calledFuncs = %d, want 2", calledFuncs) + } + if err := MarshalEncode(enc, "\xde\xad\xbe\xef", + jsontext.AllowInvalidUTF8(false), // should be ignored + WithMarshalers(nil), // should override + ); err != nil { + t.Fatalf("MarshalEncode: %v", err) + } + if calledFuncs != 2 { + t.Fatalf("calledFuncs = %d, want 2", calledFuncs) + } + if err := MarshalEncode(enc, "\xde\xad\xbe\xef"); err != nil { + t.Fatalf("MarshalEncode: %v", err) + } + if calledFuncs != 3 { + t.Fatalf("calledFuncs = %d, want 3", calledFuncs) + } + if err := MarshalEncode(enc, "\xde\xad\xbe\xef", JoinOptions( + jsontext.AllowInvalidUTF8(false), // should be ignored + WithMarshalers(MarshalToFunc(func(enc *jsontext.Encoder, _ any) error { + opts := enc.Options() + if v, _ := GetOption(opts, jsontext.AllowInvalidUTF8); !v { + t.Errorf("nested Options.AllowInvalidUTF8 = false, want true") + } + calledFuncs = math.MaxInt + return SkipFunc + })), // should override + )); err != nil { + t.Fatalf("MarshalEncode: %v", err) + } + if calledFuncs != math.MaxInt { + t.Fatalf("calledFuncs = %d, want %d", calledFuncs, math.MaxInt) + } + if out.String() != strings.Repeat("\"\xde\xad\ufffd\ufffd\"\n", 5) { + t.Fatalf("output mismatch:\n\tgot: %s\n\twant: %s", out.String(), strings.Repeat("\"\xde\xad\xbe\xef\"\n", 5)) + } + + // Reset with the encoder options as part of the arguments should not + // observe mutations to the options until after Reset is done. + opts := enc.Options() // AllowInvalidUTF8 is currently true + enc.Reset(out, jsontext.AllowInvalidUTF8(false), opts) // earlier AllowInvalidUTF8(false) should be overridden by latter AllowInvalidUTF8(true) in opts + if v, _ := GetOption(enc.Options(), jsontext.AllowInvalidUTF8); v == false { + t.Errorf("Options.AllowInvalidUTF8 = false, want true") + } +} + +// BenchmarkMarshalEncodeOptions is a minimal encode operation to measure +// the overhead of options setup before the marshal operation. +func BenchmarkMarshalEncodeOptions(b *testing.B) { + var i int + out := new(bytes.Buffer) + enc := jsontext.NewEncoder(out) + makeBench := func(opts ...Options) func(*testing.B) { + return func(b *testing.B) { + out.Reset() + enc.Reset(out) + b.ResetTimer() + for range b.N { + MarshalEncode(enc, &i, opts...) + } + } + } + b.Run("None", makeBench()) + b.Run("Same", makeBench(&export.Encoder(enc).Struct)) + b.Run("New", makeBench(DefaultOptionsV2())) +} diff --git a/src/encoding/json/v2/arshal_time.go b/src/encoding/json/v2/arshal_time.go new file mode 100644 index 0000000000..e40a04f12a --- /dev/null +++ b/src/encoding/json/v2/arshal_time.go @@ -0,0 +1,600 @@ +// Copyright 2020 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 + +package json + +import ( + "bytes" + "cmp" + "errors" + "fmt" + "math" + "math/bits" + "reflect" + "strconv" + "strings" + "time" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +var ( + timeDurationType = reflect.TypeFor[time.Duration]() + timeTimeType = reflect.TypeFor[time.Time]() +) + +func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { + // Ideally, time types would implement MarshalerTo and UnmarshalerFrom, + // but that would incur a dependency on package json from package time. + // Given how widely used time is, it is more acceptable that we incur a + // dependency on time from json. + // + // Injecting the arshaling functionality like this will not be identical + // to actually declaring methods on the time types since embedding of the + // time types will not be able to forward this functionality. + switch t { + case timeDurationType: + fncs.nonDefault = true + marshalNano := fncs.marshal + 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 !m.initFormat(mo.Format) { + return newInvalidFormatError(enc, t, mo) + } + } else if mo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) { + return marshalNano(enc, va, mo) + } + + // TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo. + m.td = *va.Addr().Interface().(*time.Duration) + k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) + if err := xe.AppendRaw(k, true, m.appendMarshal); err != nil { + if !isSyntacticError(err) && !export.IsIOError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err + } + return nil + } + unmarshalNano := fncs.unmarshal + 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 !u.initFormat(uo.Format) { + return newInvalidFormatError(dec, t, uo) + } + } else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) { + return unmarshalNano(dec, va, uo) + } + + stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) + var flags jsonwire.ValueFlags + td := va.Addr().Interface().(*time.Duration) + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + switch k := val.Kind(); k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + *td = time.Duration(0) + } + return nil + case '"': + if !stringify { + break + } + val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if err := u.unmarshal(val); err != nil { + return newUnmarshalErrorAfter(dec, t, err) + } + *td = u.td + return nil + case '0': + if stringify { + break + } + if err := u.unmarshal(val); err != nil { + return newUnmarshalErrorAfter(dec, t, err) + } + *td = u.td + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + case timeTimeType: + fncs.nonDefault = true + 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 !m.initFormat(mo.Format) { + return newInvalidFormatError(enc, t, mo) + } + } + + // TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo. + m.tt = *va.Addr().Interface().(*time.Time) + k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers)) + 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 + } + if !isSyntacticError(err) && !export.IsIOError(err) { + err = newMarshalErrorBefore(enc, t, err) + } + return err + } + return nil + } + 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 !u.initFormat(uo.Format) { + return newInvalidFormatError(dec, t, uo) + } + } else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) { + u.looseRFC3339 = true + } + + stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers) + var flags jsonwire.ValueFlags + tt := va.Addr().Interface().(*time.Time) + val, err := xd.ReadValue(&flags) + if err != nil { + return err + } + switch k := val.Kind(); k { + case 'n': + if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) { + *tt = time.Time{} + } + return nil + case '"': + if !stringify { + break + } + val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim()) + if err := u.unmarshal(val); err != nil { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err // unlike marshal, never wrapped + } + return newUnmarshalErrorAfter(dec, t, err) + } + *tt = u.tt + return nil + case '0': + if stringify { + break + } + if err := u.unmarshal(val); err != nil { + if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + return err // unlike marshal, never wrapped + } + return newUnmarshalErrorAfter(dec, t, err) + } + *tt = u.tt + return nil + } + return newUnmarshalErrorAfter(dec, t, nil) + } + } + return fncs +} + +type durationArshaler struct { + td time.Duration + + // base records the representation where: + // - 0 uses time.Duration.String + // - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the duration as + // nanoseconds, microseconds, milliseconds, or seconds. + base uint64 +} + +func (a *durationArshaler) initFormat(format string) (ok bool) { + switch format { + case "units": + a.base = 0 + case "sec": + a.base = 1e9 + case "milli": + a.base = 1e6 + case "micro": + a.base = 1e3 + case "nano": + a.base = 1e0 + default: + return false + } + return true +} + +func (a *durationArshaler) isNumeric() bool { + return a.base != 0 && a.base != 60 +} + +func (a *durationArshaler) appendMarshal(b []byte) ([]byte, error) { + switch a.base { + case 0: + return append(b, a.td.String()...), nil + default: + return appendDurationBase10(b, a.td, a.base), nil + } +} + +func (a *durationArshaler) unmarshal(b []byte) (err error) { + switch a.base { + case 0: + a.td, err = time.ParseDuration(string(b)) + default: + a.td, err = parseDurationBase10(b, a.base) + } + return err +} + +type timeArshaler struct { + tt time.Time + + // base records the representation where: + // - 0 uses RFC 3339 encoding of the timestamp + // - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the timestamp as + // seconds, milliseconds, microseconds, or nanoseconds since Unix epoch. + // - math.MaxUint uses time.Time.Format to encode the timestamp + base uint64 + format string // time format passed to time.Parse + + looseRFC3339 bool +} + +func (a *timeArshaler) initFormat(format string) bool { + // We assume that an exported constant in the time package will + // always start with an uppercase ASCII letter. + if len(format) == 0 { + return false + } + a.base = math.MaxUint // implies custom format + if c := format[0]; !('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z') { + a.format = format + return true + } + switch format { + case "ANSIC": + a.format = time.ANSIC + case "UnixDate": + a.format = time.UnixDate + case "RubyDate": + a.format = time.RubyDate + case "RFC822": + a.format = time.RFC822 + case "RFC822Z": + a.format = time.RFC822Z + case "RFC850": + a.format = time.RFC850 + case "RFC1123": + a.format = time.RFC1123 + case "RFC1123Z": + a.format = time.RFC1123Z + case "RFC3339": + a.base = 0 + a.format = time.RFC3339 + case "RFC3339Nano": + a.base = 0 + a.format = time.RFC3339Nano + case "Kitchen": + a.format = time.Kitchen + case "Stamp": + a.format = time.Stamp + case "StampMilli": + a.format = time.StampMilli + case "StampMicro": + a.format = time.StampMicro + case "StampNano": + a.format = time.StampNano + case "DateTime": + a.format = time.DateTime + case "DateOnly": + a.format = time.DateOnly + case "TimeOnly": + a.format = time.TimeOnly + case "unix": + a.base = 1e0 + case "unixmilli": + a.base = 1e3 + case "unixmicro": + a.base = 1e6 + case "unixnano": + a.base = 1e9 + default: + // Reject any Go identifier in case new constants are supported. + if strings.TrimFunc(format, isLetterOrDigit) == "" { + return false + } + a.format = format + } + return true +} + +func (a *timeArshaler) isNumeric() bool { + return int(a.base) > 0 +} + +func (a *timeArshaler) hasCustomFormat() bool { + return a.base == math.MaxUint +} + +func (a *timeArshaler) appendMarshal(b []byte) ([]byte, error) { + switch a.base { + case 0: + format := cmp.Or(a.format, time.RFC3339Nano) + n0 := len(b) + b = a.tt.AppendFormat(b, format) + // Not all Go timestamps can be represented as valid RFC 3339. + // Explicitly check for these edge cases. + // See https://go.dev/issue/4556 and https://go.dev/issue/54580. + switch b := b[n0:]; { + case b[len("9999")] != '-': // year must be exactly 4 digits wide + return b, errors.New("year outside of range [0,9999]") + case b[len(b)-1] != 'Z': + c := b[len(b)-len("Z07:00")] + if ('0' <= c && c <= '9') || parseDec2(b[len(b)-len("07:00"):]) >= 24 { + return b, errors.New("timezone hour outside of range [0,23]") + } + } + return b, nil + case math.MaxUint: + return a.tt.AppendFormat(b, a.format), nil + default: + return appendTimeUnix(b, a.tt, a.base), nil + } +} + +func (a *timeArshaler) unmarshal(b []byte) (err error) { + switch a.base { + case 0: + // Use time.Time.UnmarshalText to avoid possible string allocation. + if err := a.tt.UnmarshalText(b); err != nil { + return err + } + // TODO(https://go.dev/issue/57912): + // RFC 3339 specifies the grammar for a valid timestamp. + // However, the parsing functionality in "time" is too loose and + // incorrectly accepts invalid timestamps as valid. + // Remove these manual checks when "time" checks it for us. + newParseError := func(layout, value, layoutElem, valueElem, message string) error { + return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message} + } + switch { + case a.looseRFC3339: + return nil + case b[len("2006-01-02T")+1] == ':': // hour must be two digits + return newParseError(time.RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), "") + case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period + return newParseError(time.RFC3339, string(b), ".", ",", "") + case b[len(b)-1] != 'Z': + switch { + case parseDec2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range + return newParseError(time.RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range") + case parseDec2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range + return newParseError(time.RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range") + } + } + return nil + case math.MaxUint: + a.tt, err = time.Parse(a.format, string(b)) + return err + default: + a.tt, err = parseTimeUnix(b, a.base) + return err + } +} + +// appendDurationBase10 appends d formatted as a decimal fractional number, +// where pow10 is a power-of-10 used to scale down the number. +func appendDurationBase10(b []byte, d time.Duration, pow10 uint64) []byte { + b, n := mayAppendDurationSign(b, d) // append sign + whole, frac := bits.Div64(0, n, uint64(pow10)) // compute whole and frac fields + b = strconv.AppendUint(b, whole, 10) // append whole field + return appendFracBase10(b, frac, pow10) // append frac field +} + +// parseDurationBase10 parses d from a decimal fractional number, +// where pow10 is a power-of-10 used to scale up the number. +func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) { + suffix, neg := consumeSign(b) // consume sign + wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields + whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow + frac, okFrac := parseFracBase10(fracBytes, pow10) // parse frac field + hi, lo := bits.Mul64(whole, uint64(pow10)) // overflow if hi > 0 + sum, co := bits.Add64(lo, uint64(frac), 0) // overflow if co > 0 + switch d := mayApplyDurationSign(sum, neg); { // overflow if neg != (d < 0) + case (!okWhole && whole != math.MaxUint64) || !okFrac: + return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrSyntax) + case !okWhole || hi > 0 || co > 0 || neg != (d < 0): + return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrRange) + default: + return d, nil + } +} + +// mayAppendDurationSign appends a negative sign if n is negative. +func mayAppendDurationSign(b []byte, d time.Duration) ([]byte, uint64) { + if d < 0 { + b = append(b, '-') + d *= -1 + } + return b, uint64(d) +} + +// mayApplyDurationSign inverts n if neg is specified. +func mayApplyDurationSign(n uint64, neg bool) time.Duration { + if neg { + return -1 * time.Duration(n) + } else { + return +1 * time.Duration(n) + } +} + +// appendTimeUnix appends t formatted as a decimal fractional number, +// where pow10 is a power-of-10 used to scale up the number. +func appendTimeUnix(b []byte, t time.Time, pow10 uint64) []byte { + sec, nsec := t.Unix(), int64(t.Nanosecond()) + if sec < 0 { + b = append(b, '-') + sec, nsec = negateSecNano(sec, nsec) + } + switch { + case pow10 == 1e0: // fast case where units is in seconds + b = strconv.AppendUint(b, uint64(sec), 10) + return appendFracBase10(b, uint64(nsec), 1e9) + case uint64(sec) < 1e9: // intermediate case where units is not seconds, but no overflow + b = strconv.AppendUint(b, uint64(sec)*uint64(pow10)+uint64(uint64(nsec)/(1e9/pow10)), 10) + return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9) + default: // slow case where units is not seconds and overflow would occur + b = strconv.AppendUint(b, uint64(sec), 10) + b = appendPaddedBase10(b, uint64(nsec)/(1e9/pow10), pow10) + return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9) + } +} + +// parseTimeUnix parses t formatted as a decimal fractional number, +// where pow10 is a power-of-10 used to scale down the number. +func parseTimeUnix(b []byte, pow10 uint64) (time.Time, error) { + suffix, neg := consumeSign(b) // consume sign + wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields + whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow + frac, okFrac := parseFracBase10(fracBytes, 1e9/pow10) // parse frac field + var sec, nsec int64 + switch { + case pow10 == 1e0: // fast case where units is in seconds + sec = int64(whole) // check overflow later after negation + nsec = int64(frac) // cannot overflow + case okWhole: // intermediate case where units is not seconds, but no overflow + sec = int64(whole / pow10) // check overflow later after negation + nsec = int64((whole%pow10)*(1e9/pow10) + frac) // cannot overflow + case !okWhole && whole == math.MaxUint64: // slow case where units is not seconds and overflow occurred + width := int(math.Log10(float64(pow10))) // compute len(strconv.Itoa(pow10-1)) + whole, okWhole = jsonwire.ParseUint(wholeBytes[:len(wholeBytes)-width]) // parse the upper whole field + mid, _ := parsePaddedBase10(wholeBytes[len(wholeBytes)-width:], pow10) // parse the lower whole field + sec = int64(whole) // check overflow later after negation + nsec = int64(mid*(1e9/pow10) + frac) // cannot overflow + } + if neg { + sec, nsec = negateSecNano(sec, nsec) + } + switch t := time.Unix(sec, nsec).UTC(); { + case (!okWhole && whole != math.MaxUint64) || !okFrac: + return time.Time{}, fmt.Errorf("invalid time %q: %w", b, strconv.ErrSyntax) + case !okWhole || neg != (t.Unix() < 0): + return time.Time{}, fmt.Errorf("invalid time %q: %w", b, strconv.ErrRange) + default: + return t, nil + } +} + +// negateSecNano negates a Unix timestamp, where nsec must be within [0, 1e9). +func negateSecNano(sec, nsec int64) (int64, int64) { + sec = ^sec // twos-complement negation (i.e., -1*sec + 1) + nsec = -nsec + 1e9 // negate nsec and add 1e9 (which is the extra +1 from sec negation) + sec += int64(nsec / 1e9) // handle possible overflow of nsec if it started as zero + nsec %= 1e9 // ensure nsec stays within [0, 1e9) + return sec, nsec +} + +// appendFracBase10 appends the fraction of n/max10, +// where max10 is a power-of-10 that is larger than n. +func appendFracBase10(b []byte, n, max10 uint64) []byte { + if n == 0 { + return b + } + return bytes.TrimRight(appendPaddedBase10(append(b, '.'), n, max10), "0") +} + +// parseFracBase10 parses the fraction of n/max10, +// where max10 is a power-of-10 that is larger than n. +func parseFracBase10(b []byte, max10 uint64) (n uint64, ok bool) { + switch { + case len(b) == 0: + return 0, true + case len(b) < len(".0") || b[0] != '.': + return 0, false + } + return parsePaddedBase10(b[len("."):], max10) +} + +// appendPaddedBase10 appends a zero-padded encoding of n, +// where max10 is a power-of-10 that is larger than n. +func appendPaddedBase10(b []byte, n, max10 uint64) []byte { + if n < max10/10 { + // Formatting of n is shorter than log10(max10), + // so add max10/10 to ensure the length is equal to log10(max10). + i := len(b) + b = strconv.AppendUint(b, n+max10/10, 10) + b[i]-- // subtract the addition of max10/10 + return b + } + return strconv.AppendUint(b, n, 10) +} + +// parsePaddedBase10 parses b as the zero-padded encoding of n, +// where max10 is a power-of-10 that is larger than n. +// Truncated suffix is treated as implicit zeros. +// Extended suffix is ignored, but verified to contain only digits. +func parsePaddedBase10(b []byte, max10 uint64) (n uint64, ok bool) { + pow10 := uint64(1) + for pow10 < max10 { + n *= 10 + if len(b) > 0 { + if b[0] < '0' || '9' < b[0] { + return n, false + } + n += uint64(b[0] - '0') + b = b[1:] + } + pow10 *= 10 + } + if len(b) > 0 && len(bytes.TrimRight(b, "0123456789")) > 0 { + return n, false // trailing characters are not digits + } + return n, true +} + +// consumeSign consumes an optional leading negative sign. +func consumeSign(b []byte) ([]byte, bool) { + if len(b) > 0 && b[0] == '-' { + return b[len("-"):], true + } + return b, false +} + +// bytesCutByte is similar to bytes.Cut(b, []byte{c}), +// except c may optionally be included as part of the suffix. +func bytesCutByte(b []byte, c byte, include bool) ([]byte, []byte) { + if i := bytes.IndexByte(b, c); i >= 0 { + if include { + return b[:i], b[i:] + } + return b[:i], b[i+1:] + } + return b, nil +} + +// parseDec2 parses b as an unsigned, base-10, 2-digit number. +// The result is undefined if digits are not base-10. +func parseDec2(b []byte) byte { + if len(b) < 2 { + return 0 + } + return 10*(b[0]-'0') + (b[1] - '0') +} diff --git a/src/encoding/json/v2/arshal_time_test.go b/src/encoding/json/v2/arshal_time_test.go new file mode 100644 index 0000000000..faa09de509 --- /dev/null +++ b/src/encoding/json/v2/arshal_time_test.go @@ -0,0 +1,312 @@ +// 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 + +package json + +import ( + "fmt" + "math" + "testing" + "time" + + "encoding/json/internal/jsonwire" +) + +func baseLabel(base uint64) string { + if log10 := math.Log10(float64(base)); log10 == float64(int64(log10)) { + return fmt.Sprintf("1e%d", int(log10)) + } + return fmt.Sprint(base) +} + +var formatDurationTestdata = []struct { + td time.Duration + base10Sec string + base10Milli string + base10Micro string + base10Nano string +}{ + {math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807"}, + {1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000"}, + {1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000"}, + {1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000"}, + {1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000"}, + {1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000"}, + {1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000"}, + {1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000"}, + {1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000"}, + {1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000"}, + {1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000"}, + {1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100"}, + {1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010"}, + {1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001"}, + {+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001"}, + {+(1e9), "1", "1000", "1000000", "1000000000"}, + {+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999"}, + {+100000000, "0.1", "100", "100000", "100000000"}, + {+120000000, "0.12", "120", "120000", "120000000"}, + {+123000000, "0.123", "123", "123000", "123000000"}, + {+123400000, "0.1234", "123.4", "123400", "123400000"}, + {+123450000, "0.12345", "123.45", "123450", "123450000"}, + {+123456000, "0.123456", "123.456", "123456", "123456000"}, + {+123456700, "0.1234567", "123.4567", "123456.7", "123456700"}, + {+123456780, "0.12345678", "123.45678", "123456.78", "123456780"}, + {+123456789, "0.123456789", "123.456789", "123456.789", "123456789"}, + {+12345678, "0.012345678", "12.345678", "12345.678", "12345678"}, + {+1234567, "0.001234567", "1.234567", "1234.567", "1234567"}, + {+123456, "0.000123456", "0.123456", "123.456", "123456"}, + {+12345, "0.000012345", "0.012345", "12.345", "12345"}, + {+1234, "0.000001234", "0.001234", "1.234", "1234"}, + {+123, "0.000000123", "0.000123", "0.123", "123"}, + {+12, "0.000000012", "0.000012", "0.012", "12"}, + {+1, "0.000000001", "0.000001", "0.001", "1"}, + {0, "0", "0", "0", "0"}, + {-1, "-0.000000001", "-0.000001", "-0.001", "-1"}, + {-12, "-0.000000012", "-0.000012", "-0.012", "-12"}, + {-123, "-0.000000123", "-0.000123", "-0.123", "-123"}, + {-1234, "-0.000001234", "-0.001234", "-1.234", "-1234"}, + {-12345, "-0.000012345", "-0.012345", "-12.345", "-12345"}, + {-123456, "-0.000123456", "-0.123456", "-123.456", "-123456"}, + {-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567"}, + {-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678"}, + {-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789"}, + {-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780"}, + {-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700"}, + {-123456000, "-0.123456", "-123.456", "-123456", "-123456000"}, + {-123450000, "-0.12345", "-123.45", "-123450", "-123450000"}, + {-123400000, "-0.1234", "-123.4", "-123400", "-123400000"}, + {-123000000, "-0.123", "-123", "-123000", "-123000000"}, + {-120000000, "-0.12", "-120", "-120000", "-120000000"}, + {-100000000, "-0.1", "-100", "-100000", "-100000000"}, + {-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999"}, + {-(1e9), "-1", "-1000", "-1000000", "-1000000000"}, + {-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001"}, + {math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808"}, +} + +func TestFormatDuration(t *testing.T) { + var gotBuf []byte + check := func(td time.Duration, s string, base uint64) { + a := durationArshaler{td, base} + gotBuf, _ = a.appendMarshal(gotBuf[:0]) + if string(gotBuf) != s { + t.Errorf("formatDuration(%d, %s) = %q, want %q", td, baseLabel(base), string(gotBuf), s) + } + if err := a.unmarshal(gotBuf); err != nil { + t.Errorf("parseDuration(%q, %s) error: %v", gotBuf, baseLabel(base), err) + } + if a.td != td { + t.Errorf("parseDuration(%q, %s) = %d, want %d", gotBuf, baseLabel(base), a.td, td) + } + } + for _, tt := range formatDurationTestdata { + check(tt.td, tt.base10Sec, 1e9) + check(tt.td, tt.base10Milli, 1e6) + check(tt.td, tt.base10Micro, 1e3) + check(tt.td, tt.base10Nano, 1e0) + } +} + +var parseDurationTestdata = []struct { + in string + base uint64 + want time.Duration + wantErr bool +}{ + {"0", 1e0, 0, false}, + {"0.", 1e0, 0, true}, + {"0.0", 1e0, 0, false}, + {"0.00", 1e0, 0, false}, + {"00.0", 1e0, 0, true}, + {"+0", 1e0, 0, true}, + {"1e0", 1e0, 0, true}, + {"1.000000000x", 1e9, 0, true}, + {"1.000000x", 1e6, 0, true}, + {"1.000x", 1e3, 0, true}, + {"1.x", 1e0, 0, true}, + {"1.0000000009", 1e9, +time.Second, false}, + {"1.0000009", 1e6, +time.Millisecond, false}, + {"1.0009", 1e3, +time.Microsecond, false}, + {"1.9", 1e0, +time.Nanosecond, false}, + {"-9223372036854775809", 1e0, 0, true}, + {"9223372036854775.808", 1e3, 0, true}, + {"-9223372036854.775809", 1e6, 0, true}, + {"9223372036.854775808", 1e9, 0, true}, + {"-1.9", 1e0, -time.Nanosecond, false}, + {"-1.0009", 1e3, -time.Microsecond, false}, + {"-1.0000009", 1e6, -time.Millisecond, false}, + {"-1.0000000009", 1e9, -time.Second, false}, +} + +func TestParseDuration(t *testing.T) { + for _, tt := range parseDurationTestdata { + a := durationArshaler{base: tt.base} + switch err := a.unmarshal([]byte(tt.in)); { + case a.td != tt.want: + t.Errorf("parseDuration(%q, %s) = %v, want %v", tt.in, baseLabel(tt.base), a.td, tt.want) + case (err == nil) && tt.wantErr: + t.Errorf("parseDuration(%q, %s) error is nil, want non-nil", tt.in, baseLabel(tt.base)) + case (err != nil) && !tt.wantErr: + t.Errorf("parseDuration(%q, %s) error is non-nil, want nil", tt.in, baseLabel(tt.base)) + } + } +} + +func FuzzFormatDuration(f *testing.F) { + for _, tt := range formatDurationTestdata { + f.Add(int64(tt.td)) + } + f.Fuzz(func(t *testing.T, want int64) { + var buf []byte + for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9} { + a := durationArshaler{td: time.Duration(want), base: base} + buf, _ = a.appendMarshal(buf[:0]) + switch err := a.unmarshal(buf); { + case err != nil: + t.Fatalf("parseDuration(%q, %s) error: %v", buf, baseLabel(base), err) + case a.td != time.Duration(want): + t.Fatalf("parseDuration(%q, %s) = %v, want %v", buf, baseLabel(base), a.td, time.Duration(want)) + } + } + }) +} + +func FuzzParseDuration(f *testing.F) { + for _, tt := range parseDurationTestdata { + f.Add([]byte(tt.in)) + } + f.Fuzz(func(t *testing.T, in []byte) { + for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9, 60} { + a := durationArshaler{base: base} + if err := a.unmarshal(in); err == nil && base != 60 { + if n, err := jsonwire.ConsumeNumber(in); err != nil || n != len(in) { + t.Fatalf("parseDuration(%q) error is nil for invalid JSON number", in) + } + } + } + }) +} + +type formatTimeTestdataEntry struct { + ts time.Time + unixSec string + unixMilli string + unixMicro string + unixNano string +} + +var formatTimeTestdata = func() []formatTimeTestdataEntry { + out := []formatTimeTestdataEntry{ + {time.Unix(math.MaxInt64/int64(1e0), 1e9-1).UTC(), "9223372036854775807.999999999", "9223372036854775807999.999999", "9223372036854775807999999.999", "9223372036854775807999999999"}, + {time.Unix(math.MaxInt64/int64(1e1), 1e9-1).UTC(), "922337203685477580.999999999", "922337203685477580999.999999", "922337203685477580999999.999", "922337203685477580999999999"}, + {time.Unix(math.MaxInt64/int64(1e2), 1e9-1).UTC(), "92233720368547758.999999999", "92233720368547758999.999999", "92233720368547758999999.999", "92233720368547758999999999"}, + {time.Unix(math.MinInt64, 1).UTC(), "-9223372036854775807.999999999", "-9223372036854775807999.999999", "-9223372036854775807999999.999", "-9223372036854775807999999999"}, + {time.Unix(math.MinInt64, 0).UTC(), "-9223372036854775808", "-9223372036854775808000", "-9223372036854775808000000", "-9223372036854775808000000000"}, + } + for _, tt := range formatDurationTestdata { + out = append(out, formatTimeTestdataEntry{time.Unix(0, int64(tt.td)).UTC(), tt.base10Sec, tt.base10Milli, tt.base10Micro, tt.base10Nano}) + } + return out +}() + +func TestFormatTime(t *testing.T) { + var gotBuf []byte + check := func(ts time.Time, s string, pow10 uint64) { + gotBuf = appendTimeUnix(gotBuf[:0], ts, pow10) + if string(gotBuf) != s { + t.Errorf("formatTime(time.Unix(%d, %d), %s) = %q, want %q", ts.Unix(), ts.Nanosecond(), baseLabel(pow10), string(gotBuf), s) + } + gotTS, err := parseTimeUnix(gotBuf, pow10) + if err != nil { + t.Errorf("parseTime(%q, %s) error: %v", gotBuf, baseLabel(pow10), err) + } + if !gotTS.Equal(ts) { + t.Errorf("parseTime(%q, %s) = time.Unix(%d, %d), want time.Unix(%d, %d)", gotBuf, baseLabel(pow10), gotTS.Unix(), gotTS.Nanosecond(), ts.Unix(), ts.Nanosecond()) + } + } + for _, tt := range formatTimeTestdata { + check(tt.ts, tt.unixSec, 1e0) + check(tt.ts, tt.unixMilli, 1e3) + check(tt.ts, tt.unixMicro, 1e6) + check(tt.ts, tt.unixNano, 1e9) + } +} + +var parseTimeTestdata = []struct { + in string + base uint64 + want time.Time + wantErr bool +}{ + {"0", 1e0, time.Unix(0, 0).UTC(), false}, + {"0.", 1e0, time.Time{}, true}, + {"0.0", 1e0, time.Unix(0, 0).UTC(), false}, + {"0.00", 1e0, time.Unix(0, 0).UTC(), false}, + {"00.0", 1e0, time.Time{}, true}, + {"+0", 1e0, time.Time{}, true}, + {"1e0", 1e0, time.Time{}, true}, + {"1234567890123456789012345678901234567890", 1e0, time.Time{}, true}, + {"9223372036854775808000.000000", 1e3, time.Time{}, true}, + {"9223372036854775807999999.9999", 1e6, time.Unix(math.MaxInt64, 1e9-1).UTC(), false}, + {"9223372036854775807999999999.9", 1e9, time.Unix(math.MaxInt64, 1e9-1).UTC(), false}, + {"9223372036854775807.999999999x", 1e0, time.Time{}, true}, + {"9223372036854775807000000000", 1e9, time.Unix(math.MaxInt64, 0).UTC(), false}, + {"-9223372036854775808", 1e0, time.Unix(math.MinInt64, 0).UTC(), false}, + {"-9223372036854775808000.000001", 1e3, time.Time{}, true}, + {"-9223372036854775808000000.0001", 1e6, time.Unix(math.MinInt64, 0).UTC(), false}, + {"-9223372036854775808000000000.x", 1e9, time.Time{}, true}, + {"-1234567890123456789012345678901234567890", 1e9, time.Time{}, true}, +} + +func TestParseTime(t *testing.T) { + for _, tt := range parseTimeTestdata { + a := timeArshaler{base: tt.base} + switch err := a.unmarshal([]byte(tt.in)); { + case a.tt != tt.want: + t.Errorf("parseTime(%q, %s) = time.Unix(%d, %d), want time.Unix(%d, %d)", tt.in, baseLabel(tt.base), a.tt.Unix(), a.tt.Nanosecond(), tt.want.Unix(), tt.want.Nanosecond()) + case (err == nil) && tt.wantErr: + t.Errorf("parseTime(%q, %s) = (time.Unix(%d, %d), nil), want non-nil error", tt.in, baseLabel(tt.base), a.tt.Unix(), a.tt.Nanosecond()) + case (err != nil) && !tt.wantErr: + t.Errorf("parseTime(%q, %s) error is non-nil, want nil", tt.in, baseLabel(tt.base)) + } + } +} + +func FuzzFormatTime(f *testing.F) { + for _, tt := range formatTimeTestdata { + f.Add(tt.ts.Unix(), int64(tt.ts.Nanosecond())) + } + f.Fuzz(func(t *testing.T, wantSec, wantNano int64) { + want := time.Unix(wantSec, int64(uint64(wantNano)%1e9)).UTC() + var buf []byte + for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9} { + a := timeArshaler{tt: want, base: base} + buf, _ = a.appendMarshal(buf[:0]) + switch err := a.unmarshal(buf); { + case err != nil: + t.Fatalf("parseTime(%q, %s) error: %v", buf, baseLabel(base), err) + case a.tt != want: + t.Fatalf("parseTime(%q, %s) = time.Unix(%d, %d), want time.Unix(%d, %d)", buf, baseLabel(base), a.tt.Unix(), a.tt.Nanosecond(), want.Unix(), want.Nanosecond()) + } + } + }) +} + +func FuzzParseTime(f *testing.F) { + for _, tt := range parseTimeTestdata { + f.Add([]byte(tt.in)) + } + f.Fuzz(func(t *testing.T, in []byte) { + for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9} { + a := timeArshaler{base: base} + if err := a.unmarshal(in); err == nil { + if n, err := jsonwire.ConsumeNumber(in); err != nil || n != len(in) { + t.Fatalf("parseTime(%q) error is nil for invalid JSON number", in) + } + } + } + }) +} diff --git a/src/encoding/json/v2/bench_test.go b/src/encoding/json/v2/bench_test.go new file mode 100644 index 0000000000..a46f4ab5d3 --- /dev/null +++ b/src/encoding/json/v2/bench_test.go @@ -0,0 +1,647 @@ +// Copyright 2020 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 + +package json_test + +import ( + "bytes" + "cmp" + "fmt" + "io" + "os" + "path" + "reflect" + "strings" + "testing" + "testing/iotest" + "time" + + jsonv1 "encoding/json" + + jsonv1in2 "encoding/json" + "encoding/json/internal/jsontest" + "encoding/json/jsontext" + jsonv2 "encoding/json/v2" +) + +// benchVersion is the version to benchmark (either "v1", "v1in2", or "v2"). +var benchVersion = cmp.Or(os.Getenv("BENCHMARK_VERSION"), "v2") + +var jsonFuncs = func() (funcs struct { + marshal func(any) ([]byte, error) + unmarshal func([]byte, any) error + encodeValue func(w io.Writer, b []byte) error + encodeTokens func(w io.Writer, toks []jsontext.Token) error + decodeValue func(r io.Reader) error + decodeTokens func(r io.Reader) error +}) { + ignoreEOF := func(err error) error { + if err == io.EOF { + err = nil + } + return err + } + + switch benchVersion { + case "v1": + funcs.marshal = jsonv1.Marshal + funcs.unmarshal = jsonv1.Unmarshal + funcs.encodeValue = func(w io.Writer, b []byte) error { + return jsonv1.NewEncoder(w).Encode(jsonv1.RawMessage(b)) + } + funcs.decodeValue = func(r io.Reader) error { + var v jsonv1.RawMessage + return jsonv1.NewDecoder(r).Decode(&v) + } + funcs.decodeTokens = func(r io.Reader) error { + d := jsonv1.NewDecoder(r) + for { + if _, err := d.Token(); err != nil { + return ignoreEOF(err) + } + } + } + case "v1in2": + funcs.marshal = jsonv1in2.Marshal + funcs.unmarshal = jsonv1in2.Unmarshal + funcs.encodeValue = func(w io.Writer, b []byte) error { + return jsonv1in2.NewEncoder(w).Encode(jsonv1in2.RawMessage(b)) + } + funcs.decodeValue = func(r io.Reader) error { + var v jsonv1in2.RawMessage + return jsonv1in2.NewDecoder(r).Decode(&v) + } + funcs.decodeTokens = func(r io.Reader) error { + d := jsonv1in2.NewDecoder(r) + for { + if _, err := d.Token(); err != nil { + return ignoreEOF(err) + } + } + } + case "v2": + funcs.marshal = func(v any) ([]byte, error) { return jsonv2.Marshal(v) } + funcs.unmarshal = func(b []byte, v any) error { return jsonv2.Unmarshal(b, v) } + funcs.encodeValue = func(w io.Writer, b []byte) error { + return jsontext.NewEncoder(w).WriteValue(b) + } + funcs.encodeTokens = func(w io.Writer, toks []jsontext.Token) error { + e := jsontext.NewEncoder(w) + for _, tok := range toks { + if err := e.WriteToken(tok); err != nil { + return err + } + } + return nil + } + funcs.decodeValue = func(r io.Reader) error { + _, err := jsontext.NewDecoder(r).ReadValue() + return err + } + funcs.decodeTokens = func(r io.Reader) error { + d := jsontext.NewDecoder(r) + for { + if _, err := d.ReadToken(); err != nil { + return ignoreEOF(err) + } + } + } + default: + panic("unknown version: " + benchVersion) + } + return +}() + +// bytesBuffer is identical to bytes.Buffer, +// but a different type to avoid any optimizations for bytes.Buffer. +type bytesBuffer struct{ *bytes.Buffer } + +func addr[T any](v T) *T { + return &v +} + +func len64[Bytes ~[]byte | ~string](in Bytes) int64 { + return int64(len(in)) +} + +var arshalTestdata = []struct { + name string + raw []byte + val any + new func() any + skipV1 bool +}{{ + name: "Bool", + raw: []byte("true"), + val: addr(true), + new: func() any { return new(bool) }, +}, { + name: "String", + raw: []byte(`"hello, world!"`), + val: addr("hello, world!"), + new: func() any { return new(string) }, +}, { + name: "Int", + raw: []byte("-1234"), + val: addr(int64(-1234)), + new: func() any { return new(int64) }, +}, { + name: "Uint", + raw: []byte("1234"), + val: addr(uint64(1234)), + new: func() any { return new(uint64) }, +}, { + name: "Float", + raw: []byte("12.34"), + val: addr(float64(12.34)), + new: func() any { return new(float64) }, +}, { + name: "Map/ManyEmpty", + raw: []byte(`[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]`), + val: addr(func() (out []map[string]string) { + for range 100 { + out = append(out, map[string]string{}) + } + return out + }()), + new: func() any { return new([]map[string]string) }, +}, { + name: "Map/OneLarge", + raw: []byte(`{"A":"A","B":"B","C":"C","D":"D","E":"E","F":"F","G":"G","H":"H","I":"I","J":"J","K":"K","L":"L","M":"M","N":"N","O":"O","P":"P","Q":"Q","R":"R","S":"S","T":"T","U":"U","V":"V","W":"W","X":"X","Y":"Y","Z":"Z"}`), + val: addr(map[string]string{"A": "A", "B": "B", "C": "C", "D": "D", "E": "E", "F": "F", "G": "G", "H": "H", "I": "I", "J": "J", "K": "K", "L": "L", "M": "M", "N": "N", "O": "O", "P": "P", "Q": "Q", "R": "R", "S": "S", "T": "T", "U": "U", "V": "V", "W": "W", "X": "X", "Y": "Y", "Z": "Z"}), + new: func() any { return new(map[string]string) }, +}, { + name: "Map/ManySmall", + raw: []byte(`{"A":{"K":"V"},"B":{"K":"V"},"C":{"K":"V"},"D":{"K":"V"},"E":{"K":"V"},"F":{"K":"V"},"G":{"K":"V"},"H":{"K":"V"},"I":{"K":"V"},"J":{"K":"V"},"K":{"K":"V"},"L":{"K":"V"},"M":{"K":"V"},"N":{"K":"V"},"O":{"K":"V"},"P":{"K":"V"},"Q":{"K":"V"},"R":{"K":"V"},"S":{"K":"V"},"T":{"K":"V"},"U":{"K":"V"},"V":{"K":"V"},"W":{"K":"V"},"X":{"K":"V"},"Y":{"K":"V"},"Z":{"K":"V"}}`), + val: addr(map[string]map[string]string{"A": {"K": "V"}, "B": {"K": "V"}, "C": {"K": "V"}, "D": {"K": "V"}, "E": {"K": "V"}, "F": {"K": "V"}, "G": {"K": "V"}, "H": {"K": "V"}, "I": {"K": "V"}, "J": {"K": "V"}, "K": {"K": "V"}, "L": {"K": "V"}, "M": {"K": "V"}, "N": {"K": "V"}, "O": {"K": "V"}, "P": {"K": "V"}, "Q": {"K": "V"}, "R": {"K": "V"}, "S": {"K": "V"}, "T": {"K": "V"}, "U": {"K": "V"}, "V": {"K": "V"}, "W": {"K": "V"}, "X": {"K": "V"}, "Y": {"K": "V"}, "Z": {"K": "V"}}), + new: func() any { return new(map[string]map[string]string) }, +}, { + name: "Struct/ManyEmpty", + raw: []byte(`[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]`), + val: addr(make([]struct{}, 100)), + new: func() any { + return new([]struct{}) + }, +}, { + name: "Struct/OneLarge", + raw: []byte(`{"A":"A","B":"B","C":"C","D":"D","E":"E","F":"F","G":"G","H":"H","I":"I","J":"J","K":"K","L":"L","M":"M","N":"N","O":"O","P":"P","Q":"Q","R":"R","S":"S","T":"T","U":"U","V":"V","W":"W","X":"X","Y":"Y","Z":"Z"}`), + val: addr(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z string }{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}), + new: func() any { + return new(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z string }) + }, +}, { + name: "Struct/ManySmall", + raw: []byte(`{"A":{"K":"V"},"B":{"K":"V"},"C":{"K":"V"},"D":{"K":"V"},"E":{"K":"V"},"F":{"K":"V"},"G":{"K":"V"},"H":{"K":"V"},"I":{"K":"V"},"J":{"K":"V"},"K":{"K":"V"},"L":{"K":"V"},"M":{"K":"V"},"N":{"K":"V"},"O":{"K":"V"},"P":{"K":"V"},"Q":{"K":"V"},"R":{"K":"V"},"S":{"K":"V"},"T":{"K":"V"},"U":{"K":"V"},"V":{"K":"V"},"W":{"K":"V"},"X":{"K":"V"},"Y":{"K":"V"},"Z":{"K":"V"}}`), + val: func() any { + V := struct{ K string }{"V"} + return addr(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z struct{ K string } }{ + V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, V, + }) + }(), + new: func() any { + return new(struct{ A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z struct{ K string } }) + }, +}, { + name: "Slice/ManyEmpty", + raw: []byte(`[[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]]`), + val: addr(func() (out [][]string) { + for range 100 { + out = append(out, []string{}) + } + return out + }()), + new: func() any { return new([][]string) }, +}, { + name: "Slice/OneLarge", + raw: []byte(`["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]`), + val: addr([]string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}), + new: func() any { return new([]string) }, +}, { + name: "Slice/ManySmall", + raw: []byte(`[["A"],["B"],["C"],["D"],["E"],["F"],["G"],["H"],["I"],["J"],["K"],["L"],["M"],["N"],["O"],["P"],["Q"],["R"],["S"],["T"],["U"],["V"],["W"],["X"],["Y"],["Z"]]`), + val: addr([][]string{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"G"}, {"H"}, {"I"}, {"J"}, {"K"}, {"L"}, {"M"}, {"N"}, {"O"}, {"P"}, {"Q"}, {"R"}, {"S"}, {"T"}, {"U"}, {"V"}, {"W"}, {"X"}, {"Y"}, {"Z"}}), + new: func() any { return new([][]string) }, +}, { + name: "Array/OneLarge", + raw: []byte(`["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]`), + val: addr([26]string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}), + new: func() any { return new([26]string) }, +}, { + name: "Array/ManySmall", + raw: []byte(`[["A"],["B"],["C"],["D"],["E"],["F"],["G"],["H"],["I"],["J"],["K"],["L"],["M"],["N"],["O"],["P"],["Q"],["R"],["S"],["T"],["U"],["V"],["W"],["X"],["Y"],["Z"]]`), + val: addr([26][1]string{{"A"}, {"B"}, {"C"}, {"D"}, {"E"}, {"F"}, {"G"}, {"H"}, {"I"}, {"J"}, {"K"}, {"L"}, {"M"}, {"N"}, {"O"}, {"P"}, {"Q"}, {"R"}, {"S"}, {"T"}, {"U"}, {"V"}, {"W"}, {"X"}, {"Y"}, {"Z"}}), + new: func() any { return new([26][1]string) }, +}, { + name: "Bytes/Slice", + raw: []byte(`"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="`), + val: addr([]byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}), + new: func() any { return new([]byte) }, +}, { + name: "Bytes/Array", + raw: []byte(`"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="`), + val: addr([32]byte{0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55}), + new: func() any { return new([32]byte) }, + skipV1: true, +}, { + name: "Pointer", + raw: []byte("true"), + val: addr(addr(addr(addr(addr(addr(addr(addr(addr(addr(addr(true))))))))))), + new: func() any { return new(**********bool) }, +}, { + name: "TextArshal", + raw: []byte(`"method"`), + val: new(textArshaler), + new: func() any { return new(textArshaler) }, +}, { + name: "JSONArshalV1", + raw: []byte(`"method"`), + val: new(jsonArshalerV1), + new: func() any { return new(jsonArshalerV1) }, +}, { + name: "JSONArshalV2", + raw: []byte(`"method"`), + val: new(jsonArshalerV2), + new: func() any { return new(jsonArshalerV2) }, + skipV1: true, +}, { + name: "Duration", + raw: []byte(`"1h1m1s"`), + val: addr(time.Hour + time.Minute + time.Second), + new: func() any { return new(time.Duration) }, + skipV1: true, +}, { + name: "Time", + raw: []byte(`"2006-01-02T22:04:05Z"`), + val: addr(time.Unix(1136239445, 0).UTC()), + new: func() any { return new(time.Time) }, +}} + +type textArshaler struct{ _ [4]int } + +func (textArshaler) MarshalText() ([]byte, error) { + return []byte("method"), nil +} +func (*textArshaler) UnmarshalText(b []byte) error { + if string(b) != "method" { + return fmt.Errorf("UnmarshalText: got %q, want %q", b, "method") + } + return nil +} + +type jsonArshalerV1 struct{ _ [4]int } + +func (jsonArshalerV1) MarshalJSON() ([]byte, error) { + return []byte(`"method"`), nil +} +func (*jsonArshalerV1) UnmarshalJSON(b []byte) error { + if string(b) != `"method"` { + return fmt.Errorf("UnmarshalJSON: got %q, want %q", b, `"method"`) + } + return nil +} + +type jsonArshalerV2 struct{ _ [4]int } + +func (jsonArshalerV2) MarshalJSONTo(enc *jsontext.Encoder) error { + return enc.WriteToken(jsontext.String("method")) +} +func (*jsonArshalerV2) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + b, err := dec.ReadValue() + if string(b) != `"method"` { + return fmt.Errorf("UnmarshalJSONFrom: got %q, want %q", b, `"method"`) + } + return err +} + +func TestBenchmarkUnmarshal(t *testing.T) { runUnmarshal(t) } +func BenchmarkUnmarshal(b *testing.B) { runUnmarshal(b) } + +func runUnmarshal(tb testing.TB) { + for _, tt := range arshalTestdata { + if tt.skipV1 && strings.HasPrefix(benchVersion, "v1") { + runTestOrBench(tb, tt.name, 0, func(tb testing.TB) { tb.Skip("not supported in v1") }) + return + } + + // Setup the unmarshal operation. + var val any + run := func(tb testing.TB) { + val = tt.new() + if err := jsonFuncs.unmarshal(tt.raw, val); err != nil { + tb.Fatalf("Unmarshal error: %v", err) + } + } + + // Verify the results. + if _, ok := tb.(*testing.T); ok { + run0 := run + run = func(tb testing.TB) { + run0(tb) + if !reflect.DeepEqual(val, tt.val) { + tb.Fatalf("Unmarshal output mismatch:\ngot %v\nwant %v", val, tt.val) + } + } + } + + runTestOrBench(tb, tt.name, len64(tt.raw), run) + } +} + +func TestBenchmarkMarshal(t *testing.T) { runMarshal(t) } +func BenchmarkMarshal(b *testing.B) { runMarshal(b) } + +func runMarshal(tb testing.TB) { + for _, tt := range arshalTestdata { + if tt.skipV1 && strings.HasPrefix(benchVersion, "v1") { + runTestOrBench(tb, tt.name, 0, func(tb testing.TB) { tb.Skip("not supported in v1") }) + return + } + + // Setup the marshal operation. + var raw []byte + run := func(tb testing.TB) { + var err error + raw, err = jsonFuncs.marshal(tt.val) + if err != nil { + tb.Fatalf("Marshal error: %v", err) + } + } + + // Verify the results. + if _, ok := tb.(*testing.T); ok { + run0 := run + run = func(tb testing.TB) { + run0(tb) + if !bytes.Equal(raw, tt.raw) { + // Map marshaling in v2 is non-deterministic. + byteHistogram := func(b []byte) (h [256]int) { + for _, c := range b { + h[c]++ + } + return h + } + if !(strings.HasPrefix(tt.name, "Map/") && byteHistogram(raw) == byteHistogram(tt.raw)) { + tb.Fatalf("Marshal output mismatch:\ngot %s\nwant %s", raw, tt.raw) + } + } + } + } + + runTestOrBench(tb, tt.name, len64(tt.raw), run) + } +} + +func TestBenchmarkTestdata(t *testing.T) { runAllTestdata(t) } +func BenchmarkTestdata(b *testing.B) { runAllTestdata(b) } + +func runAllTestdata(tb testing.TB) { + for _, td := range jsontest.Data { + for _, arshalName := range []string{"Marshal", "Unmarshal"} { + for _, typeName := range []string{"Concrete", "Interface"} { + newValue := func() any { return new(any) } + if typeName == "Concrete" { + if td.New == nil { + continue + } + newValue = td.New + } + value := mustUnmarshalValue(tb, td.Data(), newValue) + name := path.Join(td.Name, arshalName, typeName) + runTestOrBench(tb, name, int64(len(td.Data())), func(tb testing.TB) { + runArshal(tb, arshalName, newValue, td.Data(), value) + }) + } + } + + tokens := mustDecodeTokens(tb, td.Data()) + buffer := make([]byte, 0, 2*len(td.Data())) + for _, codeName := range []string{"Encode", "Decode"} { + for _, typeName := range []string{"Token", "Value"} { + for _, modeName := range []string{"Streaming", "Buffered"} { + name := path.Join(td.Name, codeName, typeName, modeName) + runTestOrBench(tb, name, int64(len(td.Data())), func(tb testing.TB) { + runCode(tb, codeName, typeName, modeName, buffer, td.Data(), tokens) + }) + } + } + } + } +} + +func mustUnmarshalValue(t testing.TB, data []byte, newValue func() any) (value any) { + value = newValue() + if err := jsonv2.Unmarshal(data, value); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + return value +} + +func runArshal(t testing.TB, arshalName string, newValue func() any, data []byte, value any) { + switch arshalName { + case "Marshal": + if _, err := jsonFuncs.marshal(value); err != nil { + t.Fatalf("Marshal error: %v", err) + } + case "Unmarshal": + if err := jsonFuncs.unmarshal(data, newValue()); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + } +} + +func mustDecodeTokens(t testing.TB, data []byte) []jsontext.Token { + var tokens []jsontext.Token + dec := jsontext.NewDecoder(bytes.NewReader(data)) + for { + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + t.Fatalf("Decoder.ReadToken error: %v", err) + } + + // Prefer exact representation for JSON strings and numbers + // since this more closely matches common use cases. + switch tok.Kind() { + case '"': + tokens = append(tokens, jsontext.String(tok.String())) + case '0': + tokens = append(tokens, jsontext.Float(tok.Float())) + default: + tokens = append(tokens, tok.Clone()) + } + } + return tokens +} + +func runCode(t testing.TB, codeName, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) { + switch codeName { + case "Encode": + runEncode(t, typeName, modeName, buffer, data, tokens) + case "Decode": + runDecode(t, typeName, modeName, buffer, data, tokens) + } +} + +func runEncode(t testing.TB, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) { + if strings.HasPrefix(benchVersion, "v1") { + switch { + case modeName == "Buffered": + t.Skip("no support for direct buffered output in v1; see https://go.dev/issue/7872") + case typeName == "Token": + t.Skip("no support for encoding tokens in v1; see https://go.dev/issue/40127") + } + } + + var w io.Writer + switch modeName { + case "Streaming": + w = bytesBuffer{bytes.NewBuffer(buffer[:0])} + case "Buffered": + w = bytes.NewBuffer(buffer[:0]) + } + switch typeName { + case "Token": + if err := jsonFuncs.encodeTokens(w, tokens); err != nil { + t.Fatalf("Encoder.WriteToken error: %v", err) + } + case "Value": + if err := jsonFuncs.encodeValue(w, data); err != nil { + t.Fatalf("Encoder.WriteValue error: %v", err) + } + } +} + +func runDecode(t testing.TB, typeName, modeName string, buffer, data []byte, tokens []jsontext.Token) { + if strings.HasPrefix(benchVersion, "v1") && modeName == "Buffered" { + t.Skip("no support for direct buffered input in v1; see https://go.dev/issue/11046") + } + + var r io.Reader + switch modeName { + case "Streaming": + r = bytesBuffer{bytes.NewBuffer(data)} + case "Buffered": + r = bytes.NewBuffer(data) + } + switch typeName { + case "Token": + if err := jsonFuncs.decodeTokens(r); err != nil { + t.Fatalf("Decoder.ReadToken error: %v", err) + } + case "Value": + if err := jsonFuncs.decodeValue(r); err != nil { + t.Fatalf("Decoder.ReadValue error: %v", err) + } + } +} + +var ws = strings.Repeat(" ", 4<<10) +var slowStreamingDecodeTestdata = []struct { + name string + data []byte +}{ + {"LargeString", []byte(`"` + strings.Repeat(" ", 4<<10) + `"`)}, + {"LargeNumber", []byte("0." + strings.Repeat("0", 4<<10))}, + {"LargeWhitespace/Null", []byte(ws + "null" + ws)}, + {"LargeWhitespace/Object", []byte(ws + "{" + ws + `"name1"` + ws + ":" + ws + `"value"` + ws + "," + ws + `"name2"` + ws + ":" + ws + `"value"` + ws + "}" + ws)}, + {"LargeWhitespace/Array", []byte(ws + "[" + ws + `"value"` + ws + "," + ws + `"value"` + ws + "]" + ws)}, +} + +func TestBenchmarkSlowStreamingDecode(t *testing.T) { runAllSlowStreamingDecode(t) } +func BenchmarkSlowStreamingDecode(b *testing.B) { runAllSlowStreamingDecode(b) } + +func runAllSlowStreamingDecode(tb testing.TB) { + for _, td := range slowStreamingDecodeTestdata { + for _, typeName := range []string{"Token", "Value"} { + name := path.Join(td.name, typeName) + runTestOrBench(tb, name, len64(td.data), func(tb testing.TB) { + runSlowStreamingDecode(tb, typeName, td.data) + }) + } + } +} + +// runSlowStreamingDecode tests a streaming Decoder operating on +// a slow io.Reader that only returns 1 byte at a time, +// which tends to exercise pathological behavior. +func runSlowStreamingDecode(t testing.TB, typeName string, data []byte) { + r := iotest.OneByteReader(bytes.NewReader(data)) + switch typeName { + case "Token": + if err := jsonFuncs.decodeTokens(r); err != nil { + t.Fatalf("Decoder.ReadToken error: %v", err) + } + case "Value": + if err := jsonFuncs.decodeValue(r); err != nil { + t.Fatalf("Decoder.ReadValue error: %v", err) + } + } +} + +func TestBenchmarkTextValue(t *testing.T) { runValue(t) } +func BenchmarkTextValue(b *testing.B) { runValue(b) } + +func runValue(tb testing.TB) { + if testing.Short() { + tb.Skip() // CitmCatalog is not loaded in short mode + } + var data []byte + for _, ts := range jsontest.Data { + if ts.Name == "CitmCatalog" { + data = ts.Data() + } + } + + runTestOrBench(tb, "IsValid", len64(data), func(tb testing.TB) { + jsontext.Value(data).IsValid() + }) + + methods := []struct { + name string + format func(*jsontext.Value, ...jsontext.Options) error + }{ + {"Compact", (*jsontext.Value).Compact}, + {"Indent", (*jsontext.Value).Indent}, + {"Canonicalize", (*jsontext.Value).Canonicalize}, + } + + var v jsontext.Value + for _, method := range methods { + runTestOrBench(tb, method.name, len64(data), func(tb testing.TB) { + v = append(v[:0], data...) // reset with original input + if err := method.format(&v); err != nil { + tb.Errorf("jsontext.Value.%v error: %v", method.name, err) + } + }) + v = append(v[:0], data...) + method.format(&v) + runTestOrBench(tb, method.name+"/Noop", len64(data), func(tb testing.TB) { + if err := method.format(&v); err != nil { + tb.Errorf("jsontext.Value.%v error: %v", method.name, err) + } + }) + } +} + +func runTestOrBench(tb testing.TB, name string, numBytes int64, run func(tb testing.TB)) { + switch tb := tb.(type) { + case *testing.T: + tb.Run(name, func(t *testing.T) { + run(t) + }) + case *testing.B: + tb.Run(name, func(b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + b.SetBytes(numBytes) + for range b.N { + run(b) + } + }) + } +} diff --git a/src/encoding/json/v2/doc.go b/src/encoding/json/v2/doc.go new file mode 100644 index 0000000000..8dd0b138f5 --- /dev/null +++ b/src/encoding/json/v2/doc.go @@ -0,0 +1,170 @@ +// Copyright 2020 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 + +// Package json implements semantic processing of JSON as specified in RFC 8259. +// JSON is a simple data interchange format that can represent +// primitive data types such as booleans, strings, and numbers, +// in addition to structured data types such as objects and arrays. +// +// [Marshal] and [Unmarshal] encode and decode Go values +// to/from JSON text contained within a []byte. +// [MarshalWrite] and [UnmarshalRead] operate on JSON text +// by writing to or reading from an [io.Writer] or [io.Reader]. +// [MarshalEncode] and [UnmarshalDecode] operate on JSON text +// by encoding to or decoding from a [jsontext.Encoder] or [jsontext.Decoder]. +// [Options] may be passed to each of the marshal or unmarshal functions +// to configure the semantic behavior of marshaling and unmarshaling +// (i.e., alter how JSON data is understood as Go data and vice versa). +// [jsontext.Options] may also be passed to the marshal or unmarshal functions +// to configure the syntactic behavior of encoding or decoding. +// +// The data types of JSON are mapped to/from the data types of Go based on +// the closest logical equivalent between the two type systems. For example, +// a JSON boolean corresponds with a Go bool, +// a JSON string corresponds with a Go string, +// a JSON number corresponds with a Go int, uint or float, +// a JSON array corresponds with a Go slice or array, and +// a JSON object corresponds with a Go struct or map. +// See the documentation on [Marshal] and [Unmarshal] for a comprehensive list +// of how the JSON and Go type systems correspond. +// +// Arbitrary Go types can customize their JSON representation by implementing +// [Marshaler], [MarshalerTo], [Unmarshaler], or [UnmarshalerFrom]. +// This provides authors of Go types with control over how their types are +// serialized as JSON. Alternatively, users can implement functions that match +// [MarshalFunc], [MarshalToFunc], [UnmarshalFunc], or [UnmarshalFromFunc] +// to specify the JSON representation for arbitrary types. +// This provides callers of JSON functionality with control over +// how any arbitrary type is serialized as JSON. +// +// # JSON Representation of Go structs +// +// A Go struct is naturally represented as a JSON object, +// where each Go struct field corresponds with a JSON object member. +// When marshaling, all Go struct fields are recursively encoded in depth-first +// order as JSON object members except those that are ignored or omitted. +// When unmarshaling, JSON object members are recursively decoded +// into the corresponding Go struct fields. +// Object members that do not match any struct fields, +// also known as “unknown members”, are ignored by default or rejected +// 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. +// As a special case, if the entire tag is `json:"-"`, +// then the field is ignored with regard to its JSON representation. +// Some options also have equivalent behavior controlled by a caller-specified [Options]. +// Field-specified options take precedence over caller-specified options. +// +// The first option is the JSON object name override for the Go struct field. +// If the name is not specified, then the Go struct field name +// 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. +// By default, unmarshaling uses case-sensitive matching to identify +// the Go struct field associated with a JSON object name. +// +// After the name, the following tag options are supported: +// +// - omitzero: When marshaling, the "omitzero" option specifies that +// the struct field should be omitted if the field value is zero +// as determined by the "IsZero() bool" method if present, +// otherwise based on whether the field is the zero Go value. +// This option has no effect when unmarshaling. +// +// - omitempty: When marshaling, the "omitempty" option specifies that +// the struct field should be omitted if the field value would have been +// 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. +// +// - 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 'strict' value specifies that matching is case-sensitive. +// This takes precedence over the [MatchCaseInsensitiveNames] option. +// +// - inline: The "inline" option specifies that +// the JSON representable content of this field type is to be promoted +// as if they 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 +// (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 +// JSON object members not directly handled by the parent struct. +// Only one inlined 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 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. +// For example, only a nil slice or map is omitted under "omitzero", while +// an empty slice or map is omitted under "omitempty" regardless of nilness. +// 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 +// 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. +// 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. +// 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. +// This is analogous to Go visibility rules for struct field selection +// with embedded struct types. +// +// Marshaling or unmarshaling a non-empty struct +// without any JSON representable fields results in a [SemanticError]. +// Unexported fields must not have any `json` tags except for `json:"-"`. +package json + +// requireKeyedLiterals can be embedded in a struct to require keyed literals. +type requireKeyedLiterals struct{} + +// nonComparable can be embedded in a struct to prevent comparability. +type nonComparable [0]func() diff --git a/src/encoding/json/v2/errors.go b/src/encoding/json/v2/errors.go new file mode 100644 index 0000000000..48cdcc953b --- /dev/null +++ b/src/encoding/json/v2/errors.go @@ -0,0 +1,420 @@ +// Copyright 2020 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 + +package json + +import ( + "cmp" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +// ErrUnknownName indicates that a JSON object member could not be +// unmarshaled because the name is not known to the target Go struct. +// This error is directly wrapped within a [SemanticError] when produced. +// +// The name of an unknown JSON object member can be extracted as: +// +// err := ... +// var serr json.SemanticError +// if errors.As(err, &serr) && serr.Err == json.ErrUnknownName { +// ptr := serr.JSONPointer // JSON pointer to unknown name +// name := ptr.LastToken() // unknown name itself +// ... +// } +// +// This error is only returned if [RejectUnknownMembers] is true. +var ErrUnknownName = errors.New("unknown object member name") + +const errorPrefix = "json: " + +func isSemanticError(err error) bool { + _, ok := err.(*SemanticError) + return ok +} + +func isSyntacticError(err error) bool { + _, ok := err.(*jsontext.SyntacticError) + return ok +} + +// isFatalError reports whether this error must terminate asharling. +// All errors are considered fatal unless operating under +// [jsonflags.ReportErrorsWithLegacySemantics] in which case only +// syntactic errors and I/O errors are considered fatal. +func isFatalError(err error, flags jsonflags.Flags) bool { + return !flags.Get(jsonflags.ReportErrorsWithLegacySemantics) || + isSyntacticError(err) || export.IsIOError(err) +} + +// SemanticError describes an error determining the meaning +// of JSON data as Go data or vice-versa. +// +// The contents of this error as produced by this package may change over time. +type SemanticError struct { + requireKeyedLiterals + nonComparable + + action string // either "marshal" or "unmarshal" + + // ByteOffset indicates that an error occurred 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). + JSONPointer jsontext.Pointer + + // JSONKind is the JSON kind that could not be handled. + JSONKind jsontext.Kind // may be zero if unknown + // JSONValue is the JSON number or string that could not be unmarshaled. + // It is not populated during marshaling. + JSONValue jsontext.Value // may be nil if irrelevant or unknown + // GoType is the Go type that could not be handled. + GoType reflect.Type // may be nil if unknown + + // Err is the underlying error. + Err error // may be nil +} + +// coder is implemented by [jsontext.Encoder] or [jsontext.Decoder]. +type coder interface{ StackPointer() jsontext.Pointer } + +// newInvalidFormatError wraps err in a SemanticError because +// the current type t cannot handle the provided options format. +// This error must be called before producing or consuming the next value. +// +// If [jsonflags.ReportErrorsWithLegacySemantics] is specified, +// then this automatically skips the next value when unmarshaling +// to ensure that the value is fully consumed. +func newInvalidFormatError(c coder, t reflect.Type, o *jsonopts.Struct) error { + err := fmt.Errorf("invalid format flag %q", o.Format) + switch c := c.(type) { + case *jsontext.Encoder: + err = newMarshalErrorBefore(c, t, err) + case *jsontext.Decoder: + err = newUnmarshalErrorBeforeWithSkipping(c, o, t, err) + } + return err +} + +// newMarshalErrorBefore wraps err in a SemanticError assuming that e +// is positioned right before the next token or value, which causes an error. +func newMarshalErrorBefore(e *jsontext.Encoder, t reflect.Type, err error) error { + return &SemanticError{action: "marshal", GoType: t, Err: err, + ByteOffset: e.OutputOffset() + int64(export.Encoder(e).CountNextDelimWhitespace()), + JSONPointer: jsontext.Pointer(export.Encoder(e).AppendStackPointer(nil, +1))} +} + +// newUnmarshalErrorBefore wraps err in a SemanticError assuming that d +// is positioned right before the next token or value, which causes an error. +// It does not record the next JSON kind as this error is used to indicate +// the receiving Go value is invalid to unmarshal into (and not a JSON error). +func newUnmarshalErrorBefore(d *jsontext.Decoder, t reflect.Type, err error) error { + return &SemanticError{action: "unmarshal", GoType: t, Err: err, + ByteOffset: d.InputOffset() + int64(export.Decoder(d).CountNextDelimWhitespace()), + JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, +1))} +} + +// newUnmarshalErrorBeforeWithSkipping is like [newUnmarshalErrorBefore], +// but automatically skips the next value if +// [jsonflags.ReportErrorsWithLegacySemantics] is specified. +func newUnmarshalErrorBeforeWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error { + err = newUnmarshalErrorBefore(d, t, err) + if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := export.Decoder(d).SkipValue(); err2 != nil { + return err2 + } + } + return err +} + +// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d +// is positioned right after the previous token or value, which caused an error. +func newUnmarshalErrorAfter(d *jsontext.Decoder, t reflect.Type, err error) error { + tokOrVal := export.Decoder(d).PreviousTokenOrValue() + return &SemanticError{action: "unmarshal", GoType: t, Err: err, + ByteOffset: d.InputOffset() - int64(len(tokOrVal)), + JSONPointer: jsontext.Pointer(export.Decoder(d).AppendStackPointer(nil, -1)), + JSONKind: jsontext.Value(tokOrVal).Kind()} +} + +// newUnmarshalErrorAfter wraps err in a SemanticError assuming that d +// is positioned right after the previous token or value, which caused an error. +// It also stores a copy of the last JSON value if it is a string or number. +func newUnmarshalErrorAfterWithValue(d *jsontext.Decoder, t reflect.Type, err error) error { + serr := newUnmarshalErrorAfter(d, t, err).(*SemanticError) + if serr.JSONKind == '"' || serr.JSONKind == '0' { + serr.JSONValue = jsontext.Value(export.Decoder(d).PreviousTokenOrValue()).Clone() + } + return serr +} + +// newUnmarshalErrorAfterWithSkipping is like [newUnmarshalErrorAfter], +// but automatically skips the remainder of the current value if +// [jsonflags.ReportErrorsWithLegacySemantics] is specified. +func newUnmarshalErrorAfterWithSkipping(d *jsontext.Decoder, o *jsonopts.Struct, t reflect.Type, err error) error { + err = newUnmarshalErrorAfter(d, t, err) + if o.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) { + if err2 := export.Decoder(d).SkipValueRemainder(); err2 != nil { + return err2 + } + } + return err +} + +// newSemanticErrorWithPosition wraps err in a SemanticError assuming that +// the error occurred at the provided depth, and length. +// If err is already a SemanticError, then position information is only +// injected if it is currently unpopulated. +// +// If the position is unpopulated, it is ambiguous where the error occurred +// in the user code, whether it was before or after the current position. +// For the byte offset, we assume that the error occurred before the last read +// token or value when decoding, or before the next value when encoding. +// For the JSON pointer, we point to the parent object or array unless +// we can be certain that it happened with an object member. +// +// This is used to annotate errors returned by user-provided +// v2 MarshalJSON or UnmarshalJSON methods or functions. +func newSemanticErrorWithPosition(c coder, t reflect.Type, prevDepth int, prevLength int64, err error) error { + serr, _ := err.(*SemanticError) + if serr == nil { + serr = &SemanticError{Err: err} + } + var currDepth int + var currLength int64 + var coderState interface{ AppendStackPointer([]byte, int) []byte } + var offset int64 + switch c := c.(type) { + case *jsontext.Encoder: + e := export.Encoder(c) + serr.action = cmp.Or(serr.action, "marshal") + currDepth, currLength = e.Tokens.DepthLength() + offset = c.OutputOffset() + int64(export.Encoder(c).CountNextDelimWhitespace()) + coderState = e + case *jsontext.Decoder: + d := export.Decoder(c) + serr.action = cmp.Or(serr.action, "unmarshal") + currDepth, currLength = d.Tokens.DepthLength() + tokOrVal := d.PreviousTokenOrValue() + offset = c.InputOffset() - int64(len(tokOrVal)) + if (prevDepth == currDepth && prevLength == currLength) || len(tokOrVal) == 0 { + // If no Read method was called in the user-defined method or + // if the Peek method was called, then use the offset of the next value. + offset = c.InputOffset() + int64(export.Decoder(c).CountNextDelimWhitespace()) + } + coderState = d + } + serr.ByteOffset = cmp.Or(serr.ByteOffset, offset) + if serr.JSONPointer == "" { + where := 0 // default to ambiguous positioning + switch { + case prevDepth == currDepth && prevLength+0 == currLength: + where = +1 + case prevDepth == currDepth && prevLength+1 == currLength: + where = -1 + } + serr.JSONPointer = jsontext.Pointer(coderState.AppendStackPointer(nil, where)) + } + serr.GoType = cmp.Or(serr.GoType, t) + return serr +} + +// collapseSemanticErrors collapses double SemanticErrors at the outer levels +// into a single SemanticError by preserving the inner error, +// but prepending the ByteOffset and JSONPointer with the outer error. +// +// For example: +// +// collapseSemanticErrors(&SemanticError{ +// ByteOffset: len64(`[0,{"alpha":[0,1,`), +// JSONPointer: "/1/alpha/2", +// GoType: reflect.TypeFor[outerType](), +// Err: &SemanticError{ +// ByteOffset: len64(`{"foo":"bar","fizz":[0,`), +// JSONPointer: "/fizz/1", +// GoType: reflect.TypeFor[innerType](), +// Err: ..., +// }, +// }) +// +// results in: +// +// &SemanticError{ +// ByteOffset: len64(`[0,{"alpha":[0,1,`) + len64(`{"foo":"bar","fizz":[0,`), +// JSONPointer: "/1/alpha/2" + "/fizz/1", +// GoType: reflect.TypeFor[innerType](), +// Err: ..., +// } +// +// This is used to annotate errors returned by user-provided +// v1 MarshalJSON or UnmarshalJSON methods with precise position information +// if they themselves happened to return a SemanticError. +// Since MarshalJSON and UnmarshalJSON are not operating on the root JSON value, +// their positioning must be relative to the nested JSON value +// returned by UnmarshalJSON or passed to MarshalJSON. +// Therefore, we can construct an absolute position by concatenating +// the outer with the inner positions. +// +// Note that we do not use collapseSemanticErrors with user-provided functions +// that take in an [jsontext.Encoder] or [jsontext.Decoder] since they contain +// methods to report position relative to the root JSON value. +// We assume user-constructed errors are correctly precise about position. +func collapseSemanticErrors(err error) error { + if serr1, ok := err.(*SemanticError); ok { + if serr2, ok := serr1.Err.(*SemanticError); ok { + serr2.ByteOffset = serr1.ByteOffset + serr2.ByteOffset + serr2.JSONPointer = serr1.JSONPointer + serr2.JSONPointer + *serr1 = *serr2 + } + } + return err +} + +// errorModalVerb is a modal verb like "cannot" or "unable to". +// +// Once per process, Hyrum-proof the error message by deliberately +// switching between equivalent renderings of the same error message. +// The randomization is tied to the Hyrum-proofing already applied +// on map iteration in Go. +var errorModalVerb = sync.OnceValue(func() string { + for phrase := range map[string]struct{}{"cannot": {}, "unable to": {}} { + return phrase // use whichever phrase we get in the first iteration + } + return "" +}) + +func (e *SemanticError) Error() string { + var sb strings.Builder + sb.WriteString(errorPrefix) + sb.WriteString(errorModalVerb()) + + // Format action. + var preposition string + switch e.action { + case "marshal": + sb.WriteString(" marshal") + preposition = " from" + case "unmarshal": + sb.WriteString(" unmarshal") + preposition = " into" + default: + sb.WriteString(" handle") + preposition = " with" + } + + // Format JSON kind. + switch e.JSONKind { + case 'n': + sb.WriteString(" JSON null") + case 'f', 't': + sb.WriteString(" JSON boolean") + case '"': + sb.WriteString(" JSON string") + case '0': + sb.WriteString(" JSON number") + case '{', '}': + sb.WriteString(" JSON object") + case '[', ']': + sb.WriteString(" JSON array") + default: + if e.action == "" { + preposition = "" + } + } + if len(e.JSONValue) > 0 && len(e.JSONValue) < 100 { + sb.WriteByte(' ') + sb.Write(e.JSONValue) + } + + // Format Go type. + if e.GoType != nil { + typeString := e.GoType.String() + if len(typeString) > 100 { + // An excessively long type string most likely occurs for + // an anonymous struct declaration with many fields. + // Reduce the noise by just printing the kind, + // and optionally prepending it with the package name + // if the struct happens to include an unexported field. + typeString = e.GoType.Kind().String() + if e.GoType.Kind() == reflect.Struct && e.GoType.Name() == "" { + for i := range e.GoType.NumField() { + if pkgPath := e.GoType.Field(i).PkgPath; pkgPath != "" { + typeString = pkgPath[strings.LastIndexByte(pkgPath, '/')+len("/"):] + ".struct" + break + } + } + } + } + sb.WriteString(preposition) + sb.WriteString(" Go ") + sb.WriteString(typeString) + } + + // Special handling for unknown names. + if e.Err == ErrUnknownName { + sb.WriteString(": ") + sb.WriteString(ErrUnknownName.Error()) + sb.WriteString(" ") + sb.WriteString(strconv.Quote(e.JSONPointer.LastToken())) + if parent := e.JSONPointer.Parent(); parent != "" { + sb.WriteString(" within ") + sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(parent), 100))) + } + return sb.String() + } + + // Format where. + // Avoid printing if it overlaps with a wrapped SyntacticError. + switch serr, _ := e.Err.(*jsontext.SyntacticError); { + case e.JSONPointer != "": + if serr == nil || !e.JSONPointer.Contains(serr.JSONPointer) { + sb.WriteString(" within ") + sb.WriteString(strconv.Quote(jsonwire.TruncatePointer(string(e.JSONPointer), 100))) + } + case e.ByteOffset > 0: + if serr == nil || !(e.ByteOffset <= serr.ByteOffset) { + sb.WriteString(" after offset ") + sb.WriteString(strconv.FormatInt(e.ByteOffset, 10)) + } + } + + // Format underlying error. + if e.Err != nil { + errString := e.Err.Error() + if isSyntacticError(e.Err) { + errString = strings.TrimPrefix(errString, "jsontext: ") + } + sb.WriteString(": ") + sb.WriteString(errString) + } + + return sb.String() +} + +func (e *SemanticError) Unwrap() error { + return e.Err +} + +func newDuplicateNameError(ptr jsontext.Pointer, quotedName []byte, offset int64) error { + if quotedName != nil { + name, _ := jsonwire.AppendUnquote(nil, quotedName) + ptr = ptr.AppendToken(string(name)) + } + return &jsontext.SyntacticError{ + ByteOffset: offset, + JSONPointer: ptr, + Err: jsontext.ErrDuplicateName, + } +} diff --git a/src/encoding/json/v2/errors_test.go b/src/encoding/json/v2/errors_test.go new file mode 100644 index 0000000000..76a7f2ae31 --- /dev/null +++ b/src/encoding/json/v2/errors_test.go @@ -0,0 +1,115 @@ +// Copyright 2020 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 + +package json + +import ( + "archive/tar" + "bytes" + "errors" + "io" + "strings" + "testing" + + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" +) + +func TestSemanticError(t *testing.T) { + tests := []struct { + err error + want string + }{{ + err: &SemanticError{}, + want: `json: cannot handle`, + }, { + err: &SemanticError{JSONKind: 'n'}, + want: `json: cannot handle JSON null`, + }, { + err: &SemanticError{action: "unmarshal", JSONKind: 't'}, + want: `json: cannot unmarshal JSON boolean`, + }, { + err: &SemanticError{action: "unmarshal", JSONKind: 'x'}, + want: `json: cannot unmarshal`, // invalid token kinds are ignored + }, { + err: &SemanticError{action: "marshal", JSONKind: '"'}, + want: `json: cannot marshal JSON string`, + }, { + err: &SemanticError{GoType: T[bool]()}, + want: `json: cannot handle Go bool`, + }, { + err: &SemanticError{action: "marshal", GoType: T[int]()}, + want: `json: cannot marshal from Go int`, + }, { + err: &SemanticError{action: "unmarshal", GoType: T[uint]()}, + want: `json: cannot unmarshal into Go uint`, + }, { + err: &SemanticError{GoType: T[struct{ Alpha, Bravo, Charlie, Delta, Echo, Foxtrot, Golf, Hotel string }]()}, + want: `json: cannot handle Go struct`, + }, { + err: &SemanticError{GoType: T[struct{ Alpha, Bravo, Charlie, Delta, Echo, Foxtrot, Golf, Hotel, x string }]()}, + want: `json: cannot handle Go v2.struct`, + }, { + err: &SemanticError{JSONKind: '0', GoType: T[tar.Header]()}, + want: `json: cannot handle JSON number with Go tar.Header`, + }, { + err: &SemanticError{action: "unmarshal", JSONKind: '0', JSONValue: jsontext.Value(`1e1000`), GoType: T[int]()}, + want: `json: cannot unmarshal JSON number 1e1000 into Go int`, + }, { + err: &SemanticError{action: "marshal", JSONKind: '{', GoType: T[bytes.Buffer]()}, + want: `json: cannot marshal JSON object from Go bytes.Buffer`, + }, { + err: &SemanticError{action: "unmarshal", JSONKind: ']', GoType: T[strings.Reader]()}, + want: `json: cannot unmarshal JSON array into Go strings.Reader`, + }, { + err: &SemanticError{action: "unmarshal", JSONKind: '{', GoType: T[float64](), ByteOffset: 123}, + want: `json: cannot unmarshal JSON object into Go float64 after offset 123`, + }, { + err: &SemanticError{action: "marshal", JSONKind: 'f', GoType: T[complex128](), ByteOffset: 123, JSONPointer: "/foo/2/bar/3"}, + want: `json: cannot marshal JSON boolean from Go complex128 within "/foo/2/bar/3"`, + }, { + err: &SemanticError{action: "unmarshal", JSONKind: '}', GoType: T[io.Reader](), ByteOffset: 123, JSONPointer: "/foo/2/bar/3", Err: errors.New("some underlying error")}, + want: `json: cannot unmarshal JSON object into Go io.Reader within "/foo/2/bar/3": some underlying error`, + }, { + err: &SemanticError{Err: errors.New("some underlying error")}, + want: `json: cannot handle: some underlying error`, + }, { + err: &SemanticError{ByteOffset: 123}, + want: `json: cannot handle after offset 123`, + }, { + err: &SemanticError{JSONPointer: "/foo/2/bar/3"}, + want: `json: cannot handle within "/foo/2/bar/3"`, + }, { + err: &SemanticError{action: "unmarshal", JSONPointer: "/3", GoType: T[struct{ Fizz, Buzz string }](), Err: ErrUnknownName}, + want: `json: cannot unmarshal into Go struct { Fizz string; Buzz string }: unknown object member name "3"`, + }, { + err: &SemanticError{action: "unmarshal", JSONPointer: "/foo/2/bar/3", GoType: T[struct{ Foo string }](), Err: ErrUnknownName}, + want: `json: cannot unmarshal into Go struct { Foo string }: unknown object member name "3" within "/foo/2/bar"`, + }, { + err: &SemanticError{JSONPointer: "/foo/bar", ByteOffset: 16, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}}, + want: `json: cannot handle Go string: invalid UTF-8 within "/foo/bar/baz" after offset 53`, + }, { + err: &SemanticError{JSONPointer: "/fizz/bar", ByteOffset: 16, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}}, + want: `json: cannot handle Go string within "/fizz/bar": invalid UTF-8 within "/foo/bar/baz" after offset 53`, + }, { + err: &SemanticError{ByteOffset: 16, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}}, + want: `json: cannot handle Go string: invalid UTF-8 within "/foo/bar/baz" after offset 53`, + }, { + err: &SemanticError{ByteOffset: 85, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}}, + want: `json: cannot handle Go string after offset 85: invalid UTF-8 within "/foo/bar/baz" after offset 53`, + }} + + for _, tt := range tests { + got := tt.err.Error() + // Cleanup the error of non-deterministic rendering effects. + if strings.HasPrefix(got, errorPrefix+"unable to ") { + got = errorPrefix + "cannot " + strings.TrimPrefix(got, errorPrefix+"unable to ") + } + if got != tt.want { + t.Errorf("%#v.Error mismatch:\ngot %v\nwant %v", tt.err, got, tt.want) + } + } +} diff --git a/src/encoding/json/v2/example_orderedobject_test.go b/src/encoding/json/v2/example_orderedobject_test.go new file mode 100644 index 0000000000..d68782f725 --- /dev/null +++ b/src/encoding/json/v2/example_orderedobject_test.go @@ -0,0 +1,113 @@ +// Copyright 2022 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 + +package json_test + +import ( + "fmt" + "log" + "reflect" + + "encoding/json/jsontext" + "encoding/json/v2" +) + +// OrderedObject is an ordered sequence of name/value members in a JSON object. +// +// RFC 8259 defines an object as an "unordered collection". +// JSON implementations need not make "ordering of object members visible" +// to applications nor will they agree on the semantic meaning of an object if +// "the names within an object are not unique". For maximum compatibility, +// applications should avoid relying on ordering or duplicity of object names. +type OrderedObject[V any] []ObjectMember[V] + +// ObjectMember is a JSON object member. +type ObjectMember[V any] struct { + Name string + Value V +} + +// MarshalJSONTo encodes obj as a JSON object into enc. +func (obj *OrderedObject[V]) MarshalJSONTo(enc *jsontext.Encoder) error { + if err := enc.WriteToken(jsontext.BeginObject); err != nil { + return err + } + for i := range *obj { + member := &(*obj)[i] + if err := json.MarshalEncode(enc, &member.Name); err != nil { + return err + } + if err := json.MarshalEncode(enc, &member.Value); err != nil { + return err + } + } + if err := enc.WriteToken(jsontext.EndObject); err != nil { + return err + } + return nil +} + +// UnmarshalJSONFrom decodes a JSON object from dec into obj. +func (obj *OrderedObject[V]) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + if k := dec.PeekKind(); k != '{' { + return fmt.Errorf("expected object start, but encountered %v", k) + } + if _, err := dec.ReadToken(); err != nil { + return err + } + for dec.PeekKind() != '}' { + *obj = append(*obj, ObjectMember[V]{}) + member := &(*obj)[len(*obj)-1] + if err := json.UnmarshalDecode(dec, &member.Name); err != nil { + return err + } + if err := json.UnmarshalDecode(dec, &member.Value); err != nil { + return err + } + } + if _, err := dec.ReadToken(); err != nil { + return err + } + return nil +} + +// The exact order of JSON object can be preserved through the use of a +// specialized type that implements [MarshalerTo] and [UnmarshalerFrom]. +func Example_orderedObject() { + // Round-trip marshal and unmarshal an ordered object. + // We expect the order and duplicity of JSON object members to be preserved. + // Specify jsontext.AllowDuplicateNames since this object contains "fizz" twice. + want := OrderedObject[string]{ + {"fizz", "buzz"}, + {"hello", "world"}, + {"fizz", "wuzz"}, + } + b, err := json.Marshal(&want, jsontext.AllowDuplicateNames(true)) + if err != nil { + log.Fatal(err) + } + var got OrderedObject[string] + err = json.Unmarshal(b, &got, jsontext.AllowDuplicateNames(true)) + if err != nil { + log.Fatal(err) + } + + // Sanity check. + if !reflect.DeepEqual(got, want) { + log.Fatalf("roundtrip mismatch: got %v, want %v", got, want) + } + + // Print the serialized JSON object. + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println(string(b)) + + // Output: + // { + // "fizz": "buzz", + // "hello": "world", + // "fizz": "wuzz" + // } +} diff --git a/src/encoding/json/v2/example_test.go b/src/encoding/json/v2/example_test.go new file mode 100644 index 0000000000..fe40bff964 --- /dev/null +++ b/src/encoding/json/v2/example_test.go @@ -0,0 +1,692 @@ +// Copyright 2022 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 + +package json_test + +import ( + "bytes" + "errors" + "fmt" + "log" + "math" + "net/http" + "net/netip" + "os" + "reflect" + "strconv" + "strings" + "sync/atomic" + "time" + + "encoding/json/jsontext" + "encoding/json/v2" +) + +// If a type implements [encoding.TextMarshaler] and/or [encoding.TextUnmarshaler], +// then the MarshalText and UnmarshalText methods are used to encode/decode +// the value to/from a JSON string. +func Example_textMarshal() { + // Round-trip marshal and unmarshal a hostname map where the netip.Addr type + // implements both encoding.TextMarshaler and encoding.TextUnmarshaler. + want := map[netip.Addr]string{ + netip.MustParseAddr("192.168.0.100"): "carbonite", + netip.MustParseAddr("192.168.0.101"): "obsidian", + netip.MustParseAddr("192.168.0.102"): "diamond", + } + b, err := json.Marshal(&want, json.Deterministic(true)) + if err != nil { + log.Fatal(err) + } + var got map[netip.Addr]string + err = json.Unmarshal(b, &got) + if err != nil { + log.Fatal(err) + } + + // Sanity check. + if !reflect.DeepEqual(got, want) { + log.Fatalf("roundtrip mismatch: got %v, want %v", got, want) + } + + // Print the serialized JSON object. + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println(string(b)) + + // Output: + // { + // "192.168.0.100": "carbonite", + // "192.168.0.101": "obsidian", + // "192.168.0.102": "diamond" + // } +} + +// By default, JSON object names for Go struct fields are derived from +// the Go field name, but may be specified in the `json` tag. +// Due to JSON's heritage in JavaScript, the most common naming convention +// used for JSON object names is camelCase. +func Example_fieldNames() { + var value struct { + // This field is explicitly ignored with the special "-" name. + Ignored any `json:"-"` + // No JSON name is not provided, so the Go field name is used. + GoName any + // A JSON name is provided without any special characters. + JSONName any `json:"jsonName"` + // No JSON name is not provided, so the Go field name is used. + Option any `json:",case:ignore"` + // An empty JSON name specified using an single-quoted string literal. + Empty any `json:"''"` + // A dash JSON name specified using an single-quoted string literal. + Dash any `json:"'-'"` + // A comma JSON name specified using an single-quoted string literal. + Comma any `json:"','"` + // JSON name with quotes specified using a single-quoted string literal. + Quote any `json:"'\"\\''"` + // An unexported field is always ignored. + unexported any + } + + b, err := json.Marshal(value) + if err != nil { + log.Fatal(err) + } + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println(string(b)) + + // Output: + // { + // "GoName": null, + // "jsonName": null, + // "Option": null, + // "": null, + // "-": null, + // ",": null, + // "\"'": null + // } +} + +// Unmarshal matches JSON object names with Go struct fields using +// a case-sensitive match, but can be configured to use a case-insensitive +// match with the "case:ignore" option. This permits unmarshaling from inputs +// that use naming conventions such as camelCase, snake_case, or kebab-case. +func Example_caseSensitivity() { + // JSON input using various naming conventions. + const input = `[ + {"firstname": true}, + {"firstName": true}, + {"FirstName": true}, + {"FIRSTNAME": true}, + {"first_name": true}, + {"FIRST_NAME": true}, + {"first-name": true}, + {"FIRST-NAME": true}, + {"unknown": true} + ]` + + // Without "case:ignore", Unmarshal looks for an exact match. + var caseStrict []struct { + X bool `json:"firstName"` + } + if err := json.Unmarshal([]byte(input), &caseStrict); err != nil { + log.Fatal(err) + } + fmt.Println(caseStrict) // exactly 1 match found + + // With "case:ignore", Unmarshal looks first for an exact match, + // then for a case-insensitive match if none found. + var caseIgnore []struct { + X bool `json:"firstName,case:ignore"` + } + if err := json.Unmarshal([]byte(input), &caseIgnore); err != nil { + log.Fatal(err) + } + fmt.Println(caseIgnore) // 8 matches found + + // Output: + // [{false} {true} {false} {false} {false} {false} {false} {false} {false}] + // [{true} {true} {true} {true} {true} {true} {true} {true} {false}] +} + +// Go struct fields can be omitted from the output depending on either +// the input Go value or the output JSON encoding of the value. +// The "omitzero" option omits a field if it is the zero Go value or +// implements a "IsZero() bool" method that reports true. +// The "omitempty" option omits a field if it encodes as an empty JSON value, +// which we define as a JSON null or empty JSON string, object, or array. +// In many cases, the behavior of "omitzero" and "omitempty" are equivalent. +// If both provide the desired effect, then using "omitzero" is preferred. +func Example_omitFields() { + type MyStruct struct { + Foo string `json:",omitzero"` + Bar []int `json:",omitempty"` + // Both "omitzero" and "omitempty" can be specified together, + // in which case the field is omitted if either would take effect. + // This omits the Baz field either if it is a nil pointer or + // if it would have encoded as an empty JSON object. + Baz *MyStruct `json:",omitzero,omitempty"` + } + + // Demonstrate behavior of "omitzero". + b, err := json.Marshal(struct { + Bool bool `json:",omitzero"` + Int int `json:",omitzero"` + String string `json:",omitzero"` + Time time.Time `json:",omitzero"` + Addr netip.Addr `json:",omitzero"` + Struct MyStruct `json:",omitzero"` + SliceNil []int `json:",omitzero"` + Slice []int `json:",omitzero"` + MapNil map[int]int `json:",omitzero"` + Map map[int]int `json:",omitzero"` + PointerNil *string `json:",omitzero"` + Pointer *string `json:",omitzero"` + InterfaceNil any `json:",omitzero"` + Interface any `json:",omitzero"` + }{ + // Bool is omitted since false is the zero value for a Go bool. + Bool: false, + // Int is omitted since 0 is the zero value for a Go int. + Int: 0, + // String is omitted since "" is the zero value for a Go string. + String: "", + // Time is omitted since time.Time.IsZero reports true. + Time: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), + // Addr is omitted since netip.Addr{} is the zero value for a Go struct. + Addr: netip.Addr{}, + // Struct is NOT omitted since it is not the zero value for a Go struct. + Struct: MyStruct{Bar: []int{}, Baz: new(MyStruct)}, + // SliceNil is omitted since nil is the zero value for a Go slice. + SliceNil: nil, + // Slice is NOT omitted since []int{} is not the zero value for a Go slice. + Slice: []int{}, + // MapNil is omitted since nil is the zero value for a Go map. + MapNil: nil, + // Map is NOT omitted since map[int]int{} is not the zero value for a Go map. + Map: map[int]int{}, + // PointerNil is omitted since nil is the zero value for a Go pointer. + PointerNil: nil, + // Pointer is NOT omitted since new(string) is not the zero value for a Go pointer. + Pointer: new(string), + // InterfaceNil is omitted since nil is the zero value for a Go interface. + InterfaceNil: nil, + // Interface is NOT omitted since (*string)(nil) is not the zero value for a Go interface. + Interface: (*string)(nil), + }) + if err != nil { + log.Fatal(err) + } + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println("OmitZero:", string(b)) // outputs "Struct", "Slice", "Map", "Pointer", and "Interface" + + // Demonstrate behavior of "omitempty". + b, err = json.Marshal(struct { + Bool bool `json:",omitempty"` + Int int `json:",omitempty"` + String string `json:",omitempty"` + Time time.Time `json:",omitempty"` + Addr netip.Addr `json:",omitempty"` + Struct MyStruct `json:",omitempty"` + Slice []int `json:",omitempty"` + Map map[int]int `json:",omitempty"` + PointerNil *string `json:",omitempty"` + Pointer *string `json:",omitempty"` + InterfaceNil any `json:",omitempty"` + Interface any `json:",omitempty"` + }{ + // Bool is NOT omitted since false is not an empty JSON value. + Bool: false, + // Int is NOT omitted since 0 is not a empty JSON value. + Int: 0, + // String is omitted since "" is an empty JSON string. + String: "", + // Time is NOT omitted since this encodes as a non-empty JSON string. + Time: time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), + // Addr is omitted since this encodes as an empty JSON string. + Addr: netip.Addr{}, + // Struct is omitted since {} is an empty JSON object. + Struct: MyStruct{Bar: []int{}, Baz: new(MyStruct)}, + // Slice is omitted since [] is an empty JSON array. + Slice: []int{}, + // Map is omitted since {} is an empty JSON object. + Map: map[int]int{}, + // PointerNil is omitted since null is an empty JSON value. + PointerNil: nil, + // Pointer is omitted since "" is an empty JSON string. + Pointer: new(string), + // InterfaceNil is omitted since null is an empty JSON value. + InterfaceNil: nil, + // Interface is omitted since null is an empty JSON value. + Interface: (*string)(nil), + }) + if err != nil { + log.Fatal(err) + } + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println("OmitEmpty:", string(b)) // outputs "Bool", "Int", and "Time" + + // Output: + // OmitZero: { + // "Struct": {}, + // "Slice": [], + // "Map": {}, + // "Pointer": "", + // "Interface": null + // } + // OmitEmpty: { + // "Bool": false, + // "Int": 0, + // "Time": "0001-01-01T00:00:00Z" + // } +} + +// JSON objects can be inlined within a parent object similar to +// how Go structs can be embedded within a parent struct. +// The inlining rules are similar to those of Go embedding, +// but operates upon the JSON namespace. +func Example_inlinedFields() { + // Base is embedded within Container. + type Base struct { + // ID is promoted into the JSON object for Container. + ID string + // Type is ignored due to presence of Container.Type. + Type string + // Time cancels out with Container.Inlined.Time. + Time time.Time + } + // Other is embedded within Container. + type Other struct{ Cost float64 } + // Container embeds Base and Other. + type Container struct { + // Base is an embedded struct and is implicitly JSON inlined. + Base + // Type takes precedence over Base.Type. + Type int + // Inlined is a named Go field, but is explicitly JSON inlined. + Inlined struct { + // User is promoted into the JSON object for Container. + User string + // Time cancels out with Base.Time. + Time string + } `json:",inline"` + // ID does not conflict with Base.ID since the JSON name is different. + ID string `json:"uuid"` + // Other is not JSON inlined since it has an explicit JSON name. + Other `json:"other"` + } + + // Format an empty Container to show what fields are JSON serializable. + var input Container + b, err := json.Marshal(&input) + if err != nil { + log.Fatal(err) + } + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println(string(b)) + + // Output: + // { + // "ID": "", + // "Type": 0, + // "User": "", + // "uuid": "", + // "other": { + // "Cost": 0 + // } + // } +} + +// Due to version skew, the set of JSON object members known at compile-time +// may differ from the set of members encountered at execution-time. +// As such, it may be useful to have finer grain handling of unknown members. +// This package supports preserving, rejecting, or discarding such members. +func Example_unknownMembers() { + const input = `{ + "Name": "Teal", + "Value": "#008080", + "WebSafe": false + }` + type Color struct { + Name string + Value string + + // Unknown is a Go struct field that holds unknown JSON object members. + // It is marked as having this behavior with the "unknown" tag option. + // + // The type may be a jsontext.Value or map[string]T. + Unknown jsontext.Value `json:",unknown"` + } + + // By default, unknown members are stored in a Go field marked as "unknown" + // or ignored if no such field exists. + var color Color + err := json.Unmarshal([]byte(input), &color) + if err != nil { + log.Fatal(err) + } + fmt.Println("Unknown members:", string(color.Unknown)) + + // Specifying RejectUnknownMembers causes Unmarshal + // to reject the presence of any unknown members. + err = json.Unmarshal([]byte(input), new(Color), json.RejectUnknownMembers(true)) + var serr *json.SemanticError + if errors.As(err, &serr) && serr.Err == json.ErrUnknownName { + fmt.Println("Unmarshal error:", serr.Err, strconv.Quote(serr.JSONPointer.LastToken())) + } + + // By default, Marshal preserves unknown members stored in + // a Go struct field marked as "unknown". + b, err := json.Marshal(color) + if err != nil { + log.Fatal(err) + } + fmt.Println("Output with unknown members: ", string(b)) + + // Specifying DiscardUnknownMembers causes Marshal + // to discard any unknown members. + b, err = json.Marshal(color, json.DiscardUnknownMembers(true)) + if err != nil { + log.Fatal(err) + } + fmt.Println("Output without unknown members:", string(b)) + + // Output: + // Unknown members: {"WebSafe":false} + // Unmarshal error: unknown object member name "WebSafe" + // Output with unknown members: {"Name":"Teal","Value":"#008080","WebSafe":false} + // Output without unknown members: {"Name":"Teal","Value":"#008080"} +} + +// The "format" tag option can be used to alter the formatting of certain types. +func Example_formatFlags() { + value := struct { + BytesBase64 []byte `json:",format:base64"` + BytesHex [8]byte `json:",format:hex"` + BytesArray []byte `json:",format:array"` + FloatNonFinite float64 `json:",format:nonfinite"` + MapEmitNull map[string]any `json:",format:emitnull"` + SliceEmitNull []any `json:",format:emitnull"` + TimeDateOnly time.Time `json:",format:'2006-01-02'"` + TimeUnixSec time.Time `json:",format:unix"` + DurationSecs time.Duration `json:",format:sec"` + DurationNanos time.Duration `json:",format:nano"` + }{ + BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + BytesArray: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + FloatNonFinite: math.NaN(), + MapEmitNull: nil, + SliceEmitNull: nil, + TimeDateOnly: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + TimeUnixSec: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + DurationSecs: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, + DurationNanos: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, + } + + b, err := json.Marshal(&value) + if err != nil { + log.Fatal(err) + } + (*jsontext.Value)(&b).Indent() // indent for readability + fmt.Println(string(b)) + + // Output: + // { + // "BytesBase64": "ASNFZ4mrze8=", + // "BytesHex": "0123456789abcdef", + // "BytesArray": [ + // 1, + // 35, + // 69, + // 103, + // 137, + // 171, + // 205, + // 239 + // ], + // "FloatNonFinite": "NaN", + // "MapEmitNull": null, + // "SliceEmitNull": null, + // "TimeDateOnly": "2000-01-01", + // "TimeUnixSec": 946684800, + // "DurationSecs": 45296.007008009, + // "DurationNanos": 45296007008009 + // } +} + +// When implementing HTTP endpoints, it is common to be operating with an +// [io.Reader] and an [io.Writer]. The [MarshalWrite] and [UnmarshalRead] functions +// assist in operating on such input/output types. +// [UnmarshalRead] reads the entirety of the [io.Reader] to ensure that [io.EOF] +// is encountered without any unexpected bytes after the top-level JSON value. +func Example_serveHTTP() { + // Some global state maintained by the server. + var n int64 + + // The "add" endpoint accepts a POST request with a JSON object + // containing a number to atomically add to the server's global counter. + // It returns the updated value of the counter. + http.HandleFunc("/api/add", func(w http.ResponseWriter, r *http.Request) { + // Unmarshal the request from the client. + var val struct{ N int64 } + if err := json.UnmarshalRead(r.Body, &val); err != nil { + // Inability to unmarshal the input suggests a client-side problem. + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Marshal a response from the server. + val.N = atomic.AddInt64(&n, val.N) + if err := json.MarshalWrite(w, &val); err != nil { + // Inability to marshal the output suggests a server-side problem. + // This error is not always observable by the client since + // json.MarshalWrite may have already written to the output. + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) +} + +// Some Go types have a custom JSON representation where the implementation +// is delegated to some external package. Consequently, the "json" package +// will not know how to use that external implementation. +// For example, the [google.golang.org/protobuf/encoding/protojson] package +// implements JSON for all [google.golang.org/protobuf/proto.Message] types. +// [WithMarshalers] and [WithUnmarshalers] can be used +// to configure "json" and "protojson" to cooperate together. +func Example_protoJSON() { + // Let protoMessage be "google.golang.org/protobuf/proto".Message. + type protoMessage interface{ ProtoReflect() } + // Let foopbMyMessage be a concrete implementation of proto.Message. + type foopbMyMessage struct{ protoMessage } + // Let protojson be an import of "google.golang.org/protobuf/encoding/protojson". + var protojson struct { + Marshal func(protoMessage) ([]byte, error) + Unmarshal func([]byte, protoMessage) error + } + + // This value mixes both non-proto.Message types and proto.Message types. + // It should use the "json" package to handle non-proto.Message types and + // should use the "protojson" package to handle proto.Message types. + var value struct { + // GoStruct does not implement proto.Message and + // should use the default behavior of the "json" package. + GoStruct struct { + Name string + Age int + } + + // ProtoMessage implements proto.Message and + // should be handled using protojson.Marshal. + ProtoMessage *foopbMyMessage + } + + // Marshal using protojson.Marshal for proto.Message types. + b, err := json.Marshal(&value, + // Use protojson.Marshal as a type-specific marshaler. + json.WithMarshalers(json.MarshalFunc(protojson.Marshal))) + if err != nil { + log.Fatal(err) + } + + // Unmarshal using protojson.Unmarshal for proto.Message types. + err = json.Unmarshal(b, &value, + // Use protojson.Unmarshal as a type-specific unmarshaler. + json.WithUnmarshalers(json.UnmarshalFunc(protojson.Unmarshal))) + if err != nil { + log.Fatal(err) + } +} + +// Many error types are not serializable since they tend to be Go structs +// without any exported fields (e.g., errors constructed with [errors.New]). +// Some applications, may desire to marshal an error as a JSON string +// even if these errors cannot be unmarshaled. +func ExampleWithMarshalers_errors() { + // Response to serialize with some Go errors encountered. + response := []struct { + Result string `json:",omitzero"` + Error error `json:",omitzero"` + }{ + {Result: "Oranges are a good source of Vitamin C."}, + {Error: &strconv.NumError{Func: "ParseUint", Num: "-1234", Err: strconv.ErrSyntax}}, + {Error: &os.PathError{Op: "ReadFile", Path: "/path/to/secret/file", Err: os.ErrPermission}}, + } + + b, err := json.Marshal(&response, + // Intercept every attempt to marshal an error type. + json.WithMarshalers(json.JoinMarshalers( + // Suppose we consider strconv.NumError to be a safe to serialize: + // this type-specific marshal function intercepts this type + // and encodes the error message as a JSON string. + json.MarshalToFunc(func(enc *jsontext.Encoder, err *strconv.NumError) error { + return enc.WriteToken(jsontext.String(err.Error())) + }), + // Error messages may contain sensitive information that may not + // be appropriate to serialize. For all errors not handled above, + // report some generic error message. + json.MarshalFunc(func(error) ([]byte, error) { + return []byte(`"internal server error"`), nil + }), + )), + jsontext.Multiline(true)) // expand for readability + if err != nil { + log.Fatal(err) + } + fmt.Println(string(b)) + + // Output: + // [ + // { + // "Result": "Oranges are a good source of Vitamin C." + // }, + // { + // "Error": "strconv.ParseUint: parsing \"-1234\": invalid syntax" + // }, + // { + // "Error": "internal server error" + // } + // ] +} + +// In some applications, the exact precision of JSON numbers needs to be +// preserved when unmarshaling. This can be accomplished using a type-specific +// unmarshal function that intercepts all any types and pre-populates the +// interface value with a [jsontext.Value], which can represent a JSON number exactly. +func ExampleWithUnmarshalers_rawNumber() { + // Input with JSON numbers beyond the representation of a float64. + const input = `[false, 1e-1000, 3.141592653589793238462643383279, 1e+1000, true]` + + var value any + err := json.Unmarshal([]byte(input), &value, + // Intercept every attempt to unmarshal into the any type. + json.WithUnmarshalers( + json.UnmarshalFromFunc(func(dec *jsontext.Decoder, val *any) error { + // If the next value to be decoded is a JSON number, + // then provide a concrete Go type to unmarshal into. + if dec.PeekKind() == '0' { + *val = jsontext.Value(nil) + } + // Return SkipFunc to fallback on default unmarshal behavior. + return json.SkipFunc + }), + )) + if err != nil { + log.Fatal(err) + } + fmt.Println(value) + + // Sanity check. + want := []any{false, jsontext.Value("1e-1000"), jsontext.Value("3.141592653589793238462643383279"), jsontext.Value("1e+1000"), true} + if !reflect.DeepEqual(value, want) { + log.Fatalf("value mismatch:\ngot %v\nwant %v", value, want) + } + + // Output: + // [false 1e-1000 3.141592653589793238462643383279 1e+1000 true] +} + +// When using JSON for parsing configuration files, +// the parsing logic often needs to report an error with a line and column +// indicating where in the input an error occurred. +func ExampleWithUnmarshalers_recordOffsets() { + // Hypothetical configuration file. + const input = `[ + {"Source": "192.168.0.100:1234", "Destination": "192.168.0.1:80"}, + {"Source": "192.168.0.251:4004"}, + {"Source": "192.168.0.165:8080", "Destination": "0.0.0.0:80"} + ]` + type Tunnel struct { + Source netip.AddrPort + Destination netip.AddrPort + + // ByteOffset is populated during unmarshal with the byte offset + // within the JSON input of the JSON object for this Go struct. + ByteOffset int64 `json:"-"` // metadata to be ignored for JSON serialization + } + + var tunnels []Tunnel + err := json.Unmarshal([]byte(input), &tunnels, + // Intercept every attempt to unmarshal into the Tunnel type. + json.WithUnmarshalers( + json.UnmarshalFromFunc(func(dec *jsontext.Decoder, tunnel *Tunnel) error { + // Decoder.InputOffset reports the offset after the last token, + // but we want to record the offset before the next token. + // + // Call Decoder.PeekKind to buffer enough to reach the next token. + // Add the number of leading whitespace, commas, and colons + // to locate the start of the next token. + dec.PeekKind() + unread := dec.UnreadBuffer() + n := len(unread) - len(bytes.TrimLeft(unread, " \n\r\t,:")) + tunnel.ByteOffset = dec.InputOffset() + int64(n) + + // Return SkipFunc to fallback on default unmarshal behavior. + return json.SkipFunc + }), + )) + if err != nil { + log.Fatal(err) + } + + // lineColumn converts a byte offset into a one-indexed line and column. + // The offset must be within the bounds of the input. + lineColumn := func(input string, offset int) (line, column int) { + line = 1 + strings.Count(input[:offset], "\n") + column = 1 + offset - (strings.LastIndex(input[:offset], "\n") + len("\n")) + return line, column + } + + // Verify that the configuration file is valid. + for _, tunnel := range tunnels { + if !tunnel.Source.IsValid() || !tunnel.Destination.IsValid() { + line, column := lineColumn(input, int(tunnel.ByteOffset)) + fmt.Printf("%d:%d: source and destination must both be specified", line, column) + } + } + + // Output: + // 3:3: source and destination must both be specified +} diff --git a/src/encoding/json/v2/fields.go b/src/encoding/json/v2/fields.go new file mode 100644 index 0000000000..9413189c08 --- /dev/null +++ b/src/encoding/json/v2/fields.go @@ -0,0 +1,646 @@ +// Copyright 2021 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 + +package json + +import ( + "cmp" + "errors" + "fmt" + "io" + "reflect" + "slices" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonwire" +) + +type isZeroer interface { + IsZero() bool +} + +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 +} + +// reindex recomputes index to avoid bounds check during runtime. +// +// During the construction of each [structField] in [makeStructFields], +// the index field is 0-indexed. However, before it returns, +// the 0th field is stored in index0 and index stores the remainder. +func (sf *structFields) reindex() { + reindex := func(f *structField) { + f.index0 = f.index[0] + f.index = f.index[1:] + if len(f.index) == 0 { + f.index = nil // avoid pinning the backing slice + } + } + for i := range sf.flattened { + reindex(&sf.flattened[i]) + } + if sf.inlinedFallback != nil { + reindex(sf.inlinedFallback) + } +} + +// lookupByFoldedName looks up name by a case-insensitive match +// that also ignores the presence of dashes and underscores. +func (fs *structFields) lookupByFoldedName(name []byte) []*structField { + return fs.byFoldedName[string(foldName(name))] +} + +type structField struct { + id int // unique numeric ID in breadth-first ordering + index0 int // 0th index into a struct according to [reflect.Type.FieldByIndex] + index []int // 1st index and remainder according to [reflect.Type.FieldByIndex] + typ reflect.Type + fncs *arshaler + isZero func(addressableValue) bool + isEmpty func(addressableValue) bool + fieldOptions +} + +var errNoExportedFields = errors.New("Go struct has no exported fields") + +func makeStructFields(root reflect.Type) (fs structFields, serr *SemanticError) { + orErrorf := func(serr *SemanticError, t reflect.Type, f string, a ...any) *SemanticError { + return cmp.Or(serr, &SemanticError{GoType: t, Err: fmt.Errorf(f, a...)}) + } + + // Setup a queue for a breath-first search. + var queueIndex int + type queueEntry struct { + typ reflect.Type + index []int + visitChildren bool // whether to recursively visit inlined 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 + for queueIndex < len(queue) { + qe := queue[queueIndex] + queueIndex++ + + t := qe.typ + inlinedFallbackIndex := -1 // index of last inlined 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 + for i := range t.NumField() { + sf := t.Field(i) + _, hasTag := sf.Tag.Lookup("json") + hasAnyJSONTag = hasAnyJSONTag || hasTag + options, ignored, err := parseFieldOptions(sf) + if err != nil { + serr = cmp.Or(serr, &SemanticError{GoType: t, Err: err}) + } + if ignored { + continue + } + hasAnyJSONField = true + f := structField{ + // Allocate a new slice (len=N+1) to hold both + // the parent index (len=N) and the current index (len=1). + // Do this to avoid clobbering the memory of the parent index. + index: append(append(make([]int, 0, len(qe.index)+1), qe.index...), i), + typ: sf.Type, + fieldOptions: options, + } + if sf.Anonymous && !f.hasName { + if indirectType(f.typ).Kind() != reflect.Struct { + serr = orErrorf(serr, t, "embedded Go struct field %s of non-struct type must be explicitly given a JSON name", sf.Name) + } else { + f.inline = true // implied by use of Go embedding without an explicit name + } + } + if f.inline || f.unknown { + // Handle an inlined field that serializes to/from + // zero or more JSON object members. + + 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.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 + } + } + + // Reject any types with custom serialization otherwise + // it becomes impossible to know what sub-fields to inline. + tf := indirectType(f.typ) + if implementsAny(tf, allMethodTypes...) && tf != jsontextValueType { + serr = orErrorf(serr, t, "inlined Go struct field %s of type %s must not implement marshal or unmarshal methods", sf.Name, tf) + } + + // Handle an inlined field that serializes to/from + // a finite number of JSON object members backed by a Go struct. + if tf.Kind() == reflect.Struct { + if f.unknown { + serr = orErrorf(serr, t, "inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf) + continue // invalid inlined field; treat as ignored + } + if qe.visitChildren { + queue = append(queue, queueEntry{tf, f.index, !seen[tf]}) + } + seen[tf] = true + continue + } else if !sf.IsExported() { + serr = orErrorf(serr, t, "inlined Go struct field %s is not exported", sf.Name) + continue // invalid inlined field; treat as ignored + } + + // Handle an inlined field that serializes to/from any number of + // JSON object members back by a Go map or jsontext.Value. + switch { + case tf == jsontextValueType: + f.fncs = nil // specially handled in arshal_inlined.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 + } + 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 + } + + // 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. + } + inlinedFallbackIndex = i + + inlinedFallbacks = append(inlinedFallbacks, f) + } else { + // Handle normal Go struct field that serializes to/from + // a single JSON object member. + + // Unexported fields cannot be serialized except for + // embedded fields of a struct type, + // which might promote exported fields of their own. + if !sf.IsExported() { + tf := indirectType(f.typ) + if !(sf.Anonymous && tf.Kind() == reflect.Struct) { + serr = orErrorf(serr, t, "Go struct field %s is not exported", sf.Name) + continue + } + // Unfortunately, methods on the unexported field + // still cannot be called. + if implementsAny(tf, allMethodTypes...) || + (f.omitzero && implementsAny(tf, isZeroerType)) { + serr = orErrorf(serr, t, "Go struct field %s is not exported for method calls", sf.Name) + continue + } + } + + // Provide a function that uses a type's IsZero method. + switch { + case sf.Type.Kind() == reflect.Interface && sf.Type.Implements(isZeroerType): + f.isZero = func(va addressableValue) bool { + // Avoid panics calling IsZero on a nil interface or + // non-nil interface with nil pointer. + return va.IsNil() || (va.Elem().Kind() == reflect.Pointer && va.Elem().IsNil()) || va.Interface().(isZeroer).IsZero() + } + case sf.Type.Kind() == reflect.Pointer && sf.Type.Implements(isZeroerType): + f.isZero = func(va addressableValue) bool { + // Avoid panics calling IsZero on nil pointer. + return va.IsNil() || va.Interface().(isZeroer).IsZero() + } + case sf.Type.Implements(isZeroerType): + f.isZero = func(va addressableValue) bool { return va.Interface().(isZeroer).IsZero() } + case reflect.PointerTo(sf.Type).Implements(isZeroerType): + f.isZero = func(va addressableValue) bool { return va.Addr().Interface().(isZeroer).IsZero() } + } + + // Provide a function that can determine whether the value would + // serialize as an empty JSON value. + switch sf.Type.Kind() { + case reflect.String, reflect.Map, reflect.Array, reflect.Slice: + f.isEmpty = func(va addressableValue) bool { return va.Len() == 0 } + case reflect.Pointer, reflect.Interface: + f.isEmpty = func(va addressableValue) bool { return va.IsNil() } + } + + // Reject multiple fields with same name within the same struct. + if j, ok := namesIndex[f.name]; ok { + serr = orErrorf(serr, t, "Go struct fields %s and %s conflict over JSON object name %q", t.Field(j).Name, sf.Name, f.name) + // Still append f to allFields as there is still a + // check for a dominant field before returning. + } + namesIndex[f.name] = i + + f.id = len(allFields) + f.fncs = lookupArshaler(sf.Type) + allFields = append(allFields, f) + } + } + + // NOTE: New users to the json package are occasionally surprised that + // unexported fields are ignored. This occurs by necessity due to our + // inability to directly introspect such fields with Go reflection + // without the use of unsafe. + // + // To reduce friction here, refuse to serialize any Go struct that + // has no JSON serializable fields, has at least one Go struct field, + // and does not have any `json` tags present. For example, + // errors returned by errors.New would fail to serialize. + isEmptyStruct := t.NumField() == 0 + if !isEmptyStruct && !hasAnyJSONTag && !hasAnyJSONField { + serr = cmp.Or(serr, &SemanticError{GoType: t, Err: errNoExportedFields}) + } + } + + // Sort the fields by exact name (breaking ties by depth and + // then by presence of an explicitly provided JSON name). + // Select the dominant field from each set of fields with the same name. + // If multiple fields have the same name, then the dominant field + // is the one that exists alone at the shallowest depth, + // or the one that is uniquely tagged with a JSON name. + // Otherwise, no dominant field exists for the set. + flattened := allFields[:0] + slices.SortStableFunc(allFields, func(x, y structField) int { + return cmp.Or( + strings.Compare(x.name, y.name), + cmp.Compare(len(x.index), len(y.index)), + boolsCompare(!x.hasName, !y.hasName)) + }) + for len(allFields) > 0 { + n := 1 // number of fields with the same exact name + for n < len(allFields) && allFields[n-1].name == allFields[n].name { + n++ + } + if n == 1 || len(allFields[0].index) != len(allFields[1].index) || allFields[0].hasName != allFields[1].hasName { + flattened = append(flattened, allFields[0]) // only keep field if there is a dominant field + } + allFields = allFields[n:] + } + + // Sort the fields according to a breadth-first ordering + // so that we can re-number IDs with the smallest possible values. + // This optimizes use of uintSet such that it fits in the 64-entry bit set. + slices.SortFunc(flattened, func(x, y structField) int { + return cmp.Compare(x.id, y.id) + }) + for i := range flattened { + flattened[i].id = i + } + + // Sort the fields according to a depth-first ordering + // as the typical order that fields are marshaled. + slices.SortFunc(flattened, func(x, y structField) int { + return slices.Compare(x.index, y.index) + }) + + // Compute the mapping of fields in the byActualName map. + // Pre-fold all names so that we can lookup folded names quickly. + fs = structFields{ + flattened: flattened, + byActualName: make(map[string]*structField, len(flattened)), + byFoldedName: make(map[string][]*structField, len(flattened)), + } + for i, f := range fs.flattened { + foldedName := string(foldName([]byte(f.name))) + fs.byActualName[f.name] = &fs.flattened[i] + fs.byFoldedName[foldedName] = append(fs.byFoldedName[foldedName], &fs.flattened[i]) + } + for foldedName, fields := range fs.byFoldedName { + if len(fields) > 1 { + // The precedence order for conflicting ignoreCase names + // is by breadth-first order, rather than depth-first order. + slices.SortFunc(fields, func(x, y *structField) int { + return cmp.Compare(x.id, y.id) + }) + 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 + } + fs.reindex() + return fs, serr +} + +// indirectType unwraps one level of pointer indirection +// similar to how Go only allows embedding either T or *T, +// but not **T or P (which is a named pointer). +func indirectType(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Pointer && t.Name() == "" { + t = t.Elem() + } + return t +} + +// matchFoldedName matches a case-insensitive name depending on the options. +// It assumes that foldName(f.name) == foldName(name). +// +// Case-insensitive matching is used if the `case:ignore` tag option is specified +// or the MatchCaseInsensitiveNames call option is specified +// (and the `case:strict` tag option is not specified). +// Functionally, the `case:ignore` and `case:strict` tag options take precedence. +// +// The v1 definition of case-insensitivity operated under strings.EqualFold +// and would strictly compare dashes and underscores, +// while the v2 definition would ignore the presence of dashes and underscores. +// Thus, if the MatchCaseSensitiveDelimiter call option is specified, +// the match is further restricted to using strings.EqualFold. +func (f *structField) matchFoldedName(name []byte, flags *jsonflags.Flags) bool { + if f.casing == caseIgnore || (flags.Get(jsonflags.MatchCaseInsensitiveNames) && f.casing != caseStrict) { + if !flags.Get(jsonflags.MatchCaseSensitiveDelimiter) || strings.EqualFold(string(name), f.name) { + return true + } + } + return false +} + +const ( + caseIgnore = 1 + caseStrict = 2 +) + +type fieldOptions struct { + name string + quotedName string // quoted name per RFC 8785, section 3.2.2.2. + hasName bool + nameNeedEscape bool + casing int8 // either 0, caseIgnore, or caseStrict + inline bool + unknown bool + omitzero bool + omitempty bool + string bool + format string +} + +// parseFieldOptions parses the `json` tag in a Go struct field as +// a structured set of options configuring parameters such as +// the JSON member name and other features. +func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool, err error) { + tag, hasTag := sf.Tag.Lookup("json") + + // Check whether this field is explicitly ignored. + if tag == "-" { + return fieldOptions{}, true, nil + } + + // Check whether this field is unexported and not embedded, + // which Go reflection cannot mutate for the sake of serialization. + // + // An embedded field of an unexported type is still capable of + // forwarding exported fields, which may be JSON serialized. + // This technically operates on the edge of what is permissible by + // the Go language, but the most recent decision is to permit this. + // + // See https://go.dev/issue/24153 and https://go.dev/issue/32772. + if !sf.IsExported() && !sf.Anonymous { + // Tag options specified on an unexported field suggests user error. + if hasTag { + err = cmp.Or(err, fmt.Errorf("unexported Go struct field %s cannot have non-ignored `json:%q` tag", sf.Name, tag)) + } + 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. + 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. + n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool { + return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes + })) + name := tag[:n] + + // If the next character is not a comma, then the name is either + // malformed (if n > 0) or a single-quoted name. + // In either case, call consumeTagOption to handle it further. + var err2 error + if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) { + name, n, err2 = consumeTagOption(tag) + if err2 != nil { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2)) + } + } + if !utf8.ValidString(name) { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has JSON object name %q with invalid UTF-8", sf.Name, name)) + name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError + } + if err2 == nil { + out.hasName = true + out.name = name + } + tag = tag[n:] + } + b, _ := jsonwire.AppendQuote(nil, out.name, &jsonflags.Flags{}) + out.quotedName = string(b) + out.nameNeedEscape = jsonwire.NeedEscape(out.name) + + // Handle any additional tag options (if any). + var wasFormat bool + seenOpts := make(map[string]bool) + for len(tag) > 0 { + // Consume comma delimiter. + if tag[0] != ',' { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid character %q before next option (expecting ',')", sf.Name, tag[0])) + } else { + tag = tag[len(","):] + if len(tag) == 0 { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: invalid trailing ',' character", sf.Name)) + break + } + } + + // Consume and process the tag option. + opt, n, err2 := consumeTagOption(tag) + if err2 != nil { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed `json` tag: %v", sf.Name, err2)) + } + rawOpt := tag[:n] + tag = tag[n:] + switch { + case wasFormat: + err = cmp.Or(err, fmt.Errorf("Go struct field %s has `format` tag option that was not specified last", sf.Name)) + case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "": + err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", sf.Name, rawOpt, opt)) + } + switch opt { + case "case": + if !strings.HasPrefix(tag, ":") { + err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `case` tag option; specify `case:ignore` or `case:strict` instead", sf.Name)) + break + } + tag = tag[len(":"):] + opt, n, err2 := consumeTagOption(tag) + if err2 != nil { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `case` tag option: %v", sf.Name, err2)) + break + } + rawOpt := tag[:n] + tag = tag[n:] + if strings.HasPrefix(rawOpt, "'") { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead", sf.Name, rawOpt, opt)) + } + switch opt { + case "ignore": + out.casing |= caseIgnore + case "strict": + out.casing |= caseStrict + default: + err = cmp.Or(err, fmt.Errorf("Go struct field %s has unknown `case:%s` tag value", sf.Name, rawOpt)) + } + case "inline": + out.inline = true + case "unknown": + out.unknown = true + case "omitzero": + out.omitzero = true + case "omitempty": + out.omitempty = true + case "string": + out.string = true + case "format": + if !strings.HasPrefix(tag, ":") { + err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name)) + break + } + tag = tag[len(":"):] + opt, n, err2 := consumeTagOption(tag) + if err2 != nil { + err = cmp.Or(err, fmt.Errorf("Go struct field %s has malformed value for `format` tag option: %v", sf.Name, err2)) + break + } + tag = tag[n:] + out.format = opt + wasFormat = true + default: + // Reject keys that resemble one of the supported options. + // 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": + err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt)) + } + + // NOTE: Everything else is ignored. This does not mean it is + // forward compatible to insert arbitrary tag options since + // a future version of this package may understand that tag. + } + + // Reject duplicates. + switch { + case out.casing == caseIgnore|caseStrict: + err = cmp.Or(err, fmt.Errorf("Go struct field %s cannot have both `case:ignore` and `case:strict` tag options", sf.Name)) + case seenOpts[opt]: + err = cmp.Or(err, fmt.Errorf("Go struct field %s has duplicate appearance of `%s` tag option", sf.Name, rawOpt)) + } + seenOpts[opt] = true + } + return out, false, err +} + +// consumeTagOption consumes the next option, +// which is either a Go identifier or a single-quoted string. +// If the next option is invalid, it returns all of in until the next comma, +// and reports an error. +func consumeTagOption(in string) (string, int, error) { + // For legacy compatibility with v1, assume options are comma-separated. + i := strings.IndexByte(in, ',') + if i < 0 { + i = len(in) + } + + switch r, _ := utf8.DecodeRuneInString(in); { + // Option as a Go identifier. + case r == '_' || unicode.IsLetter(r): + n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit)) + return in[:n], n, nil + // Option as a single-quoted string. + case 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 + // verbatim in a struct tag. + // + // Convert a single-quoted string to a double-quote string and rely on + // strconv.Unquote to handle the rest. + var inEscape bool + b := []byte{'"'} + n := len(`'`) + for len(in) > n { + r, rn := utf8.DecodeRuneInString(in[n:]) + switch { + case inEscape: + if r == '\'' { + b = b[:len(b)-1] // remove escape character: `\'` => `'` + } + inEscape = false + case r == '\\': + inEscape = true + case r == '"': + b = append(b, '\\') // insert escape character: `"` => `\"` + case r == '\'': + b = append(b, '"') + n += len(`'`) + out, err := strconv.Unquote(string(b)) + if err != nil { + return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n]) + } + return out, n, nil + } + b = append(b, in[n:][:rn]...) + n += rn + } + if n > 10 { + n = 10 // limit the amount of context printed in the error + } + return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n]) + case len(in) == 0: + return in[:i], i, io.ErrUnexpectedEOF + default: + return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r) + } +} + +func isLetterOrDigit(r rune) bool { + return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r) +} + +// boolsCompare compares x and y, ordering false before true. +func boolsCompare(x, y bool) int { + switch { + case !x && y: + return -1 + default: + return 0 + case x && !y: + return +1 + } +} diff --git a/src/encoding/json/v2/fields_test.go b/src/encoding/json/v2/fields_test.go new file mode 100644 index 0000000000..1c36f80905 --- /dev/null +++ b/src/encoding/json/v2/fields_test.go @@ -0,0 +1,821 @@ +// Copyright 2021 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 + +package json + +import ( + "encoding" + "errors" + "reflect" + "testing" + + "encoding/json/internal/jsontest" + "encoding/json/jsontext" +) + +type unexported struct{} + +func TestMakeStructFields(t *testing.T) { + type Embed struct { + Foo string + } + type Recursive struct { + A string + *Recursive `json:",inline"` + B string + } + type MapStringAny map[string]any + tests := []struct { + name jsontest.CaseName + in any + want structFields + wantErr error + }{{ + name: jsontest.Name("Names"), + in: struct { + F1 string + F2 string `json:"-"` + F3 string `json:"json_name"` + f3 string + F5 string `json:"json_name_nocase,case:ignore"` + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "F1", quotedName: `"F1"`}}, + {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "json_name", quotedName: `"json_name"`, hasName: true}}, + {id: 2, index: []int{4}, typ: stringType, fieldOptions: fieldOptions{name: "json_name_nocase", quotedName: `"json_name_nocase"`, hasName: true, casing: caseIgnore}}, + }, + }, + }, { + name: jsontest.Name("BreadthFirstSearch"), + in: struct { + L1A string + L1B struct { + L2A string + L2B struct { + L3A string + } `json:",inline"` + L2C string + } `json:",inline"` + L1C string + L1D struct { + L2D string + L2E struct { + L3B string + } `json:",inline"` + L2F string + } `json:",inline"` + L1E string + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "L1A", quotedName: `"L1A"`}}, + {id: 3, index: []int{1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L2A", quotedName: `"L2A"`}}, + {id: 7, index: []int{1, 1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L3A", quotedName: `"L3A"`}}, + {id: 4, index: []int{1, 2}, typ: stringType, fieldOptions: fieldOptions{name: "L2C", quotedName: `"L2C"`}}, + {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "L1C", quotedName: `"L1C"`}}, + {id: 5, index: []int{3, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L2D", quotedName: `"L2D"`}}, + {id: 8, index: []int{3, 1, 0}, typ: stringType, fieldOptions: fieldOptions{name: "L3B", quotedName: `"L3B"`}}, + {id: 6, index: []int{3, 2}, typ: stringType, fieldOptions: fieldOptions{name: "L2F", quotedName: `"L2F"`}}, + {id: 2, index: []int{4}, typ: stringType, fieldOptions: fieldOptions{name: "L1E", quotedName: `"L1E"`}}, + }, + }, + }, { + name: jsontest.Name("NameResolution"), + in: struct { + X1 struct { + X struct { + A string // loses in precedence to A + B string // cancels out with X2.X.B + D string // loses in precedence to D + } `json:",inline"` + } `json:",inline"` + X2 struct { + X struct { + B string // cancels out with X1.X.B + C string + D string // loses in precedence to D + } `json:",inline"` + } `json:",inline"` + A string // takes precedence over X1.X.A + D string // takes precedence over X1.X.D and X2.X.D + }{}, + want: structFields{ + flattened: []structField{ + {id: 2, index: []int{1, 0, 1}, typ: stringType, fieldOptions: fieldOptions{name: "C", quotedName: `"C"`}}, + {id: 0, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "A", quotedName: `"A"`}}, + {id: 1, index: []int{3}, typ: stringType, fieldOptions: fieldOptions{name: "D", quotedName: `"D"`}}, + }, + }, + }, { + name: jsontest.Name("NameResolution/ExplicitNameUniquePrecedence"), + in: struct { + X1 struct { + A string // loses in precedence to X2.A + } `json:",inline"` + X2 struct { + A string `json:"A"` + } `json:",inline"` + X3 struct { + A string // loses in precedence to X2.A + } `json:",inline"` + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{1, 0}, typ: stringType, fieldOptions: fieldOptions{hasName: true, name: "A", quotedName: `"A"`}}, + }, + }, + }, { + name: jsontest.Name("NameResolution/ExplicitNameCancelsOut"), + in: struct { + X1 struct { + A string // loses in precedence to X2.A or X3.A + } `json:",inline"` + X2 struct { + A string `json:"A"` // cancels out with X3.A + } `json:",inline"` + X3 struct { + A string `json:"A"` // cancels out with X2.A + } `json:",inline"` + }{}, + want: structFields{flattened: []structField{}}, + }, { + name: jsontest.Name("Embed/Implicit"), + in: struct { + Embed + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0, 0}, typ: stringType, fieldOptions: fieldOptions{name: "Foo", quotedName: `"Foo"`}}, + }, + }, + }, { + name: jsontest.Name("Embed/Explicit"), + in: struct { + Embed `json:",inline"` + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0, 0}, typ: stringType, fieldOptions: fieldOptions{name: "Foo", quotedName: `"Foo"`}}, + }, + }, + }, { + name: jsontest.Name("Recursive"), + in: struct { + A string + Recursive `json:",inline"` + C string + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{name: "A", quotedName: `"A"`}}, + {id: 2, index: []int{1, 2}, typ: stringType, fieldOptions: fieldOptions{name: "B", quotedName: `"B"`}}, + {id: 1, index: []int{2}, typ: stringType, fieldOptions: fieldOptions{name: "C", quotedName: `"C"`}}, + }, + }, + }, { + name: jsontest.Name("InlinedFallback/Cancelation"), + in: struct { + X1 struct { + X jsontext.Value `json:",inline"` + } `json:",inline"` + X2 struct { + X map[string]any `json:",unknown"` + } `json:",inline"` + }{}, + want: structFields{}, + }, { + name: jsontest.Name("InlinedFallback/Precedence"), + in: struct { + X1 struct { + X jsontext.Value `json:",inline"` + } `json:",inline"` + X2 struct { + X map[string]any `json:",unknown"` + } `json:",inline"` + X map[string]jsontext.Value `json:",unknown"` + }{}, + want: structFields{ + inlinedFallback: &structField{id: 0, index: []int{2}, typ: T[map[string]jsontext.Value](), fieldOptions: fieldOptions{name: "X", quotedName: `"X"`, unknown: true}}, + }, + }, { + name: jsontest.Name("InlinedFallback/InvalidImplicit"), + in: struct { + MapStringAny + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0}, typ: reflect.TypeOf(MapStringAny(nil)), fieldOptions: fieldOptions{name: "MapStringAny", quotedName: `"MapStringAny"`}}, + }, + }, + wantErr: errors.New("embedded Go struct field MapStringAny of non-struct type must be explicitly given a JSON name"), + }, { + name: jsontest.Name("InvalidUTF8"), + in: struct { + Name string `json:"'\\xde\\xad\\xbe\\xef'"` + }{}, + want: structFields{ + flattened: []structField{ + {id: 0, index: []int{0}, typ: stringType, fieldOptions: fieldOptions{hasName: true, name: "\u07ad\ufffd\ufffd", quotedName: "\"\u07ad\ufffd\ufffd\"", nameNeedEscape: true}}, + }, + }, + wantErr: errors.New(`Go struct field Name has JSON object name "ޭ\xbe\xef" with invalid UTF-8`), + }, { + name: jsontest.Name("DuplicateName"), + in: struct { + A string `json:"same"` + B string `json:"same"` + }{}, + want: structFields{flattened: []structField{}}, + wantErr: errors.New(`Go struct fields A and B conflict over JSON object name "same"`), + }, { + name: jsontest.Name("BothInlineAndUnknown"), + in: struct { + A struct{} `json:",inline,unknown"` + }{}, + wantErr: errors.New("Go struct field A cannot have both `inline` and `unknown` specified"), + }, { + name: jsontest.Name("InlineWithOptions"), + in: struct { + A struct{} `json:",inline,omitempty"` + }{}, + wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"), + }, { + name: jsontest.Name("UnknownWithOptions"), + in: struct { + A map[string]any `json:",inline,omitempty"` + }{}, + want: structFields{inlinedFallback: &structField{ + index: []int{0}, + typ: reflect.TypeFor[map[string]any](), + fieldOptions: fieldOptions{ + name: "A", + quotedName: `"A"`, + inline: true, + }, + }}, + wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"), + }, { + name: jsontest.Name("InlineTextMarshaler"), + in: struct { + A struct{ encoding.TextMarshaler } `json:",inline"` + }{}, + want: structFields{flattened: []structField{{ + index: []int{0, 0}, + typ: reflect.TypeFor[encoding.TextMarshaler](), + fieldOptions: fieldOptions{ + name: "TextMarshaler", + quotedName: `"TextMarshaler"`, + }, + }}}, + wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextMarshaler } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("InlineTextAppender"), + in: struct { + A struct{ encoding.TextAppender } `json:",inline"` + }{}, + want: structFields{flattened: []structField{{ + index: []int{0, 0}, + typ: reflect.TypeFor[encoding.TextAppender](), + fieldOptions: fieldOptions{ + name: "TextAppender", + quotedName: `"TextAppender"`, + }, + }}}, + wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextAppender } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("UnknownJSONMarshaler"), + in: struct { + A struct{ Marshaler } `json:",unknown"` + }{}, + wantErr: errors.New(`inlined Go struct field A of type struct { json.Marshaler } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("InlineJSONMarshalerTo"), + in: struct { + A struct{ MarshalerTo } `json:",inline"` + }{}, + want: structFields{flattened: []structField{{ + index: []int{0, 0}, + typ: reflect.TypeFor[MarshalerTo](), + fieldOptions: fieldOptions{ + name: "MarshalerTo", + quotedName: `"MarshalerTo"`, + }, + }}}, + wantErr: errors.New(`inlined Go struct field A of type struct { json.MarshalerTo } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("UnknownTextUnmarshaler"), + in: struct { + A *struct{ encoding.TextUnmarshaler } `json:",unknown"` + }{}, + wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextUnmarshaler } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("InlineJSONUnmarshaler"), + in: struct { + A *struct{ Unmarshaler } `json:",inline"` + }{}, + want: structFields{flattened: []structField{{ + index: []int{0, 0}, + typ: reflect.TypeFor[Unmarshaler](), + fieldOptions: fieldOptions{ + name: "Unmarshaler", + quotedName: `"Unmarshaler"`, + }, + }}}, + wantErr: errors.New(`inlined Go struct field A of type struct { json.Unmarshaler } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("UnknownJSONUnmarshalerFrom"), + in: struct { + A struct{ UnmarshalerFrom } `json:",unknown"` + }{}, + wantErr: errors.New(`inlined Go struct field A of type struct { json.UnmarshalerFrom } must not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("UnknownStruct"), + in: struct { + A struct { + X, Y, Z string + } `json:",unknown"` + }{}, + wantErr: errors.New("inlined Go struct field A of type struct { X string; Y string; Z string } with `unknown` tag must be a Go map of string key or a jsontext.Value"), + }, { + name: jsontest.Name("InlineUnsupported/MapIntKey"), + in: struct { + A map[int]any `json:",unknown"` + }{}, + wantErr: errors.New(`inlined Go struct field A of type map[int]interface {} must be a Go struct, Go map of string key, or jsontext.Value`), + }, { + name: jsontest.Name("InlineUnsupported/MapTextMarshalerStringKey"), + in: struct { + A map[nocaseString]any `json:",inline"` + }{}, + wantErr: errors.New(`inlined map field A of type map[json.nocaseString]interface {} must have a string key that does not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("InlineUnsupported/MapMarshalerStringKey"), + in: struct { + A map[stringMarshalEmpty]any `json:",inline"` + }{}, + wantErr: errors.New(`inlined map field A of type map[json.stringMarshalEmpty]interface {} must have a string key that does not implement marshal or unmarshal methods`), + }, { + name: jsontest.Name("InlineUnsupported/DoublePointer"), + in: struct { + A **struct{} `json:",inline"` + }{}, + wantErr: errors.New(`inlined Go struct field A of type *struct {} must be a Go struct, Go map of string key, or jsontext.Value`), + }, { + name: jsontest.Name("DuplicateInline"), + in: struct { + A map[string]any `json:",inline"` + B jsontext.Value `json:",inline"` + }{}, + wantErr: errors.New(`inlined Go struct fields A and B cannot both be a Go map or jsontext.Value`), + }, { + name: jsontest.Name("DuplicateEmbedInline"), + in: struct { + A MapStringAny `json:",inline"` + B jsontext.Value `json:",inline"` + }{}, + wantErr: errors.New(`inlined Go struct fields A and B cannot both be a Go map or jsontext.Value`), + }} + + for _, tt := range tests { + t.Run(tt.name.Name, func(t *testing.T) { + got, err := makeStructFields(reflect.TypeOf(tt.in)) + + // Sanity check that pointers are consistent. + pointers := make(map[*structField]bool) + for i := range got.flattened { + pointers[&got.flattened[i]] = true + } + for _, f := range got.byActualName { + if !pointers[f] { + t.Errorf("%s: byActualName pointer not in flattened", tt.name.Where) + } + } + for _, fs := range got.byFoldedName { + for _, f := range fs { + if !pointers[f] { + t.Errorf("%s: byFoldedName pointer not in flattened", tt.name.Where) + } + } + } + + // Zero out fields that are incomparable. + for i := range got.flattened { + got.flattened[i].fncs = nil + got.flattened[i].isEmpty = nil + } + if got.inlinedFallback != nil { + got.inlinedFallback.fncs = nil + got.inlinedFallback.isEmpty = nil + } + + // Reproduce maps in want. + tt.want.byActualName = make(map[string]*structField) + for i := range tt.want.flattened { + f := &tt.want.flattened[i] + tt.want.byActualName[f.name] = f + } + tt.want.byFoldedName = make(map[string][]*structField) + for i, f := range tt.want.flattened { + foldedName := string(foldName([]byte(f.name))) + tt.want.byFoldedName[foldedName] = append(tt.want.byFoldedName[foldedName], &tt.want.flattened[i]) + } + + // Only compare underlying error to simplify test logic. + var gotErr error + if err != nil { + gotErr = err.Err + } + + tt.want.reindex() + if !reflect.DeepEqual(got, tt.want) || !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("%s: makeStructFields(%T):\n\tgot (%v, %v)\n\twant (%v, %v)", tt.name.Where, tt.in, got, gotErr, tt.want, tt.wantErr) + } + }) + } +} + +func TestParseTagOptions(t *testing.T) { + tests := []struct { + name jsontest.CaseName + in any // must be a struct with a single field + wantOpts fieldOptions + wantIgnored bool + wantErr error + }{{ + name: jsontest.Name("GoName"), + in: struct { + FieldName int + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + }, { + name: jsontest.Name("GoNameWithOptions"), + in: struct { + FieldName int `json:",inline"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true}, + }, { + name: jsontest.Name("Empty"), + in: struct { + V int `json:""` + }{}, + wantOpts: fieldOptions{name: "V", quotedName: `"V"`}, + }, { + name: jsontest.Name("Unexported"), + in: struct { + v int `json:"Hello"` + }{}, + wantIgnored: true, + wantErr: errors.New("unexported Go struct field v cannot have non-ignored `json:\"Hello\"` tag"), + }, { + name: jsontest.Name("UnexportedEmpty"), + in: struct { + v int `json:""` + }{}, + wantIgnored: true, + wantErr: errors.New("unexported Go struct field v cannot have non-ignored `json:\"\"` tag"), + }, { + name: jsontest.Name("EmbedUnexported"), + in: struct { + unexported + }{}, + wantOpts: fieldOptions{name: "unexported", quotedName: `"unexported"`}, + }, { + name: jsontest.Name("Ignored"), + in: struct { + V int `json:"-"` + }{}, + wantIgnored: true, + }, { + name: jsontest.Name("IgnoredEmbedUnexported"), + in: struct { + unexported `json:"-"` + }{}, + wantIgnored: true, + }, { + name: jsontest.Name("DashComma"), + in: struct { + V int `json:"-,"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "-", quotedName: `"-"`}, + wantErr: errors.New("Go struct field V has malformed `json` tag: invalid trailing ',' character"), + }, { + name: jsontest.Name("QuotedDashName"), + in: struct { + V int `json:"'-'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "-", quotedName: `"-"`}, + }, { + name: jsontest.Name("LatinPunctuationName"), + in: struct { + V int `json:"$%-/"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`}, + }, { + name: jsontest.Name("QuotedLatinPunctuationName"), + in: struct { + V int `json:"'$%-/'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`}, + }, { + name: jsontest.Name("LatinDigitsName"), + in: struct { + V int `json:"0123456789"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`}, + }, { + name: jsontest.Name("QuotedLatinDigitsName"), + in: struct { + V int `json:"'0123456789'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`}, + }, { + name: jsontest.Name("LatinUppercaseName"), + in: struct { + V int `json:"ABCDEFGHIJKLMOPQRSTUVWXYZ"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "ABCDEFGHIJKLMOPQRSTUVWXYZ", quotedName: `"ABCDEFGHIJKLMOPQRSTUVWXYZ"`}, + }, { + name: jsontest.Name("LatinLowercaseName"), + in: struct { + V int `json:"abcdefghijklmnopqrstuvwxyz_"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "abcdefghijklmnopqrstuvwxyz_", quotedName: `"abcdefghijklmnopqrstuvwxyz_"`}, + }, { + name: jsontest.Name("GreekName"), + in: struct { + V string `json:"Ελλάδα"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`}, + }, { + name: jsontest.Name("QuotedGreekName"), + in: struct { + V string `json:"'Ελλάδα'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`}, + }, { + name: jsontest.Name("ChineseName"), + in: struct { + V string `json:"世界"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`}, + }, { + name: jsontest.Name("QuotedChineseName"), + in: struct { + V string `json:"'世界'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`}, + }, { + name: jsontest.Name("PercentSlashName"), + in: struct { + V int `json:"text/html%"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`}, + }, { + name: jsontest.Name("QuotedPercentSlashName"), + in: struct { + V int `json:"'text/html%'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`}, + }, { + name: jsontest.Name("PunctuationName"), + in: struct { + V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` + }{}, + wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`, nameNeedEscape: true}, + }, { + name: jsontest.Name("QuotedPunctuationName"), + in: struct { + V string `json:"'!#$%&()*+-./:;<=>?@[]^_{|}~ '"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`, nameNeedEscape: true}, + }, { + name: jsontest.Name("EmptyName"), + in: struct { + V int `json:"''"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "", quotedName: `""`}, + }, { + name: jsontest.Name("SpaceName"), + in: struct { + V int `json:"' '"` + }{}, + wantOpts: fieldOptions{hasName: true, name: " ", quotedName: `" "`}, + }, { + name: jsontest.Name("CommaQuotes"), + in: struct { + V int `json:"',\\'\"\\\"'"` + }{}, + wantOpts: fieldOptions{hasName: true, name: `,'""`, quotedName: `",'\"\""`, nameNeedEscape: true}, + }, { + name: jsontest.Name("SingleComma"), + in: struct { + V int `json:","` + }{}, + wantOpts: fieldOptions{name: "V", quotedName: `"V"`}, + wantErr: errors.New("Go struct field V has malformed `json` tag: invalid trailing ',' character"), + }, { + name: jsontest.Name("SuperfluousCommas"), + in: struct { + V int `json:",,,,\"\",,inline,unknown,,,,"` + }{}, + wantOpts: fieldOptions{name: "V", quotedName: `"V"`, inline: true, unknown: true}, + wantErr: errors.New("Go struct field V has malformed `json` tag: invalid character ',' at start of option (expecting Unicode letter or single quote)"), + }, { + name: jsontest.Name("CaseAloneOption"), + in: struct { + FieldName int `json:",case"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + wantErr: errors.New("Go struct field FieldName is missing value for `case` tag option; specify `case:ignore` or `case:strict` instead"), + }, { + name: jsontest.Name("CaseIgnoreOption"), + in: struct { + FieldName int `json:",case:ignore"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, casing: caseIgnore}, + }, { + name: jsontest.Name("CaseStrictOption"), + in: struct { + FieldName int `json:",case:strict"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, casing: caseStrict}, + }, { + name: jsontest.Name("CaseUnknownOption"), + in: struct { + FieldName int `json:",case:unknown"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + wantErr: errors.New("Go struct field FieldName has unknown `case:unknown` tag value"), + }, { + name: jsontest.Name("CaseQuotedOption"), + in: struct { + FieldName int `json:",case:'ignore'"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, casing: caseIgnore}, + wantErr: errors.New("Go struct field FieldName has unnecessarily quoted appearance of `case:'ignore'` tag option; specify `case:ignore` instead"), + }, { + name: jsontest.Name("BothCaseOptions"), + in: struct { + FieldName int `json:",case:ignore,case:strict"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, casing: caseIgnore | caseStrict}, + wantErr: errors.New("Go struct field FieldName cannot have both `case:ignore` and `case:strict` tag options"), + }, { + name: jsontest.Name("InlineOption"), + in: struct { + FieldName int `json:",inline"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true}, + }, { + name: jsontest.Name("UnknownOption"), + in: struct { + FieldName int `json:",unknown"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, unknown: true}, + }, { + name: jsontest.Name("OmitZeroOption"), + in: struct { + FieldName int `json:",omitzero"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, omitzero: true}, + }, { + name: jsontest.Name("OmitEmptyOption"), + in: struct { + FieldName int `json:",omitempty"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, omitempty: true}, + }, { + name: jsontest.Name("StringOption"), + in: struct { + FieldName int `json:",string"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, string: true}, + }, { + name: jsontest.Name("FormatOptionEqual"), + in: struct { + FieldName int `json:",format=fizzbuzz"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + wantErr: errors.New("Go struct field FieldName is missing value for `format` tag option"), + }, { + name: jsontest.Name("FormatOptionColon"), + in: struct { + FieldName int `json:",format:fizzbuzz"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "fizzbuzz"}, + }, { + name: jsontest.Name("FormatOptionQuoted"), + in: struct { + FieldName int `json:",format:'2006-01-02'"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "2006-01-02"}, + }, { + name: jsontest.Name("FormatOptionInvalid"), + in: struct { + FieldName int `json:",format:'2006-01-02"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + wantErr: errors.New("Go struct field FieldName has malformed value for `format` tag option: single-quoted string not terminated: '2006-01-0..."), + }, { + name: jsontest.Name("FormatOptionNotLast"), + in: struct { + FieldName int `json:",format:alpha,ordered"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "alpha"}, + wantErr: errors.New("Go struct field FieldName has `format` tag option that was not specified last"), + }, { + name: jsontest.Name("AllOptions"), + in: struct { + FieldName int `json:",case:ignore,inline,unknown,omitzero,omitempty,string,format:format"` + }{}, + wantOpts: fieldOptions{ + name: "FieldName", + quotedName: `"FieldName"`, + casing: caseIgnore, + inline: true, + unknown: true, + omitzero: true, + omitempty: true, + string: true, + format: "format", + }, + }, { + name: jsontest.Name("AllOptionsQuoted"), + in: struct { + FieldName int `json:",'case':'ignore','inline','unknown','omitzero','omitempty','string','format':'format'"` + }{}, + wantOpts: fieldOptions{ + name: "FieldName", + quotedName: `"FieldName"`, + casing: caseIgnore, + inline: true, + unknown: true, + omitzero: true, + omitempty: true, + string: true, + format: "format", + }, + wantErr: errors.New("Go struct field FieldName has unnecessarily quoted appearance of `'case'` tag option; specify `case` instead"), + }, { + name: jsontest.Name("AllOptionsCaseSensitive"), + in: struct { + FieldName int `json:",CASE:IGNORE,INLINE,UNKNOWN,OMITZERO,OMITEMPTY,STRING,FORMAT:FORMAT"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + wantErr: errors.New("Go struct field FieldName has invalid appearance of `CASE` tag option; specify `case` instead"), + }, { + name: jsontest.Name("AllOptionsSpaceSensitive"), + in: struct { + FieldName int `json:", case:ignore , inline , unknown , omitzero , omitempty , string , format:format "` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`}, + wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character ' ' at start of option (expecting Unicode letter or single quote)"), + }, { + name: jsontest.Name("UnknownTagOption"), + in: struct { + FieldName int `json:",inline,whoknows,string"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true, string: true}, + }, { + name: jsontest.Name("MalformedQuotedString/MissingQuote"), + in: struct { + FieldName int `json:"'hello,string"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, string: true}, + wantErr: errors.New("Go struct field FieldName has malformed `json` tag: single-quoted string not terminated: 'hello,str..."), + }, { + name: jsontest.Name("MalformedQuotedString/MissingComma"), + in: struct { + FieldName int `json:"'hello'inline,string"` + }{}, + wantOpts: fieldOptions{hasName: true, name: "hello", quotedName: `"hello"`, inline: true, string: true}, + wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character 'i' before next option (expecting ',')"), + }, { + name: jsontest.Name("MalformedQuotedString/InvalidEscape"), + in: struct { + FieldName int `json:"'hello\\u####',inline,string"` + }{}, + wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true, string: true}, + wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid single-quoted string: 'hello\\u####'"), + }, { + name: jsontest.Name("MisnamedTag"), + in: struct { + V int `jsom:"Misnamed"` + }{}, + wantOpts: fieldOptions{name: "V", quotedName: `"V"`}, + }} + + for _, tt := range tests { + t.Run(tt.name.Name, func(t *testing.T) { + fs := reflect.TypeOf(tt.in).Field(0) + gotOpts, gotIgnored, gotErr := parseFieldOptions(fs) + if !reflect.DeepEqual(gotOpts, tt.wantOpts) || gotIgnored != tt.wantIgnored || !reflect.DeepEqual(gotErr, tt.wantErr) { + t.Errorf("%s: parseFieldOptions(%T) = (\n\t%v,\n\t%v,\n\t%v\n), want (\n\t%v,\n\t%v,\n\t%v\n)", tt.name.Where, tt.in, gotOpts, gotIgnored, gotErr, tt.wantOpts, tt.wantIgnored, tt.wantErr) + } + }) + } +} diff --git a/src/encoding/json/v2/fold.go b/src/encoding/json/v2/fold.go new file mode 100644 index 0000000000..ca33efe85f --- /dev/null +++ b/src/encoding/json/v2/fold.go @@ -0,0 +1,58 @@ +// Copyright 2020 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 + +package json + +import ( + "unicode" + "unicode/utf8" +) + +// foldName returns a folded string such that foldName(x) == foldName(y) +// is similar to strings.EqualFold(x, y), but ignores underscore and dashes. +// This allows foldName to match common naming conventions. +func foldName(in []byte) []byte { + // This is inlinable to take advantage of "function outlining". + // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/ + var arr [32]byte // large enough for most JSON names + return appendFoldedName(arr[:0], in) +} +func appendFoldedName(out, in []byte) []byte { + for i := 0; i < len(in); { + // Handle single-byte ASCII. + if c := in[i]; c < utf8.RuneSelf { + if c != '_' && c != '-' { + if 'a' <= c && c <= 'z' { + c -= 'a' - 'A' + } + out = append(out, c) + } + i++ + continue + } + // Handle multi-byte Unicode. + r, n := utf8.DecodeRune(in[i:]) + out = utf8.AppendRune(out, foldRune(r)) + i += n + } + return out +} + +// foldRune is a variation on unicode.SimpleFold that returns the same rune +// for all runes in the same fold set. +// +// Invariant: +// +// foldRune(x) == foldRune(y) ⇔ strings.EqualFold(string(x), string(y)) +func foldRune(r rune) rune { + for { + r2 := unicode.SimpleFold(r) + if r2 <= r { + return r2 // smallest character in the fold set + } + r = r2 + } +} diff --git a/src/encoding/json/v2/fold_test.go b/src/encoding/json/v2/fold_test.go new file mode 100644 index 0000000000..a1c8972380 --- /dev/null +++ b/src/encoding/json/v2/fold_test.go @@ -0,0 +1,127 @@ +// Copyright 2020 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 + +package json + +import ( + "fmt" + "reflect" + "testing" + "unicode" +) + +var equalFoldTestdata = []struct { + in1, in2 string + want bool +}{ + {"", "", true}, + {"abc", "abc", true}, + {"ABcd", "ABcd", true}, + {"123abc", "123ABC", true}, + {"_1_2_-_3__--a-_-b-c-", "123ABC", true}, + {"αβδ", "ΑΒΔ", true}, + {"abc", "xyz", false}, + {"abc", "XYZ", false}, + {"abcdefghijk", "abcdefghijX", false}, + {"abcdefghijk", "abcdefghij\u212A", true}, + {"abcdefghijK", "abcdefghij\u212A", true}, + {"abcdefghijkz", "abcdefghij\u212Ay", false}, + {"abcdefghijKz", "abcdefghij\u212Ay", false}, + {"1", "2", false}, + {"utf-8", "US-ASCII", false}, + {"hello, world!", "hello, world!", true}, + {"hello, world!", "Hello, World!", true}, + {"hello, world!", "HELLO, WORLD!", true}, + {"hello, world!", "jello, world!", false}, + {"γειά, κόσμε!", "γειά, κόσμε!", true}, + {"γειά, κόσμε!", "Γειά, Κόσμε!", true}, + {"γειά, κόσμε!", "ΓΕΙΆ, ΚΌΣΜΕ!", true}, + {"γειά, κόσμε!", "ΛΕΙΆ, ΚΌΣΜΕ!", false}, + {"AESKey", "aesKey", true}, + {"γειά, κόσμε!", "Γ\xce_\xb5ιά, Κόσμε!", false}, + {"aeskey", "AESKEY", true}, + {"AESKEY", "aes_key", true}, + {"aes_key", "AES_KEY", true}, + {"AES_KEY", "aes-key", true}, + {"aes-key", "AES-KEY", true}, + {"AES-KEY", "aesKey", true}, + {"aesKey", "AesKey", true}, + {"AesKey", "AESKey", true}, + {"AESKey", "aeskey", true}, + {"DESKey", "aeskey", false}, + {"AES Key", "aeskey", false}, + {"aes﹏key", "aeskey", false}, // Unicode underscore not handled + {"aes〰key", "aeskey", false}, // Unicode dash not handled +} + +func TestEqualFold(t *testing.T) { + for _, tt := range equalFoldTestdata { + got := equalFold([]byte(tt.in1), []byte(tt.in2)) + if got != tt.want { + t.Errorf("equalFold(%q, %q) = %v, want %v", tt.in1, tt.in2, got, tt.want) + } + } +} + +func equalFold(x, y []byte) bool { + return string(foldName(x)) == string(foldName(y)) +} + +func TestFoldRune(t *testing.T) { + if testing.Short() { + t.Skip() + } + + var foldSet []rune + for r := range rune(unicode.MaxRune + 1) { + // Derive all runes that are all part of the same fold set. + foldSet = foldSet[:0] + for r0 := r; r != r0 || len(foldSet) == 0; r = unicode.SimpleFold(r) { + foldSet = append(foldSet, r) + } + + // Normalized form of each rune in a foldset must be the same and + // also be within the set itself. + var withinSet bool + rr0 := foldRune(foldSet[0]) + for _, r := range foldSet { + withinSet = withinSet || rr0 == r + rr := foldRune(r) + if rr0 != rr { + t.Errorf("foldRune(%q) = %q, want %q", r, rr, rr0) + } + } + if !withinSet { + t.Errorf("foldRune(%q) = %q not in fold set %q", foldSet[0], rr0, string(foldSet)) + } + } +} + +// TestBenchmarkUnmarshalUnknown unmarshals an unknown field into a struct with +// varying number of fields. Since the unknown field does not directly match +// any known field by name, it must fall back on case-insensitive matching. +func TestBenchmarkUnmarshalUnknown(t *testing.T) { + in := []byte(`{"NameUnknown":null}`) + for _, n := range []int{1, 2, 5, 10, 20, 50, 100} { + unmarshal := Unmarshal + + var fields []reflect.StructField + for i := range n { + fields = append(fields, reflect.StructField{ + Name: fmt.Sprintf("Name%d", i), + Type: T[int](), + Tag: `json:",case:ignore"`, + }) + } + out := reflect.New(reflect.StructOf(fields)).Interface() + + t.Run(fmt.Sprintf("N%d", n), func(t *testing.T) { + if err := unmarshal(in, out); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + }) + } +} diff --git a/src/encoding/json/v2/fuzz_test.go b/src/encoding/json/v2/fuzz_test.go new file mode 100644 index 0000000000..491a08311e --- /dev/null +++ b/src/encoding/json/v2/fuzz_test.go @@ -0,0 +1,39 @@ +// Copyright 2020 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 + +package json + +import ( + "bytes" + "testing" +) + +func FuzzEqualFold(f *testing.F) { + for _, tt := range equalFoldTestdata { + f.Add([]byte(tt.in1), []byte(tt.in2)) + } + + equalFoldSimple := func(x, y []byte) bool { + strip := func(b []byte) []byte { + return bytes.Map(func(r rune) rune { + if r == '_' || r == '-' { + return -1 // ignore underscores and dashes + } + return r + }, b) + } + return bytes.EqualFold(strip(x), strip(y)) + } + + f.Fuzz(func(t *testing.T, s1, s2 []byte) { + // Compare the optimized and simplified implementations. + got := equalFold(s1, s2) + want := equalFoldSimple(s1, s2) + if got != want { + t.Errorf("equalFold(%q, %q) = %v, want %v", s1, s2, got, want) + } + }) +} diff --git a/src/encoding/json/v2/inline_test.go b/src/encoding/json/v2/inline_test.go new file mode 100644 index 0000000000..b68fefb064 --- /dev/null +++ b/src/encoding/json/v2/inline_test.go @@ -0,0 +1,109 @@ +// Copyright 2020 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 + +package json + +import ( + "os" + "os/exec" + "strings" + "testing" +) + +// Whether a function is inlinable is dependent on the Go compiler version +// and also relies on the presence of the Go toolchain itself being installed. +// This test is disabled by default and explicitly enabled with an +// environment variable that is specified in our integration tests, +// which have fine control over exactly which Go version is being tested. +var testInline = os.Getenv("TEST_INLINE") != "" + +func TestInline(t *testing.T) { + if !testInline { + t.SkipNow() + } + + pkgs := map[string]map[string]bool{ + ".": { + "hash64": true, + "foldName": true, // thin wrapper over appendFoldedName + }, + "./internal/jsonwire": { + "ConsumeWhitespace": true, + "ConsumeNull": true, + "ConsumeFalse": true, + "ConsumeTrue": true, + "ConsumeSimpleString": true, + "ConsumeString": true, // thin wrapper over consumeStringResumable + "ConsumeSimpleNumber": true, + "ConsumeNumber": true, // thin wrapper over consumeNumberResumable + "UnquoteMayCopy": true, // thin wrapper over unescapeString + "HasSuffixByte": true, + "TrimSuffixByte": true, + "TrimSuffixString": true, + "TrimSuffixWhitespace": true, + }, + "./jsontext": { + "encoderState.NeedFlush": true, + "Decoder.ReadToken": true, // thin wrapper over decoderState.ReadToken + "Decoder.ReadValue": true, // thin wrapper over decoderState.ReadValue + "Encoder.WriteToken": true, // thin wrapper over encoderState.WriteToken + "Encoder.WriteValue": true, // thin wrapper over encoderState.WriteValue + "decodeBuffer.needMore": true, + "stateMachine.appendLiteral": true, + "stateMachine.appendNumber": true, + "stateMachine.appendString": true, + "stateMachine.Depth": true, + "stateMachine.reset": true, + "stateMachine.MayAppendDelim": true, + "stateMachine.needDelim": true, + "stateMachine.popArray": true, + "stateMachine.popObject": true, + "stateMachine.pushArray": true, + "stateMachine.pushObject": true, + "stateEntry.Increment": true, + "stateEntry.decrement": true, + "stateEntry.isArray": true, + "stateEntry.isObject": true, + "stateEntry.Length": true, + "stateEntry.needImplicitColon": true, + "stateEntry.needImplicitComma": true, + "stateEntry.NeedObjectName": true, + "stateEntry.needObjectValue": true, + "objectNameStack.reset": true, + "objectNameStack.length": true, + "objectNameStack.getUnquoted": true, + "objectNameStack.push": true, + "objectNameStack.ReplaceLastQuotedOffset": true, + "objectNameStack.replaceLastUnquotedName": true, + "objectNameStack.pop": true, + "objectNameStack.ensureCopiedBuffer": true, + "objectNamespace.insertQuoted": true, // thin wrapper over objectNamespace.insert + "objectNamespace.InsertUnquoted": true, // thin wrapper over objectNamespace.insert + "Token.String": true, // thin wrapper over Token.string + }, + } + + for pkg, fncs := range pkgs { + cmd := exec.Command("go", "build", "-gcflags=-m", pkg) + b, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("exec.Command error: %v\n\n%s", err, b) + } + for _, line := range strings.Split(string(b), "\n") { + const phrase = ": can inline " + if i := strings.Index(line, phrase); i >= 0 { + fnc := line[i+len(phrase):] + fnc = strings.ReplaceAll(fnc, "(", "") + fnc = strings.ReplaceAll(fnc, "*", "") + fnc = strings.ReplaceAll(fnc, ")", "") + delete(fncs, fnc) + } + } + for fnc := range fncs { + t.Errorf("%v is not inlinable, expected it to be", fnc) + } + } +} diff --git a/src/encoding/json/v2/intern.go b/src/encoding/json/v2/intern.go new file mode 100644 index 0000000000..3c75e034f0 --- /dev/null +++ b/src/encoding/json/v2/intern.go @@ -0,0 +1,88 @@ +// Copyright 2022 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 + +package json + +import ( + "encoding/binary" + "math/bits" +) + +// stringCache is a cache for strings converted from a []byte. +type stringCache = [256]string // 256*unsafe.Sizeof(string("")) => 4KiB + +// makeString returns the string form of b. +// It returns a pre-allocated string from c if present, otherwise +// it allocates a new string, inserts it into the cache, and returns it. +func makeString(c *stringCache, b []byte) string { + const ( + minCachedLen = 2 // single byte strings are already interned by the runtime + maxCachedLen = 256 // large enough for UUIDs, IPv6 addresses, SHA-256 checksums, etc. + ) + if c == nil || len(b) < minCachedLen || len(b) > maxCachedLen { + return string(b) + } + + // Compute a hash from the fixed-width prefix and suffix of the string. + // This ensures hashing a string is a constant time operation. + var h uint32 + switch { + case len(b) >= 8: + lo := binary.LittleEndian.Uint64(b[:8]) + hi := binary.LittleEndian.Uint64(b[len(b)-8:]) + h = hash64(uint32(lo), uint32(lo>>32)) ^ hash64(uint32(hi), uint32(hi>>32)) + case len(b) >= 4: + lo := binary.LittleEndian.Uint32(b[:4]) + hi := binary.LittleEndian.Uint32(b[len(b)-4:]) + h = hash64(lo, hi) + case len(b) >= 2: + lo := binary.LittleEndian.Uint16(b[:2]) + hi := binary.LittleEndian.Uint16(b[len(b)-2:]) + h = hash64(uint32(lo), uint32(hi)) + } + + // Check the cache for the string. + i := h % uint32(len(*c)) + if s := (*c)[i]; s == string(b) { + return s + } + s := string(b) + (*c)[i] = s + return s +} + +// 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: + // var b [8]byte + // binary.LittleEndian.PutUint32(b[:4], lo) + // binary.LittleEndian.PutUint32(b[4:], hi) + // return xxhash.Sum32(b[:]) + const ( + prime1 = 0x9e3779b1 + prime2 = 0x85ebca77 + prime3 = 0xc2b2ae3d + prime4 = 0x27d4eb2f + prime5 = 0x165667b1 + ) + h := prime5 + uint32(8) + h += lo * prime3 + h = bits.RotateLeft32(h, 17) * prime4 + h += hi * prime3 + 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. + const avalanche = false + if avalanche { + h ^= h >> 15 + h *= prime2 + h ^= h >> 13 + h *= prime3 + h ^= h >> 16 + } + return h +} diff --git a/src/encoding/json/v2/intern_test.go b/src/encoding/json/v2/intern_test.go new file mode 100644 index 0000000000..9163f41006 --- /dev/null +++ b/src/encoding/json/v2/intern_test.go @@ -0,0 +1,146 @@ +// Copyright 2022 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 + +package json + +import ( + "bytes" + "fmt" + "io" + "testing" + + "encoding/json/internal/jsontest" + "encoding/json/jsontext" +) + +func TestIntern(t *testing.T) { + var sc stringCache + const alphabet = "abcdefghijklmnopqrstuvwxyz" + for i := range len(alphabet) + 1 { + want := alphabet[i:] + if got := makeString(&sc, []byte(want)); got != want { + t.Fatalf("make = %v, want %v", got, want) + } + } + for i := range 1000 { + want := fmt.Sprintf("test%b", i) + if got := makeString(&sc, []byte(want)); got != want { + t.Fatalf("make = %v, want %v", got, want) + } + } +} + +var sink string + +func BenchmarkIntern(b *testing.B) { + datasetStrings := func(name string) (out [][]byte) { + var data []byte + for _, ts := range jsontest.Data { + if ts.Name == name { + data = ts.Data() + } + } + dec := jsontext.NewDecoder(bytes.NewReader(data)) + for { + k, n := dec.StackIndex(dec.StackDepth()) + isObjectName := k == '{' && n%2 == 0 + tok, err := dec.ReadToken() + if err != nil { + if err == io.EOF { + break + } + b.Fatalf("ReadToken error: %v", err) + } + if tok.Kind() == '"' && !isObjectName { + out = append(out, []byte(tok.String())) + } + } + return out + } + + tests := []struct { + label string + data [][]byte + }{ + // Best is the best case scenario where every string is the same. + {"Best", func() (out [][]byte) { + for range 1000 { + out = append(out, []byte("hello, world!")) + } + return out + }()}, + + // Repeat is a sequence of the same set of names repeated. + // This commonly occurs when unmarshaling a JSON array of JSON objects, + // where the set of all names is usually small. + {"Repeat", func() (out [][]byte) { + for range 100 { + for _, s := range []string{"first_name", "last_name", "age", "address", "street_address", "city", "state", "postal_code", "phone_numbers", "gender"} { + out = append(out, []byte(s)) + } + } + return out + }()}, + + // Synthea is all string values encountered in the Synthea FHIR dataset. + {"Synthea", datasetStrings("SyntheaFhir")}, + + // Twitter is all string values encountered in the Twitter dataset. + {"Twitter", datasetStrings("TwitterStatus")}, + + // Worst is the worst case scenario where every string is different + // resulting in wasted time looking up a string that will never match. + {"Worst", func() (out [][]byte) { + for i := range 1000 { + out = append(out, []byte(fmt.Sprintf("%016x", i))) + } + return out + }()}, + } + + for _, tt := range tests { + b.Run(tt.label, func(b *testing.B) { + // Alloc simply heap allocates each string. + // This provides an upper bound on the number of allocations. + b.Run("Alloc", func(b *testing.B) { + b.ReportAllocs() + for range b.N { + for _, b := range tt.data { + sink = string(b) + } + } + }) + // Cache interns strings using stringCache. + // We want to optimize for having a faster runtime than Alloc, + // and also keeping the number of allocations closer to GoMap. + b.Run("Cache", func(b *testing.B) { + b.ReportAllocs() + for range b.N { + var sc stringCache + for _, b := range tt.data { + sink = makeString(&sc, b) + } + } + }) + // GoMap interns all strings in a simple Go map. + // This provides a lower bound on the number of allocations. + b.Run("GoMap", func(b *testing.B) { + b.ReportAllocs() + for range b.N { + m := make(map[string]string) + for _, b := range tt.data { + s, ok := m[string(b)] + if !ok { + s = string(b) + m[s] = s + } + sink = s + } + } + }) + }) + } +} diff --git a/src/encoding/json/v2/options.go b/src/encoding/json/v2/options.go new file mode 100644 index 0000000000..12bbdb5d86 --- /dev/null +++ b/src/encoding/json/v2/options.go @@ -0,0 +1,288 @@ +// 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 + +package json + +import ( + "fmt" + + "encoding/json/internal" + "encoding/json/internal/jsonflags" + "encoding/json/internal/jsonopts" +) + +// Options configure [Marshal], [MarshalWrite], [MarshalEncode], +// [Unmarshal], [UnmarshalRead], and [UnmarshalDecode] with specific features. +// Each function takes in a variadic list of options, where properties +// set in later options override the value of previously set properties. +// +// The Options type is identical to [encoding/json.Options] and +// [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 +// (even though the underlying implementation avoids Go maps for performance). +// +// The constructors (e.g., [Deterministic]) return a singular option value: +// +// opt := Deterministic(true) +// +// which is analogous to creating a single entry map: +// +// opt := Options{"Deterministic": true} +// +// [JoinOptions] composes multiple options values to together: +// +// out := JoinOptions(opts...) +// +// which is analogous to making a new map and copying the options over: +// +// out := make(Options) +// for _, m := range opts { +// for k, v := range m { +// out[k] = v +// } +// } +// +// [GetOption] looks up the value of options parameter: +// +// v, ok := GetOption(opts, Deterministic) +// +// which is analogous to a Go map lookup: +// +// v, ok := Options["Deterministic"] +// +// There is a single Options type, which is used with both marshal and unmarshal. +// Some options affect both operations, while others only affect one operation: +// +// - [StringifyNumbers] affects marshaling and unmarshaling +// - [Deterministic] affects marshaling only +// - [FormatNilSliceAsNull] affects marshaling only +// - [FormatNilMapAsNull] affects marshaling only +// - [OmitZeroStructFields] affects marshaling only +// - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling +// - [DiscardUnknownMembers] affects marshaling only +// - [RejectUnknownMembers] affects unmarshaling only +// - [WithMarshalers] affects marshaling only +// - [WithUnmarshalers] affects unmarshaling only +// +// Options that do not affect a particular operation are ignored. +type Options = jsonopts.Options + +// JoinOptions coalesces the provided list of options into a single Options. +// Properties set in later options override the value of previously set properties. +func JoinOptions(srcs ...Options) Options { + var dst jsonopts.Struct + dst.Join(srcs...) + return &dst +} + +// GetOption returns the value stored in opts with the provided setter, +// reporting whether the value is present. +// +// Example usage: +// +// v, ok := json.GetOption(opts, json.Deterministic) +// +// Options are most commonly introspected to alter the JSON representation of +// [MarshalerTo.MarshalJSONTo] and [UnmarshalerFrom.UnmarshalJSONFrom] methods, and +// [MarshalToFunc] and [UnmarshalFromFunc] functions. +// In such cases, the presence bit should generally be ignored. +func GetOption[T any](opts Options, setter func(T) Options) (T, bool) { + return jsonopts.GetOption(opts, setter) +} + +// DefaultOptionsV2 is the full set of all options that define v2 semantics. +// It is equivalent to all options under [Options], [encoding/json.Options], +// and [encoding/json/jsontext.Options] being set to false or the zero value, +// except for the options related to whitespace formatting. +func DefaultOptionsV2() Options { + return &jsonopts.DefaultOptionsV2 +} + +// StringifyNumbers specifies that numeric Go types should be marshaled +// as a JSON string containing the equivalent JSON number value. +// When unmarshaling, numeric Go types are parsed from a JSON string +// containing the JSON number without any surrounding whitespace. +// +// According to RFC 8259, section 6, a JSON implementation may choose to +// limit the representation of a JSON number to an IEEE 754 binary64 value. +// This may cause decoders to lose precision for int64 and uint64 types. +// Quoting JSON numbers as a JSON string preserves the exact precision. +// +// This affects either marshaling or unmarshaling. +func StringifyNumbers(v bool) Options { + if v { + return jsonflags.StringifyNumbers | 1 + } else { + return jsonflags.StringifyNumbers | 0 + } +} + +// 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. +// +// This only affects marshaling and is ignored when unmarshaling. +func Deterministic(v bool) Options { + if v { + return jsonflags.Deterministic | 1 + } else { + return jsonflags.Deterministic | 0 + } +} + +// 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 { + if v { + return jsonflags.FormatNilSliceAsNull | 1 + } else { + return jsonflags.FormatNilSliceAsNull | 0 + } +} + +// 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 { + if v { + return jsonflags.FormatNilMapAsNull | 1 + } else { + return jsonflags.FormatNilMapAsNull | 0 + } +} + +// OmitZeroStructFields specifies that a Go struct should marshal in such a way +// that all struct fields that are zero are omitted from the marshaled output +// if the value is zero as determined by the "IsZero() bool" method if present, +// otherwise based on whether the field is the zero Go value. +// This is semantically equivalent to specifying the `omitzero` tag option +// on every field in a Go struct. +// +// This only affects marshaling and is ignored when unmarshaling. +func OmitZeroStructFields(v bool) Options { + if v { + return jsonflags.OmitZeroStructFields | 1 + } else { + return jsonflags.OmitZeroStructFields | 0 + } +} + +// MatchCaseInsensitiveNames specifies that JSON object members are matched +// against Go struct fields using a case-insensitive match of the name. +// Go struct fields explicitly marked with `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. +func MatchCaseInsensitiveNames(v bool) Options { + if v { + return jsonflags.MatchCaseInsensitiveNames | 1 + } else { + return jsonflags.MatchCaseInsensitiveNames | 0 + } +} + +// 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. +// +// This only affects unmarshaling and is ignored when marshaling. +func RejectUnknownMembers(v bool) Options { + if v { + return jsonflags.RejectUnknownMembers | 1 + } else { + return jsonflags.RejectUnknownMembers | 0 + } +} + +// WithMarshalers specifies a list of type-specific marshalers to use, +// which can be used to override the default marshal behavior for values +// of particular types. +// +// This only affects marshaling and is ignored when unmarshaling. +func WithMarshalers(v *Marshalers) Options { + return (*marshalersOption)(v) +} + +// WithUnmarshalers specifies a list of type-specific unmarshalers to use, +// which can be used to override the default unmarshal behavior for values +// of particular types. +// +// This only affects unmarshaling and is ignored when marshaling. +func WithUnmarshalers(v *Unmarshalers) Options { + return (*unmarshalersOption)(v) +} + +// These option types are declared here instead of "jsonopts" +// to avoid a dependency on "reflect" from "jsonopts". +type ( + marshalersOption Marshalers + unmarshalersOption Unmarshalers +) + +func (*marshalersOption) JSONOptions(internal.NotForPublicUse) {} +func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {} + +// Inject support into "jsonopts" to handle these types. +func init() { + jsonopts.GetUnknownOption = func(src *jsonopts.Struct, zero jsonopts.Options) (any, bool) { + switch zero.(type) { + case *marshalersOption: + if !src.Flags.Has(jsonflags.Marshalers) { + return (*Marshalers)(nil), false + } + return src.Marshalers.(*Marshalers), true + case *unmarshalersOption: + if !src.Flags.Has(jsonflags.Unmarshalers) { + return (*Unmarshalers)(nil), false + } + return src.Unmarshalers.(*Unmarshalers), true + default: + panic(fmt.Sprintf("unknown option %T", zero)) + } + } + jsonopts.JoinUnknownOption = func(dst *jsonopts.Struct, src jsonopts.Options) { + switch src := src.(type) { + case *marshalersOption: + dst.Flags.Set(jsonflags.Marshalers | 1) + dst.Marshalers = (*Marshalers)(src) + case *unmarshalersOption: + dst.Flags.Set(jsonflags.Unmarshalers | 1) + dst.Unmarshalers = (*Unmarshalers)(src) + default: + panic(fmt.Sprintf("unknown option %T", src)) + } + } +} diff --git a/src/encoding/json/v2_bench_test.go b/src/encoding/json/v2_bench_test.go new file mode 100644 index 0000000000..b9ed7b6220 --- /dev/null +++ b/src/encoding/json/v2_bench_test.go @@ -0,0 +1,483 @@ +// Copyright 2011 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 + +// Large data benchmark. +// The JSON data is a summary of agl's changes in the +// go, webkit, and chromium open source projects. +// We benchmark converting between the JSON form +// and in-memory data structures. + +package json + +import ( + "bytes" + "io" + "strings" + "testing" + + "encoding/json/internal/jsontest" +) + +type codeResponse struct { + Tree *codeNode `json:"tree"` + Username string `json:"username"` +} + +type codeNode struct { + Name string `json:"name"` + Kids []*codeNode `json:"kids"` + CLWeight float64 `json:"cl_weight"` + Touches int `json:"touches"` + MinT int64 `json:"min_t"` + MaxT int64 `json:"max_t"` + MeanT int64 `json:"mean_t"` +} + +var codeJSON []byte +var codeStruct codeResponse + +func codeInit() { + var data []byte + for _, entry := range jsontest.Data { + if entry.Name == "GolangSource" { + data = entry.Data() + } + } + codeJSON = data + + if err := Unmarshal(codeJSON, &codeStruct); err != nil { + panic("unmarshal code.json: " + err.Error()) + } + + var err error + if data, err = Marshal(&codeStruct); err != nil { + panic("marshal code.json: " + err.Error()) + } + + if !bytes.Equal(data, codeJSON) { + println("different lengths", len(data), len(codeJSON)) + for i := 0; i < len(data) && i < len(codeJSON); i++ { + if data[i] != codeJSON[i] { + println("re-marshal: changed at byte", i) + println("orig: ", string(codeJSON[i-10:i+10])) + println("new: ", string(data[i-10:i+10])) + break + } + } + panic("re-marshal code.json: different result") + } +} + +func BenchmarkCodeEncoder(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + enc := NewEncoder(io.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatalf("Encode error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeEncoderError(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + b.RunParallel(func(pb *testing.PB) { + enc := NewEncoder(io.Discard) + for pb.Next() { + if err := enc.Encode(&codeStruct); err != nil { + b.Fatalf("Encode error: %v", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("Marshal error: got nil, want non-nil") + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeMarshal(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(&codeStruct); err != nil { + b.Fatalf("Marshal error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeMarshalError(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(&codeStruct); err != nil { + b.Fatalf("Marshal error: %v", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("Marshal error: got nil, want non-nil") + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func benchMarshalBytes(n int) func(*testing.B) { + sample := []byte("hello world") + // Use a struct pointer, to avoid an allocation when passing it as an + // interface parameter to Marshal. + v := &struct { + Bytes []byte + }{ + bytes.Repeat(sample, (n/len(sample))+1)[:n], + } + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Marshal(v); err != nil { + b.Fatalf("Marshal error: %v", err) + } + } + } +} + +func benchMarshalBytesError(n int) func(*testing.B) { + sample := []byte("hello world") + // Use a struct pointer, to avoid an allocation when passing it as an + // interface parameter to Marshal. + v := &struct { + Bytes []byte + }{ + bytes.Repeat(sample, (n/len(sample))+1)[:n], + } + + // Trigger an error in Marshal with cyclic data. + type Dummy struct { + Name string + Next *Dummy + } + dummy := Dummy{Name: "Dummy"} + dummy.Next = &dummy + + return func(b *testing.B) { + for i := 0; i < b.N; i++ { + if _, err := Marshal(v); err != nil { + b.Fatalf("Marshal error: %v", err) + } + if _, err := Marshal(dummy); err == nil { + b.Fatal("Marshal error: got nil, want non-nil") + } + } + } +} + +func BenchmarkMarshalBytes(b *testing.B) { + b.ReportAllocs() + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytes(32)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytes(256)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytes(4096)) +} + +func BenchmarkMarshalBytesError(b *testing.B) { + b.ReportAllocs() + // 32 fits within encodeState.scratch. + b.Run("32", benchMarshalBytesError(32)) + // 256 doesn't fit in encodeState.scratch, but is small enough to + // allocate and avoid the slower base64.NewEncoder. + b.Run("256", benchMarshalBytesError(256)) + // 4096 is large enough that we want to avoid allocating for it. + b.Run("4096", benchMarshalBytesError(4096)) +} + +func BenchmarkMarshalMap(b *testing.B) { + b.ReportAllocs() + m := map[string]int{ + "key3": 3, + "key2": 2, + "key1": 1, + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(m); err != nil { + b.Fatal("Marshal:", err) + } + } + }) +} + +func BenchmarkCodeDecoder(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + var buf bytes.Buffer + dec := NewDecoder(&buf) + var r codeResponse + for pb.Next() { + buf.Write(codeJSON) + // hide EOF + buf.WriteByte('\n') + buf.WriteByte('\n') + buf.WriteByte('\n') + if err := dec.Decode(&r); err != nil { + b.Fatalf("Decode error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkUnicodeDecoder(b *testing.B) { + b.ReportAllocs() + j := []byte(`"\uD83D\uDE01"`) + b.SetBytes(int64(len(j))) + r := bytes.NewReader(j) + dec := NewDecoder(r) + var out string + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := dec.Decode(&out); err != nil { + b.Fatalf("Decode error: %v", err) + } + r.Seek(0, 0) + } +} + +func BenchmarkDecoderStream(b *testing.B) { + b.ReportAllocs() + b.StopTimer() + var buf bytes.Buffer + dec := NewDecoder(&buf) + buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n") + var x any + if err := dec.Decode(&x); err != nil { + b.Fatalf("Decode error: %v", err) + } + ones := strings.Repeat(" 1\n", 300000) + "\n\n\n" + b.StartTimer() + for i := 0; i < b.N; i++ { + if i%300000 == 0 { + buf.WriteString(ones) + } + x = nil + switch err := dec.Decode(&x); { + case err != nil: + b.Fatalf("Decode error: %v", err) + case x != 1.0: + b.Fatalf("Decode: got %v want 1.0", i) + } + } +} + +func BenchmarkCodeUnmarshal(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + var r codeResponse + if err := Unmarshal(codeJSON, &r); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkCodeUnmarshalReuse(b *testing.B) { + b.ReportAllocs() + if codeJSON == nil { + b.StopTimer() + codeInit() + b.StartTimer() + } + b.RunParallel(func(pb *testing.PB) { + var r codeResponse + for pb.Next() { + if err := Unmarshal(codeJSON, &r); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) + b.SetBytes(int64(len(codeJSON))) +} + +func BenchmarkUnmarshalString(b *testing.B) { + b.ReportAllocs() + data := []byte(`"hello, world"`) + b.RunParallel(func(pb *testing.PB) { + var s string + for pb.Next() { + if err := Unmarshal(data, &s); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkUnmarshalFloat64(b *testing.B) { + b.ReportAllocs() + data := []byte(`3.14`) + b.RunParallel(func(pb *testing.PB) { + var f float64 + for pb.Next() { + if err := Unmarshal(data, &f); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkUnmarshalInt64(b *testing.B) { + b.ReportAllocs() + data := []byte(`3`) + b.RunParallel(func(pb *testing.PB) { + var x int64 + for pb.Next() { + if err := Unmarshal(data, &x); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkUnmarshalMap(b *testing.B) { + b.ReportAllocs() + data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`) + b.RunParallel(func(pb *testing.PB) { + x := make(map[string]string, 3) + for pb.Next() { + if err := Unmarshal(data, &x); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkIssue10335(b *testing.B) { + b.ReportAllocs() + j := []byte(`{"a":{ }}`) + b.RunParallel(func(pb *testing.PB) { + var s struct{} + for pb.Next() { + if err := Unmarshal(j, &s); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkIssue34127(b *testing.B) { + b.ReportAllocs() + j := struct { + Bar string `json:"bar,string"` + }{ + Bar: `foobar`, + } + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if _, err := Marshal(&j); err != nil { + b.Fatalf("Marshal error: %v", err) + } + } + }) +} + +func BenchmarkUnmapped(b *testing.B) { + b.ReportAllocs() + j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`) + b.RunParallel(func(pb *testing.PB) { + var s struct{} + for pb.Next() { + if err := Unmarshal(j, &s); err != nil { + b.Fatalf("Unmarshal error: %v", err) + } + } + }) +} + +func BenchmarkEncodeMarshaler(b *testing.B) { + b.ReportAllocs() + + m := struct { + A int + B RawMessage + }{} + + b.RunParallel(func(pb *testing.PB) { + enc := NewEncoder(io.Discard) + + for pb.Next() { + if err := enc.Encode(&m); err != nil { + b.Fatalf("Encode error: %v", err) + } + } + }) +} + +func BenchmarkEncoderEncode(b *testing.B) { + b.ReportAllocs() + type T struct { + X, Y string + } + v := &T{"foo", "bar"} + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + if err := NewEncoder(io.Discard).Encode(v); err != nil { + b.Fatalf("Encode error: %v", err) + } + } + }) +} diff --git a/src/encoding/json/v2_decode.go b/src/encoding/json/v2_decode.go new file mode 100644 index 0000000000..4b9e850939 --- /dev/null +++ b/src/encoding/json/v2_decode.go @@ -0,0 +1,253 @@ +// 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 + +// Represents JSON data structure using native Go types: booleans, floats, +// strings, arrays, and maps. + +package json + +import ( + "cmp" + "fmt" + "reflect" + "strconv" + + "encoding/json/internal/jsonwire" + "encoding/json/jsontext" + jsonv2 "encoding/json/v2" +) + +// 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. +// +// To unmarshal JSON into a value implementing [Unmarshaler], +// Unmarshal calls that value's [Unmarshaler.UnmarshalJSON] method, including +// when the input is a JSON null. +// Otherwise, if the value implements [encoding.TextUnmarshaler] +// and the input is a JSON quoted string, Unmarshal calls +// [encoding.TextUnmarshaler.UnmarshalText] with the unquoted form of the string. +// +// 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 resets the slice length +// to zero and then appends each element to the slice. +// 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 { + s := "json: cannot unmarshal" + if e.Value != "" { + s += " JSON " + e.Value + } + s += " into" + var preposition string + if e.Field != "" { + s += " " + e.Struct + "." + e.Field + preposition = " of" + } + if e.Type != nil { + s += preposition + s += " Go 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.UnusedBuffer() + 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} +} diff --git a/src/encoding/json/v2_decode_test.go b/src/encoding/json/v2_decode_test.go new file mode 100644 index 0000000000..fe814a3cfd --- /dev/null +++ b/src/encoding/json/v2_decode_test.go @@ -0,0 +1,2803 @@ +// 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 + +package json + +import ( + "bytes" + "encoding" + "errors" + "fmt" + "image" + "maps" + "math" + "math/big" + "net" + "reflect" + "slices" + "strconv" + "strings" + "testing" + "time" +) + +func len64(s string) int64 { + return int64(len(s)) +} + +type T struct { + X string + Y int + Z int `json:"-"` +} + +type U struct { + Alphabet string `json:"alpha"` +} + +type V struct { + F1 any + F2 int32 + F3 Number + F4 *VOuter +} + +type VOuter struct { + V V +} + +type W struct { + S SS +} + +type P struct { + PP PP +} + +type PP struct { + T T + Ts []T +} + +type SS string + +func (*SS) UnmarshalJSON(data []byte) error { + return &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[SS]()} +} + +type TAlias T + +func (tt *TAlias) UnmarshalJSON(data []byte) error { + t := T{} + if err := Unmarshal(data, &t); err != nil { + return err + } + *tt = TAlias(t) + return nil +} + +type TOuter struct { + T TAlias +} + +// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and +// without UseNumber +var ifaceNumAsFloat64 = map[string]any{ + "k1": float64(1), + "k2": "s", + "k3": []any{float64(1), float64(2.0), float64(3e-3)}, + "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, +} + +var ifaceNumAsNumber = map[string]any{ + "k1": Number("1"), + "k2": "s", + "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, + "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, +} + +type tx struct { + x int +} + +type u8 uint8 + +// A type that can unmarshal itself. + +type unmarshaler struct { + T bool +} + +func (u *unmarshaler) UnmarshalJSON(b []byte) error { + *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. + return nil +} + +type ustruct struct { + M unmarshaler +} + +type unmarshalerText struct { + A, B string +} + +// needed for re-marshaling tests +func (u unmarshalerText) MarshalText() ([]byte, error) { + return []byte(u.A + ":" + u.B), nil +} + +func (u *unmarshalerText) UnmarshalText(b []byte) error { + pos := bytes.IndexByte(b, ':') + if pos == -1 { + return errors.New("missing separator") + } + u.A, u.B = string(b[:pos]), string(b[pos+1:]) + return nil +} + +var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) + +type ustructText struct { + M unmarshalerText +} + +// u8marshal is an integer type that can marshal/unmarshal itself. +type u8marshal uint8 + +func (u8 u8marshal) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("u%d", u8)), nil +} + +var errMissingU8Prefix = errors.New("missing 'u' prefix") + +func (u8 *u8marshal) UnmarshalText(b []byte) error { + if !bytes.HasPrefix(b, []byte{'u'}) { + return errMissingU8Prefix + } + n, err := strconv.Atoi(string(b[1:])) + if err != nil { + return err + } + *u8 = u8marshal(n) + return nil +} + +var _ encoding.TextUnmarshaler = (*u8marshal)(nil) + +var ( + umtrue = unmarshaler{true} + umslice = []unmarshaler{{true}} + umstruct = ustruct{unmarshaler{true}} + + umtrueXY = unmarshalerText{"x", "y"} + umsliceXY = []unmarshalerText{{"x", "y"}} + umstructXY = ustructText{unmarshalerText{"x", "y"}} + + ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} +) + +// Test data structures for anonymous fields. + +type Point struct { + Z int +} + +type Top struct { + Level0 int + Embed0 + *Embed0a + *Embed0b `json:"e,omitempty"` // treated as named + Embed0c `json:"-"` // ignored + Loop + Embed0p // has Point with X, Y, used + Embed0q // has Point with Z, used + embed // contains exported field +} + +type Embed0 struct { + Level1a int // overridden by Embed0a's Level1a with json tag + Level1b int // used because Embed0a's Level1b is renamed + Level1c int // used because Embed0a's Level1c is ignored + Level1d int // annihilated by Embed0a's Level1d + Level1e int `json:"x"` // annihilated by Embed0a.Level1e +} + +type Embed0a struct { + Level1a int `json:"Level1a,omitempty"` + Level1b int `json:"LEVEL1B,omitempty"` + Level1c int `json:"-"` + Level1d int // annihilated by Embed0's Level1d + Level1f int `json:"x"` // annihilated by Embed0's Level1e +} + +type Embed0b Embed0 + +type Embed0c Embed0 + +type Embed0p struct { + image.Point +} + +type Embed0q struct { + Point +} + +type embed struct { + Q int +} + +type Loop struct { + Loop1 int `json:",omitempty"` + Loop2 int `json:",omitempty"` + *Loop +} + +// From reflect test: +// The X in S6 and S7 annihilate, but they also block the X in S8.S9. +type S5 struct { + S6 + S7 + S8 +} + +type S6 struct { + X int +} + +type S7 S6 + +type S8 struct { + S9 +} + +type S9 struct { + X int + Y int +} + +// From reflect test: +// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. +type S10 struct { + S11 + S12 + S13 +} + +type S11 struct { + S6 +} + +type S12 struct { + S6 +} + +type S13 struct { + S8 +} + +type Ambig struct { + // Given "hello", the first match should win. + First int `json:"HELLO"` + Second int `json:"Hello"` +} + +type XYZ struct { + X any + Y any + Z any +} + +type unexportedWithMethods struct{} + +func (unexportedWithMethods) F() {} + +type byteWithMarshalJSON byte + +func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil +} + +func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { + if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[2:4]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = byteWithMarshalJSON(i) + return nil +} + +type byteWithPtrMarshalJSON byte + +func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { + return byteWithMarshalJSON(*b).MarshalJSON() +} + +func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { + return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) +} + +type byteWithMarshalText byte + +func (b byteWithMarshalText) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil +} + +func (b *byteWithMarshalText) UnmarshalText(data []byte) error { + if len(data) != 3 || data[0] != 'Z' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[1:3]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = byteWithMarshalText(i) + return nil +} + +type byteWithPtrMarshalText byte + +func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { + return byteWithMarshalText(*b).MarshalText() +} + +func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { + return (*byteWithMarshalText)(b).UnmarshalText(data) +} + +type intWithMarshalJSON int + +func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil +} + +func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { + if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[2:4]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = intWithMarshalJSON(i) + return nil +} + +type intWithPtrMarshalJSON int + +func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { + return intWithMarshalJSON(*b).MarshalJSON() +} + +func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { + return (*intWithMarshalJSON)(b).UnmarshalJSON(data) +} + +type intWithMarshalText int + +func (b intWithMarshalText) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil +} + +func (b *intWithMarshalText) UnmarshalText(data []byte) error { + if len(data) != 3 || data[0] != 'Z' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[1:3]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = intWithMarshalText(i) + return nil +} + +type intWithPtrMarshalText int + +func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { + return intWithMarshalText(*b).MarshalText() +} + +func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { + return (*intWithMarshalText)(b).UnmarshalText(data) +} + +type mapStringToStringData struct { + Data map[string]string `json:"data"` +} + +type B struct { + B bool `json:",string"` +} + +type DoublePtr struct { + I **int + J **int +} + +var unmarshalTests = []struct { + CaseName + in string + ptr any // new(type) + out any + err error + useNumber bool + golden bool + disallowUnknownFields bool +}{ + // basic types + {CaseName: Name(""), in: `true`, ptr: new(bool), out: true}, + {CaseName: Name(""), in: `1`, ptr: new(int), out: 1}, + {CaseName: Name(""), in: `1.2`, ptr: new(float64), out: 1.2}, + {CaseName: Name(""), in: `-5`, ptr: new(int16), out: int16(-5)}, + {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, + {CaseName: Name(""), in: `2`, ptr: new(Number), out: Number("2")}, + {CaseName: Name(""), in: `2`, ptr: new(any), out: float64(2.0)}, + {CaseName: Name(""), in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, + {CaseName: Name(""), in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, + {CaseName: Name(""), in: `"http:\/\/"`, ptr: new(string), out: "http://"}, + {CaseName: Name(""), in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, + {CaseName: Name(""), in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, + {CaseName: Name(""), in: "null", ptr: new(any), out: nil}, + {CaseName: Name(""), in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeFor[string](), len64(`{"X": `), "T", "X", nil}}, + {CaseName: Name(""), in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), len64(`{"X": `), "T", "X", nil}}, + {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, + {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), out: tx{}}, + {CaseName: Name(""), in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, + {CaseName: Name(""), in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[SS](), 0, "", "", nil}}, + {CaseName: Name(""), in: `{"T": {"X": 23}}`, ptr: new(TOuter), out: TOuter{}, err: &UnmarshalTypeError{"number", reflect.TypeFor[string](), len64(`{"X": `), "T", "X", nil}}, + {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, + {CaseName: Name(""), in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, + {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, + {CaseName: Name(""), in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, + + // raw values with whitespace + {CaseName: Name(""), in: "\n true ", ptr: new(bool), out: true}, + {CaseName: Name(""), in: "\t 1 ", ptr: new(int), out: 1}, + {CaseName: Name(""), in: "\r 1.2 ", ptr: new(float64), out: 1.2}, + {CaseName: Name(""), in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, + {CaseName: Name(""), in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, + + // Z has a "-" tag. + {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, + {CaseName: Name(""), in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}, err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, + + {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {CaseName: Name(""), in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}, err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + {CaseName: Name(""), in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, + {CaseName: Name(""), in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + + // syntax errors + {CaseName: Name(""), in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", len64(`{"X": "foo", "Y"`)}}, + {CaseName: Name(""), in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", len64(`[1, 2, 3`)}}, + {CaseName: Name(""), in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", len64(`{"X":12`)}, useNumber: true}, + {CaseName: Name(""), in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: len64(`[2, 3`)}}, + {CaseName: Name(""), in: `{"F3": -}`, ptr: new(V), err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: len64(`{"F3": -`)}}, + + // raw value errors + {CaseName: Name(""), in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, + {CaseName: Name(""), in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` 42 `)}}, + {CaseName: Name(""), in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, + {CaseName: Name(""), in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` false `)}}, + {CaseName: Name(""), in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, + {CaseName: Name(""), in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` 3.4 `)}}, + {CaseName: Name(""), in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", len64(``)}}, + {CaseName: Name(""), in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", len64(` "string" `)}}, + + // array tests + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, + {CaseName: Name(""), in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, + + // empty array to interface test + {CaseName: Name(""), in: `[]`, ptr: new([]any), out: []any{}}, + {CaseName: Name(""), in: `null`, ptr: new([]any), out: []any(nil)}, + {CaseName: Name(""), in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, + {CaseName: Name(""), in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, + + // composite tests + {CaseName: Name(""), in: allValueIndent, ptr: new(All), out: allValue}, + {CaseName: Name(""), in: allValueCompact, ptr: new(All), out: allValue}, + {CaseName: Name(""), in: allValueIndent, ptr: new(*All), out: &allValue}, + {CaseName: Name(""), in: allValueCompact, ptr: new(*All), out: &allValue}, + {CaseName: Name(""), in: pallValueIndent, ptr: new(All), out: pallValue}, + {CaseName: Name(""), in: pallValueCompact, ptr: new(All), out: pallValue}, + {CaseName: Name(""), in: pallValueIndent, ptr: new(*All), out: &pallValue}, + {CaseName: Name(""), in: pallValueCompact, ptr: new(*All), out: &pallValue}, + + // unmarshal interface test + {CaseName: Name(""), in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called + {CaseName: Name(""), in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, + {CaseName: Name(""), in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, + {CaseName: Name(""), in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, + {CaseName: Name(""), in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, + + // UnmarshalText interface test + {CaseName: Name(""), in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, + {CaseName: Name(""), in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, + {CaseName: Name(""), in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, + {CaseName: Name(""), in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, + {CaseName: Name(""), in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, + + // integer-keyed map test + { + CaseName: Name(""), + in: `{"-1":"a","0":"b","1":"c"}`, + ptr: new(map[int]string), + out: map[int]string{-1: "a", 0: "b", 1: "c"}, + }, + { + CaseName: Name(""), + in: `{"0":"a","10":"c","9":"b"}`, + ptr: new(map[u8]string), + out: map[u8]string{0: "a", 9: "b", 10: "c"}, + }, + { + CaseName: Name(""), + in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, + ptr: new(map[int64]string), + out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, + }, + { + CaseName: Name(""), + in: `{"18446744073709551615":"max"}`, + ptr: new(map[uint64]string), + out: map[uint64]string{math.MaxUint64: "max"}, + }, + { + CaseName: Name(""), + in: `{"0":false,"10":true}`, + ptr: new(map[uintptr]bool), + out: map[uintptr]bool{0: false, 10: true}, + }, + + // Check that MarshalText and UnmarshalText take precedence + // over default integer handling in map keys. + { + CaseName: Name(""), + in: `{"u2":4}`, + ptr: new(map[u8marshal]int), + out: map[u8marshal]int{2: 4}, + }, + { + CaseName: Name(""), + in: `{"2":4}`, + ptr: new(map[u8marshal]int), + out: map[u8marshal]int{}, + err: errMissingU8Prefix, + }, + + // integer-keyed map errors + { + CaseName: Name(""), + in: `{"abc":"abc"}`, + ptr: new(map[int]string), + out: map[int]string{}, + err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeFor[int](), Field: "abc", Offset: len64(`{`)}, + }, + { + CaseName: Name(""), + in: `{"256":"abc"}`, + ptr: new(map[uint8]string), + out: map[uint8]string{}, + err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeFor[uint8](), Field: "256", Offset: len64(`{`)}, + }, + { + CaseName: Name(""), + in: `{"128":"abc"}`, + ptr: new(map[int8]string), + out: map[int8]string{}, + err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeFor[int8](), Field: "128", Offset: len64(`{`)}, + }, + { + CaseName: Name(""), + in: `{"-1":"abc"}`, + ptr: new(map[uint8]string), + out: map[uint8]string{}, + err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeFor[uint8](), Field: "-1", Offset: len64(`{`)}, + }, + { + CaseName: Name(""), + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[int]int), + out: map[string]map[int]int{"F": {3: 4}}, + err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[int](), Field: "F.a", Offset: len64(`{"F":{`)}, + }, + { + CaseName: Name(""), + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[uint]int), + out: map[string]map[uint]int{"F": {3: 4}}, + err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeFor[uint](), Field: "F.a", Offset: len64(`{"F":{`)}, + }, + + // Map keys can be encoding.TextUnmarshalers. + {CaseName: Name(""), in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + // If multiple values for the same key exists, only the most recent value is used. + {CaseName: Name(""), in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + + { + CaseName: Name(""), + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12 + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18 + }`, + ptr: new(Top), + out: Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + embed: embed{ + Q: 18, + }, + }, + }, + { + CaseName: Name(""), + in: `{"hello": 1}`, + ptr: new(Ambig), + out: Ambig{First: 1}, + }, + + { + CaseName: Name(""), + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + out: S5{S8: S8{S9: S9{Y: 2}}}, + }, + { + CaseName: Name(""), + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + out: S5{S8: S8{S9{Y: 2}}}, + err: fmt.Errorf("json: unknown field \"X\""), + disallowUnknownFields: true, + }, + { + CaseName: Name(""), + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, + }, + { + CaseName: Name(""), + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + out: S10{S13: S13{S8{S9{Y: 2}}}}, + err: fmt.Errorf("json: unknown field \"X\""), + disallowUnknownFields: true, + }, + { + CaseName: Name(""), + in: `{"I": 0, "I": null, "J": null}`, + ptr: new(DoublePtr), + out: DoublePtr{I: nil, J: nil}, + }, + + // invalid UTF-8 is coerced to valid UTF-8. + { + CaseName: Name(""), + in: "\"hello\xffworld\"", + ptr: new(string), + out: "hello\ufffdworld", + }, + { + CaseName: Name(""), + in: "\"hello\xc2\xc2world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + CaseName: Name(""), + in: "\"hello\xc2\xffworld\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + CaseName: Name(""), + in: "\"hello\\ud800world\"", + ptr: new(string), + out: "hello\ufffdworld", + }, + { + CaseName: Name(""), + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + CaseName: Name(""), + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + CaseName: Name(""), + in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", + ptr: new(string), + out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", + }, + + // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. + { + CaseName: Name(""), + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[time.Time]string), + out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, + }, + + // issue 8305 + { + CaseName: Name(""), + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[Point]string), + out: map[Point]string{}, + err: &UnmarshalTypeError{Value: "string", Type: reflect.TypeFor[Point](), Field: `2009-11-10T23:00:00Z`, Offset: len64(`{`)}, + }, + { + CaseName: Name(""), + in: `{"asdf": "hello world"}`, + ptr: new(map[unmarshaler]string), + out: map[unmarshaler]string{}, + err: &UnmarshalTypeError{Value: "string", Type: reflect.TypeFor[unmarshaler](), Field: "asdf", Offset: len64(`{`)}, + }, + + // related to issue 13783. + // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, + // similar to marshaling a slice of typed int. + // These tests check that, assuming the byte type also has valid decoding methods, + // either the old base64 string encoding or the new per-element encoding can be + // successfully unmarshaled. The custom unmarshalers were accessible in earlier + // versions of Go, even though the custom marshaler was not. + { + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + golden: true, + }, + { + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + golden: true, + }, + { + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + golden: true, + }, + { + CaseName: Name(""), + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + golden: true, + }, + + // ints work with the marshaler but not the base64 []byte case + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalJSON), + out: []intWithMarshalJSON{1, 2, 3}, + golden: true, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalText), + out: []intWithMarshalText{1, 2, 3}, + golden: true, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalJSON), + out: []intWithPtrMarshalJSON{1, 2, 3}, + golden: true, + }, + { + CaseName: Name(""), + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalText), + out: []intWithPtrMarshalText{1, 2, 3}, + golden: true, + }, + + {CaseName: Name(""), in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, + {CaseName: Name(""), in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, + {CaseName: Name(""), in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, + {CaseName: Name(""), in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, + {CaseName: Name(""), in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, + {CaseName: Name(""), in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, + {CaseName: Name(""), in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, + {CaseName: Name(""), in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, + {CaseName: Name(""), in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, + {CaseName: Name(""), in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, + {CaseName: Name(""), in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, + + { + CaseName: Name(""), + in: `{"V": {"F2": "hello"}}`, + ptr: new(VOuter), + err: &UnmarshalTypeError{ + Value: "string", + Struct: "VOuter", + Field: "V.F2", + Type: reflect.TypeFor[int32](), + Offset: len64(`{"V": {"F2": `), + }, + }, + { + CaseName: Name(""), + in: `{"V": {"F4": {}, "F2": "hello"}}`, + ptr: new(VOuter), + out: VOuter{V: V{F4: &VOuter{}}}, + err: &UnmarshalTypeError{ + Value: "string", + Struct: "VOuter", + Field: "V.F2", + Type: reflect.TypeFor[int32](), + Offset: len64(`{"V": {"F4": {}, "F2": `), + }, + }, + + { + CaseName: Name(""), + in: `{"Level1a": "hello"}`, + ptr: new(Top), + out: Top{Embed0a: &Embed0a{}}, + err: &UnmarshalTypeError{ + Value: "string", + Struct: "Top", + Field: "Level1a", + Type: reflect.TypeFor[int](), + Offset: len64(`{"Level1a": `), + }, + }, + + // issue 15146. + // invalid inputs in wrongStringTests below. + {CaseName: Name(""), in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, + {CaseName: Name(""), in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, + {CaseName: Name(""), in: `{"B": "maybe"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "maybe"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, + {CaseName: Name(""), in: `{"B": "tru"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "tru"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, + {CaseName: Name(""), in: `{"B": "False"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "False"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, + {CaseName: Name(""), in: `{"B": "null"}`, ptr: new(B), out: B{false}}, + {CaseName: Name(""), in: `{"B": "nul"}`, ptr: new(B), err: &UnmarshalTypeError{Value: `string "nul"`, Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `), Err: strconv.ErrSyntax}}, + {CaseName: Name(""), in: `{"B": [2, 3]}`, ptr: new(B), err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[bool](), Struct: "B", Field: "B", Offset: len64(`{"B": `)}}, + + // additional tests for disallowUnknownFields + { + CaseName: Name(""), + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12 + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18, + "extra": true + }`, + ptr: new(Top), + out: Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{Level1a: 5, Level1b: 6}, + Embed0b: &Embed0b{Level1a: 8, Level1b: 9, Level1c: 10, Level1d: 11, Level1e: 12}, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + Loop: nil, + }, + Embed0p: Embed0p{ + Point: image.Point{ + X: 15, + Y: 16, + }, + }, + Embed0q: Embed0q{Point: Point{Z: 17}}, + embed: embed{Q: 18}, + }, + err: fmt.Errorf("json: unknown field \"extra\""), + disallowUnknownFields: true, + }, + { + CaseName: Name(""), + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12, + "extra": null + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18 + }`, + ptr: new(Top), + out: Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{Level1a: 5, Level1b: 6}, + Embed0b: &Embed0b{Level1a: 8, Level1b: 9, Level1c: 10, Level1d: 11, Level1e: 12}, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + Loop: nil, + }, + Embed0p: Embed0p{ + Point: image.Point{ + X: 15, + Y: 16, + }, + }, + Embed0q: Embed0q{Point: Point{Z: 17}}, + embed: embed{Q: 18}, + }, + err: fmt.Errorf("json: unknown field \"extra\""), + disallowUnknownFields: true, + }, + // issue 26444 + // UnmarshalTypeError without field & struct values + { + CaseName: Name(""), + in: `{"data":{"test1": "bob", "test2": 123}}`, + ptr: new(mapStringToStringData), + out: mapStringToStringData{map[string]string{"test1": "bob", "test2": ""}}, + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: len64(`{"data":{"test1": "bob", "test2": `), Struct: "mapStringToStringData", Field: "data.test2"}, + }, + { + CaseName: Name(""), + in: `{"data":{"test1": 123, "test2": "bob"}}`, + ptr: new(mapStringToStringData), + out: mapStringToStringData{Data: map[string]string{"test1": "", "test2": "bob"}}, + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[string](), Offset: len64(`{"data":{"test1": `), Struct: "mapStringToStringData", Field: "data.test1"}, + }, + + // trying to decode JSON arrays or objects via TextUnmarshaler + { + CaseName: Name(""), + in: `[1, 2, 3]`, + ptr: new(MustNotUnmarshalText), + err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeFor[MustNotUnmarshalText](), Err: errors.New("JSON value must be string type")}, + }, + { + CaseName: Name(""), + in: `{"foo": "bar"}`, + ptr: new(MustNotUnmarshalText), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeFor[MustNotUnmarshalText](), Err: errors.New("JSON value must be string type")}, + }, + // #22369 + { + CaseName: Name(""), + in: `{"PP": {"T": {"Y": "bad-type"}}}`, + ptr: new(P), + err: &UnmarshalTypeError{ + Value: "string", + Struct: "P", + Field: "PP.T.Y", + Type: reflect.TypeFor[int](), + Offset: len64(`{"PP": {"T": {"Y": `), + }, + }, + { + CaseName: Name(""), + in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, + ptr: new(PP), + out: PP{Ts: []T{{Y: 1}, {Y: 2}, {Y: 0}}}, + err: &UnmarshalTypeError{ + Value: "string", + Struct: "PP", + Field: "Ts.2.Y", + Type: reflect.TypeFor[int](), + Offset: len64(`{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": `), + }, + }, + // #14702 + { + CaseName: Name(""), + in: `invalid`, + ptr: new(Number), + err: &SyntaxError{ + msg: "invalid character 'i' looking for beginning of value", + Offset: len64(``), + }, + }, + { + CaseName: Name(""), + in: `"invalid"`, + ptr: new(Number), + err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, + }, + { + CaseName: Name(""), + in: `{"A":"invalid"}`, + ptr: new(struct{ A Number }), + err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, + }, + { + CaseName: Name(""), + in: `{"A":"invalid"}`, + ptr: new(struct { + A Number `json:",string"` + }), + err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, + }, + { + CaseName: Name(""), + in: `{"A":"invalid"}`, + ptr: new(map[string]Number), + out: map[string]Number{"A": ""}, + err: &UnmarshalTypeError{Value: `string "invalid"`, Type: reflect.TypeFor[Number](), Err: strconv.ErrSyntax}, + }, + + { + CaseName: Name(""), + in: `5`, + ptr: new(Number), + out: Number("5"), + }, + { + CaseName: Name(""), + in: `"5"`, + ptr: new(Number), + out: Number("5"), + }, + { + CaseName: Name(""), + in: `{"N":5}`, + ptr: new(struct{ N Number }), + out: struct{ N Number }{"5"}, + }, + { + CaseName: Name(""), + in: `{"N":"5"}`, + ptr: new(struct{ N Number }), + out: struct{ N Number }{"5"}, + }, + { + CaseName: Name(""), + in: `{"N":5}`, + ptr: new(struct { + N Number `json:",string"` + }), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[Number]()}, + }, + { + CaseName: Name(""), + in: `{"N":"5"}`, + ptr: new(struct { + N Number `json:",string"` + }), + out: struct { + N Number `json:",string"` + }{"5"}, + }, + + // Verify that syntactic errors are immediately fatal, + // while semantic errors are lazily reported + // (i.e., allow processing to continue). + { + CaseName: Name(""), + in: `[1,2,true,4,5}`, + ptr: new([]int), + err: &SyntaxError{msg: "invalid character '}' after array element", Offset: len64(`[1,2,true,4,5`)}, + }, + { + CaseName: Name(""), + in: `[1,2,true,4,5]`, + ptr: new([]int), + out: []int{1, 2, 0, 4, 5}, + err: &UnmarshalTypeError{Value: "bool", Type: reflect.TypeFor[int](), Field: "2", Offset: len64(`[1,2,`)}, + }, +} + +func TestMarshal(t *testing.T) { + b, err := Marshal(allValue) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + if string(b) != allValueCompact { + t.Errorf("Marshal:") + diff(t, b, []byte(allValueCompact)) + return + } + + b, err = Marshal(pallValue) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + if string(b) != pallValueCompact { + t.Errorf("Marshal:") + diff(t, b, []byte(pallValueCompact)) + return + } +} + +func TestMarshalInvalidUTF8(t *testing.T) { + tests := []struct { + CaseName + in string + want string + }{ + {Name(""), "hello\xffworld", `"hello\ufffdworld"`}, + {Name(""), "", `""`}, + {Name(""), "\xff", `"\ufffd"`}, + {Name(""), "\xff\xff", `"\ufffd\ufffd"`}, + {Name(""), "a\xffb", `"a\ufffdb"`}, + {Name(""), "\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + got, err := Marshal(tt.in) + if string(got) != tt.want || err != nil { + t.Errorf("%s: Marshal(%q):\n\tgot: (%q, %v)\n\twant: (%q, nil)", tt.Where, tt.in, got, err, tt.want) + } + }) + } +} + +func TestMarshalNumberZeroVal(t *testing.T) { + var n Number + out, err := Marshal(n) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + got := string(out) + if got != "0" { + t.Fatalf("Marshal: got %s, want 0", got) + } +} + +func TestMarshalEmbeds(t *testing.T) { + top := &Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + embed: embed{ + Q: 18, + }, + } + got, err := Marshal(top) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" + if string(got) != want { + t.Errorf("Marshal:\n\tgot: %s\n\twant: %s", got, want) + } +} + +func equalError(a, b error) bool { + isJSONError := func(err error) bool { + switch err.(type) { + case + *InvalidUTF8Error, + *InvalidUnmarshalError, + *MarshalerError, + *SyntaxError, + *UnmarshalFieldError, + *UnmarshalTypeError, + *UnsupportedTypeError, + *UnsupportedValueError: + return true + } + return false + } + + if a == nil || b == nil { + return a == nil && b == nil + } + if isJSONError(a) || isJSONError(b) { + return reflect.DeepEqual(a, b) // safe for locally defined error types + } + return a.Error() == b.Error() +} + +func TestUnmarshal(t *testing.T) { + for _, tt := range unmarshalTests { + t.Run(tt.Name, func(t *testing.T) { + in := []byte(tt.in) + if err := checkValid(in); err != nil { + if !equalError(err, tt.err) { + t.Fatalf("%s: checkValid error:\n\tgot %#v\n\twant %#v", tt.Where, err, tt.err) + } + } + if tt.ptr == nil { + return + } + + typ := reflect.TypeOf(tt.ptr) + if typ.Kind() != reflect.Pointer { + t.Fatalf("%s: unmarshalTest.ptr %T is not a pointer type", tt.Where, tt.ptr) + } + typ = typ.Elem() + + // v = new(right-type) + v := reflect.New(typ) + + if !reflect.DeepEqual(tt.ptr, v.Interface()) { + // There's no reason for ptr to point to non-zero data, + // as we decode into new(right-type), so the data is + // discarded. + // This can easily mean tests that silently don't test + // what they should. To test decoding into existing + // data, see TestPrefilled. + t.Fatalf("%s: unmarshalTest.ptr %#v is not a pointer to a zero value", tt.Where, tt.ptr) + } + + dec := NewDecoder(bytes.NewReader(in)) + if tt.useNumber { + dec.UseNumber() + } + if tt.disallowUnknownFields { + dec.DisallowUnknownFields() + } + if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { + t.Fatalf("%s: Decode error:\n\tgot: %v\n\twant: %v\n\n\tgot: %#v\n\twant: %#v", tt.Where, err, tt.err, err, tt.err) + } else if err != nil && tt.out == nil { + // Initialize tt.out during an error where there are no mutations, + // so the output is just the zero value of the input type. + tt.out = reflect.Zero(v.Elem().Type()).Interface() + } + if got := v.Elem().Interface(); !reflect.DeepEqual(got, tt.out) { + gotJSON, _ := Marshal(got) + wantJSON, _ := Marshal(tt.out) + t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", tt.Where, got, tt.out, gotJSON, wantJSON) + } + + // Check round trip also decodes correctly. + if tt.err == nil { + enc, err := Marshal(v.Interface()) + if err != nil { + t.Fatalf("%s: Marshal error after roundtrip: %v", tt.Where, err) + } + if tt.golden && !bytes.Equal(enc, in) { + t.Errorf("%s: Marshal:\n\tgot: %s\n\twant: %s", tt.Where, enc, in) + } + vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) + dec = NewDecoder(bytes.NewReader(enc)) + if tt.useNumber { + dec.UseNumber() + } + if err := dec.Decode(vv.Interface()); err != nil { + t.Fatalf("%s: Decode(%#q) error after roundtrip: %v", tt.Where, enc, err) + } + if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { + t.Fatalf("%s: Decode:\n\tgot: %#+v\n\twant: %#+v\n\n\tgotJSON: %s\n\twantJSON: %s", + tt.Where, v.Elem().Interface(), vv.Elem().Interface(), + stripWhitespace(string(enc)), stripWhitespace(string(in))) + } + } + }) + } +} + +func TestUnmarshalMarshal(t *testing.T) { + initBig() + var v any + if err := Unmarshal(jsonBig, &v); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + b, err := Marshal(v) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + if !bytes.Equal(jsonBig, b) { + t.Errorf("Marshal:") + diff(t, b, jsonBig) + return + } +} + +// Independent of Decode, basic coverage of the accessors in Number +func TestNumberAccessors(t *testing.T) { + tests := []struct { + CaseName + in string + i int64 + intErr string + f float64 + floatErr string + }{ + {CaseName: Name(""), in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, + {CaseName: Name(""), in: "-12", i: -12, f: -12.0}, + {CaseName: Name(""), in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + n := Number(tt.in) + if got := n.String(); got != tt.in { + t.Errorf("%s: Number(%q).String() = %s, want %s", tt.Where, tt.in, got, tt.in) + } + if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { + t.Errorf("%s: Number(%q).Int64() = %d, want %d", tt.Where, tt.in, i, tt.i) + } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { + t.Errorf("%s: Number(%q).Int64() error:\n\tgot: %v\n\twant: %v", tt.Where, tt.in, err, tt.intErr) + } + if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { + t.Errorf("%s: Number(%q).Float64() = %g, want %g", tt.Where, tt.in, f, tt.f) + } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { + t.Errorf("%s: Number(%q).Float64() error:\n\tgot %v\n\twant: %v", tt.Where, tt.in, err, tt.floatErr) + } + }) + } +} + +func TestLargeByteSlice(t *testing.T) { + s0 := make([]byte, 2000) + for i := range s0 { + s0[i] = byte(i) + } + b, err := Marshal(s0) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + var s1 []byte + if err := Unmarshal(b, &s1); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if !bytes.Equal(s0, s1) { + t.Errorf("Marshal:") + diff(t, s0, s1) + } +} + +type Xint struct { + X int +} + +func TestUnmarshalInterface(t *testing.T) { + var xint Xint + var i any = &xint + if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if xint.X != 1 { + t.Fatalf("xint.X = %d, want 1", xint.X) + } +} + +func TestUnmarshalPtrPtr(t *testing.T) { + var xint Xint + pxint := &xint + if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if xint.X != 1 { + t.Fatalf("xint.X = %d, want 1", xint.X) + } +} + +func TestEscape(t *testing.T) { + const input = `"foobar"` + " [\u2028 \u2029]" + const want = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` + got, err := Marshal(input) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + if string(got) != want { + t.Errorf("Marshal(%#q):\n\tgot: %s\n\twant: %s", input, got, want) + } +} + +// If people misuse the ,string modifier, the error message should be +// helpful, telling the user that they're doing it wrong. +func TestErrorMessageFromMisusedString(t *testing.T) { + // WrongString is a struct that's misusing the ,string modifier. + type WrongString struct { + Message string `json:"result,string"` + } + tests := []struct { + CaseName + in, err string + }{ + {Name(""), `{"result":"x"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character 'x' looking for beginning of object key string`}, + {Name(""), `{"result":"foo"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character 'f' looking for beginning of object key string`}, + {Name(""), `{"result":"123"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: invalid character '1' looking for beginning of object key string`}, + {Name(""), `{"result":123}`, `json: cannot unmarshal JSON number into WrongString.result of Go type string`}, + {Name(""), `{"result":"\""}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: unexpected end of JSON input`}, + {Name(""), `{"result":"\"foo"}`, `json: cannot unmarshal JSON string into WrongString.result of Go type string: unexpected end of JSON input`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + r := strings.NewReader(tt.in) + var s WrongString + err := NewDecoder(r).Decode(&s) + got := fmt.Sprintf("%v", err) + if got != tt.err { + t.Errorf("%s: Decode error:\n\tgot: %s\n\twant: %s", tt.Where, got, tt.err) + } + }) + } +} + +type All struct { + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Uintptr uintptr + Float32 float32 + Float64 float64 + + Foo string `json:"bar"` + Foo2 string `json:"bar2,dummyopt"` + + IntStr int64 `json:",string"` + UintptrStr uintptr `json:",string"` + + PBool *bool + PInt *int + PInt8 *int8 + PInt16 *int16 + PInt32 *int32 + PInt64 *int64 + PUint *uint + PUint8 *uint8 + PUint16 *uint16 + PUint32 *uint32 + PUint64 *uint64 + PUintptr *uintptr + PFloat32 *float32 + PFloat64 *float64 + + String string + PString *string + + Map map[string]Small + MapP map[string]*Small + PMap *map[string]Small + PMapP *map[string]*Small + + EmptyMap map[string]Small + NilMap map[string]Small + + Slice []Small + SliceP []*Small + PSlice *[]Small + PSliceP *[]*Small + + EmptySlice []Small + NilSlice []Small + + StringSlice []string + ByteSlice []byte + + Small Small + PSmall *Small + PPSmall **Small + + Interface any + PInterface *any + + unexported int +} + +type Small struct { + Tag string +} + +var allValue = All{ + Bool: true, + Int: 2, + Int8: 3, + Int16: 4, + Int32: 5, + Int64: 6, + Uint: 7, + Uint8: 8, + Uint16: 9, + Uint32: 10, + Uint64: 11, + Uintptr: 12, + Float32: 14.1, + Float64: 15.1, + Foo: "foo", + Foo2: "foo2", + IntStr: 42, + UintptrStr: 44, + String: "16", + Map: map[string]Small{ + "17": {Tag: "tag17"}, + "18": {Tag: "tag18"}, + }, + MapP: map[string]*Small{ + "19": {Tag: "tag19"}, + "20": nil, + }, + EmptyMap: map[string]Small{}, + Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, + SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, + EmptySlice: []Small{}, + StringSlice: []string{"str24", "str25", "str26"}, + ByteSlice: []byte{27, 28, 29}, + Small: Small{Tag: "tag30"}, + PSmall: &Small{Tag: "tag31"}, + Interface: 5.2, +} + +var pallValue = All{ + PBool: &allValue.Bool, + PInt: &allValue.Int, + PInt8: &allValue.Int8, + PInt16: &allValue.Int16, + PInt32: &allValue.Int32, + PInt64: &allValue.Int64, + PUint: &allValue.Uint, + PUint8: &allValue.Uint8, + PUint16: &allValue.Uint16, + PUint32: &allValue.Uint32, + PUint64: &allValue.Uint64, + PUintptr: &allValue.Uintptr, + PFloat32: &allValue.Float32, + PFloat64: &allValue.Float64, + PString: &allValue.String, + PMap: &allValue.Map, + PMapP: &allValue.MapP, + PSlice: &allValue.Slice, + PSliceP: &allValue.SliceP, + PPSmall: &allValue.PSmall, + PInterface: &allValue.Interface, +} + +var allValueIndent = `{ + "Bool": true, + "Int": 2, + "Int8": 3, + "Int16": 4, + "Int32": 5, + "Int64": 6, + "Uint": 7, + "Uint8": 8, + "Uint16": 9, + "Uint32": 10, + "Uint64": 11, + "Uintptr": 12, + "Float32": 14.1, + "Float64": 15.1, + "bar": "foo", + "bar2": "foo2", + "IntStr": "42", + "UintptrStr": "44", + "PBool": null, + "PInt": null, + "PInt8": null, + "PInt16": null, + "PInt32": null, + "PInt64": null, + "PUint": null, + "PUint8": null, + "PUint16": null, + "PUint32": null, + "PUint64": null, + "PUintptr": null, + "PFloat32": null, + "PFloat64": null, + "String": "16", + "PString": null, + "Map": { + "17": { + "Tag": "tag17" + }, + "18": { + "Tag": "tag18" + } + }, + "MapP": { + "19": { + "Tag": "tag19" + }, + "20": null + }, + "PMap": null, + "PMapP": null, + "EmptyMap": {}, + "NilMap": null, + "Slice": [ + { + "Tag": "tag20" + }, + { + "Tag": "tag21" + } + ], + "SliceP": [ + { + "Tag": "tag22" + }, + null, + { + "Tag": "tag23" + } + ], + "PSlice": null, + "PSliceP": null, + "EmptySlice": [], + "NilSlice": null, + "StringSlice": [ + "str24", + "str25", + "str26" + ], + "ByteSlice": "Gxwd", + "Small": { + "Tag": "tag30" + }, + "PSmall": { + "Tag": "tag31" + }, + "PPSmall": null, + "Interface": 5.2, + "PInterface": null +}` + +var allValueCompact = stripWhitespace(allValueIndent) + +var pallValueIndent = `{ + "Bool": false, + "Int": 0, + "Int8": 0, + "Int16": 0, + "Int32": 0, + "Int64": 0, + "Uint": 0, + "Uint8": 0, + "Uint16": 0, + "Uint32": 0, + "Uint64": 0, + "Uintptr": 0, + "Float32": 0, + "Float64": 0, + "bar": "", + "bar2": "", + "IntStr": "0", + "UintptrStr": "0", + "PBool": true, + "PInt": 2, + "PInt8": 3, + "PInt16": 4, + "PInt32": 5, + "PInt64": 6, + "PUint": 7, + "PUint8": 8, + "PUint16": 9, + "PUint32": 10, + "PUint64": 11, + "PUintptr": 12, + "PFloat32": 14.1, + "PFloat64": 15.1, + "String": "", + "PString": "16", + "Map": null, + "MapP": null, + "PMap": { + "17": { + "Tag": "tag17" + }, + "18": { + "Tag": "tag18" + } + }, + "PMapP": { + "19": { + "Tag": "tag19" + }, + "20": null + }, + "EmptyMap": null, + "NilMap": null, + "Slice": null, + "SliceP": null, + "PSlice": [ + { + "Tag": "tag20" + }, + { + "Tag": "tag21" + } + ], + "PSliceP": [ + { + "Tag": "tag22" + }, + null, + { + "Tag": "tag23" + } + ], + "EmptySlice": null, + "NilSlice": null, + "StringSlice": null, + "ByteSlice": null, + "Small": { + "Tag": "" + }, + "PSmall": null, + "PPSmall": { + "Tag": "tag31" + }, + "Interface": null, + "PInterface": 5.2 +}` + +var pallValueCompact = stripWhitespace(pallValueIndent) + +func TestRefUnmarshal(t *testing.T) { + type S struct { + // Ref is defined in encode_test.go. + R0 Ref + R1 *Ref + R2 RefText + R3 *RefText + } + want := S{ + R0: 12, + R1: new(Ref), + R2: 13, + R3: new(RefText), + } + *want.R1 = 12 + *want.R3 = 13 + + var got S + if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("Unmarsha:\n\tgot: %+v\n\twant: %+v", got, want) + } +} + +// Test that the empty string doesn't panic decoding when ,string is specified +// Issue 3450 +func TestEmptyString(t *testing.T) { + type T2 struct { + Number1 int `json:",string"` + Number2 int `json:",string"` + } + data := `{"Number1":"1", "Number2":""}` + dec := NewDecoder(strings.NewReader(data)) + var got T2 + switch err := dec.Decode(&got); { + case err == nil: + t.Fatalf("Decode error: got nil, want non-nil") + case got.Number1 != 1: + t.Fatalf("Decode: got.Number1 = %d, want 1", got.Number1) + } +} + +// Test that a null for ,string is not replaced with the previous quoted string (issue 7046). +// It should also not be an error (issue 2540, issue 8587). +func TestNullString(t *testing.T) { + type T struct { + A int `json:",string"` + B int `json:",string"` + C *int `json:",string"` + } + data := []byte(`{"A": "1", "B": null, "C": null}`) + var s T + s.B = 1 + s.C = new(int) + *s.C = 2 + switch err := Unmarshal(data, &s); { + case err != nil: + t.Fatalf("Unmarshal error: %v", err) + case s.B != 1: + t.Fatalf("Unmarshal: s.B = %d, want 1", s.B) + case s.C != nil: + t.Fatalf("Unmarshal: s.C = %d, want non-nil", s.C) + } +} + +func addr[T any](v T) *T { + return &v +} + +func TestInterfaceSet(t *testing.T) { + errUnmarshal := &UnmarshalTypeError{Value: "object", Offset: 5, Type: reflect.TypeFor[int](), Field: "X"} + tests := []struct { + CaseName + pre any + json string + post any + }{ + {Name(""), "foo", `"bar"`, "bar"}, + {Name(""), "foo", `2`, 2.0}, + {Name(""), "foo", `true`, true}, + {Name(""), "foo", `null`, nil}, + {Name(""), map[string]any{}, `true`, true}, + {Name(""), []string{}, `true`, true}, + + {Name(""), any(nil), `null`, any(nil)}, + {Name(""), (*int)(nil), `null`, any(nil)}, + {Name(""), (*int)(addr(0)), `null`, any(nil)}, + {Name(""), (*int)(addr(1)), `null`, any(nil)}, + {Name(""), (**int)(nil), `null`, any(nil)}, + {Name(""), (**int)(addr[*int](nil)), `null`, (**int)(addr[*int](nil))}, + {Name(""), (**int)(addr(addr(1))), `null`, (**int)(addr[*int](nil))}, + {Name(""), (***int)(nil), `null`, any(nil)}, + {Name(""), (***int)(addr[**int](nil)), `null`, (***int)(addr[**int](nil))}, + {Name(""), (***int)(addr(addr[*int](nil))), `null`, (***int)(addr[**int](nil))}, + {Name(""), (***int)(addr(addr(addr(1)))), `null`, (***int)(addr[**int](nil))}, + + {Name(""), any(nil), `2`, float64(2)}, + {Name(""), (int)(1), `2`, float64(2)}, + {Name(""), (*int)(nil), `2`, float64(2)}, + {Name(""), (*int)(addr(0)), `2`, (*int)(addr(2))}, + {Name(""), (*int)(addr(1)), `2`, (*int)(addr(2))}, + {Name(""), (**int)(nil), `2`, float64(2)}, + {Name(""), (**int)(addr[*int](nil)), `2`, (**int)(addr(addr(2)))}, + {Name(""), (**int)(addr(addr(1))), `2`, (**int)(addr(addr(2)))}, + {Name(""), (***int)(nil), `2`, float64(2)}, + {Name(""), (***int)(addr[**int](nil)), `2`, (***int)(addr(addr(addr(2))))}, + {Name(""), (***int)(addr(addr[*int](nil))), `2`, (***int)(addr(addr(addr(2))))}, + {Name(""), (***int)(addr(addr(addr(1)))), `2`, (***int)(addr(addr(addr(2))))}, + + {Name(""), any(nil), `{}`, map[string]any{}}, + {Name(""), (int)(1), `{}`, map[string]any{}}, + {Name(""), (*int)(nil), `{}`, map[string]any{}}, + {Name(""), (*int)(addr(0)), `{}`, errUnmarshal}, + {Name(""), (*int)(addr(1)), `{}`, errUnmarshal}, + {Name(""), (**int)(nil), `{}`, map[string]any{}}, + {Name(""), (**int)(addr[*int](nil)), `{}`, errUnmarshal}, + {Name(""), (**int)(addr(addr(1))), `{}`, errUnmarshal}, + {Name(""), (***int)(nil), `{}`, map[string]any{}}, + {Name(""), (***int)(addr[**int](nil)), `{}`, errUnmarshal}, + {Name(""), (***int)(addr(addr[*int](nil))), `{}`, errUnmarshal}, + {Name(""), (***int)(addr(addr(addr(1)))), `{}`, errUnmarshal}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + b := struct{ X any }{tt.pre} + blob := `{"X":` + tt.json + `}` + if err := Unmarshal([]byte(blob), &b); err != nil { + if wantErr, _ := tt.post.(error); equalError(err, wantErr) { + return + } + t.Fatalf("%s: Unmarshal(%#q) error: %v", tt.Where, blob, err) + } + if !reflect.DeepEqual(b.X, tt.post) { + t.Errorf("%s: Unmarshal(%#q):\n\tpre.X: %#v\n\tgot.X: %#v\n\twant.X: %#v", tt.Where, blob, tt.pre, b.X, tt.post) + } + }) + } +} + +type NullTest struct { + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float32 float32 + Float64 float64 + String string + PBool *bool + Map map[string]string + Slice []string + Interface any + + PRaw *RawMessage + PTime *time.Time + PBigInt *big.Int + PText *MustNotUnmarshalText + PBuffer *bytes.Buffer // has methods, just not relevant ones + PStruct *struct{} + + Raw RawMessage + Time time.Time + BigInt big.Int + Text MustNotUnmarshalText + Buffer bytes.Buffer + Struct struct{} +} + +// JSON null values should be ignored for primitives and string values instead of resulting in an error. +// Issue 2540 +func TestUnmarshalNulls(t *testing.T) { + // Unmarshal docs: + // 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. + + jsonData := []byte(`{ + "Bool" : null, + "Int" : null, + "Int8" : null, + "Int16" : null, + "Int32" : null, + "Int64" : null, + "Uint" : null, + "Uint8" : null, + "Uint16" : null, + "Uint32" : null, + "Uint64" : null, + "Float32" : null, + "Float64" : null, + "String" : null, + "PBool": null, + "Map": null, + "Slice": null, + "Interface": null, + "PRaw": null, + "PTime": null, + "PBigInt": null, + "PText": null, + "PBuffer": null, + "PStruct": null, + "Raw": null, + "Time": null, + "BigInt": null, + "Text": null, + "Buffer": null, + "Struct": null + }`) + nulls := NullTest{ + Bool: true, + Int: 2, + Int8: 3, + Int16: 4, + Int32: 5, + Int64: 6, + Uint: 7, + Uint8: 8, + Uint16: 9, + Uint32: 10, + Uint64: 11, + Float32: 12.1, + Float64: 13.1, + String: "14", + PBool: new(bool), + Map: map[string]string{}, + Slice: []string{}, + Interface: new(MustNotUnmarshalJSON), + PRaw: new(RawMessage), + PTime: new(time.Time), + PBigInt: new(big.Int), + PText: new(MustNotUnmarshalText), + PStruct: new(struct{}), + PBuffer: new(bytes.Buffer), + Raw: RawMessage("123"), + Time: time.Unix(123456789, 0), + BigInt: *big.NewInt(123), + } + + before := nulls.Time.String() + + err := Unmarshal(jsonData, &nulls) + if err != nil { + t.Errorf("Unmarshal of null values failed: %v", err) + } + if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || + nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || + nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { + t.Errorf("Unmarshal of null values affected primitives") + } + + if nulls.PBool != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBool") + } + if nulls.Map != nil { + t.Errorf("Unmarshal of null did not clear nulls.Map") + } + if nulls.Slice != nil { + t.Errorf("Unmarshal of null did not clear nulls.Slice") + } + if nulls.Interface != nil { + t.Errorf("Unmarshal of null did not clear nulls.Interface") + } + if nulls.PRaw != nil { + t.Errorf("Unmarshal of null did not clear nulls.PRaw") + } + if nulls.PTime != nil { + t.Errorf("Unmarshal of null did not clear nulls.PTime") + } + if nulls.PBigInt != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBigInt") + } + if nulls.PText != nil { + t.Errorf("Unmarshal of null did not clear nulls.PText") + } + if nulls.PBuffer != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBuffer") + } + if nulls.PStruct != nil { + t.Errorf("Unmarshal of null did not clear nulls.PStruct") + } + + if string(nulls.Raw) != "null" { + t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw)) + } + if nulls.Time.String() != before { + t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) + } + if nulls.BigInt.String() != "123" { + t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) + } +} + +type MustNotUnmarshalJSON struct{} + +func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error { + return errors.New("MustNotUnmarshalJSON was used") +} + +type MustNotUnmarshalText struct{} + +func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { + return errors.New("MustNotUnmarshalText was used") +} + +func TestStringKind(t *testing.T) { + type stringKind string + want := map[stringKind]int{"foo": 42} + data, err := Marshal(want) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + var got map[stringKind]int + err = Unmarshal(data, &got) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if !maps.Equal(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) + } +} + +// Custom types with []byte as underlying type could not be marshaled +// and then unmarshaled. +// Issue 8962. +func TestByteKind(t *testing.T) { + type byteKind []byte + want := byteKind("hello") + data, err := Marshal(want) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + var got byteKind + err = Unmarshal(data, &got) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if !slices.Equal(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) + } +} + +// The fix for issue 8962 introduced a regression. +// Issue 12921. +func TestSliceOfCustomByte(t *testing.T) { + type Uint8 uint8 + want := []Uint8("hello") + data, err := Marshal(want) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + var got []Uint8 + err = Unmarshal(data, &got) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if !slices.Equal(got, want) { + t.Fatalf("Marshal/Unmarshal mismatch:\n\tgot: %v\n\twant: %v", got, want) + } +} + +func TestUnmarshalTypeError(t *testing.T) { + tests := []struct { + CaseName + dest any + in string + }{ + {Name(""), new(string), `{"user": "name"}`}, // issue 4628. + {Name(""), new(error), `{}`}, // issue 4222 + {Name(""), new(error), `[]`}, + {Name(""), new(error), `""`}, + {Name(""), new(error), `123`}, + {Name(""), new(error), `true`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), tt.dest) + if _, ok := err.(*UnmarshalTypeError); !ok { + t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %T\n\twant: %T", + tt.Where, tt.in, tt.dest, err, new(UnmarshalTypeError)) + } + }) + } +} + +func TestUnmarshalSyntax(t *testing.T) { + var x any + tests := []struct { + CaseName + in string + }{ + {Name(""), "tru"}, + {Name(""), "fals"}, + {Name(""), "nul"}, + {Name(""), "123e"}, + {Name(""), `"hello`}, + {Name(""), `[1,2,3`}, + {Name(""), `{"key":1`}, + {Name(""), `{"key":1,`}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), &x) + if _, ok := err.(*SyntaxError); !ok { + t.Errorf("%s: Unmarshal(%#q, any):\n\tgot: %T\n\twant: %T", + tt.Where, tt.in, err, new(SyntaxError)) + } + }) + } +} + +// Test handling of unexported fields that should be ignored. +// Issue 4660 +type unexportedFields struct { + Name string + m map[string]any `json:"-"` + m2 map[string]any `json:"abcd"` + + s []int `json:"-"` +} + +func TestUnmarshalUnexported(t *testing.T) { + input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` + want := &unexportedFields{Name: "Bob"} + + out := &unexportedFields{} + err := Unmarshal([]byte(input), out) + if err != nil { + t.Errorf("Unmarshal error: %v", err) + } + if !reflect.DeepEqual(out, want) { + t.Errorf("Unmarshal:\n\tgot: %+v\n\twant: %+v", out, want) + } +} + +// Time3339 is a time.Time which encodes to and from JSON +// as an RFC 3339 time in UTC. +type Time3339 time.Time + +func (t *Time3339) UnmarshalJSON(b []byte) error { + if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { + return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) + } + tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) + if err != nil { + return err + } + *t = Time3339(tm) + return nil +} + +func TestUnmarshalJSONLiteralError(t *testing.T) { + var t3 Time3339 + switch err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3); { + case err == nil: + t.Fatalf("Unmarshal error: got nil, want non-nil") + case !strings.Contains(err.Error(), "range"): + t.Errorf("Unmarshal error:\n\tgot: %v\n\twant: out of range", err) + } +} + +// Test that extra object elements in an array do not result in a +// "data changing underfoot" error. +// Issue 3717 +func TestSkipArrayObjects(t *testing.T) { + json := `[{}]` + var dest [0]any + + err := Unmarshal([]byte(json), &dest) + if err != nil { + t.Errorf("Unmarshal error: %v", err) + } +} + +// Test semantics of pre-filled data, such as struct fields, map elements, +// slices, and arrays. +// Issues 4900 and 8837, among others. +func TestPrefilled(t *testing.T) { + // Values here change, cannot reuse table across runs. + tests := []struct { + CaseName + in string + ptr any + out any + }{{ + CaseName: Name(""), + in: `{"X": 1, "Y": 2}`, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + }, { + CaseName: Name(""), + in: `{"X": 1, "Y": 2}`, + ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, + out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, + }, { + CaseName: Name(""), + in: `[2]`, + ptr: &[]int{1}, + out: &[]int{2}, + }, { + CaseName: Name(""), + in: `[2, 3]`, + ptr: &[]int{1}, + out: &[]int{2, 3}, + }, { + CaseName: Name(""), + in: `[2, 3]`, + ptr: &[...]int{1}, + out: &[...]int{2}, + }, { + CaseName: Name(""), + in: `[3]`, + ptr: &[...]int{1, 2}, + out: &[...]int{3, 0}, + }} + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + ptrstr := fmt.Sprintf("%v", tt.ptr) + err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here + if err != nil { + t.Errorf("%s: Unmarshal error: %v", tt.Where, err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("%s: Unmarshal(%#q, %T):\n\tgot: %v\n\twant: %v", tt.Where, tt.in, ptrstr, tt.ptr, tt.out) + } + }) + } +} + +func TestInvalidUnmarshal(t *testing.T) { + tests := []struct { + CaseName + in string + v any + wantErr error + }{ + {Name(""), `{"a":"1"}`, nil, &InvalidUnmarshalError{}}, + {Name(""), `{"a":"1"}`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, + {Name(""), `{"a":"1"}`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, + {Name(""), `123`, nil, &InvalidUnmarshalError{}}, + {Name(""), `123`, struct{}{}, &InvalidUnmarshalError{reflect.TypeFor[struct{}]()}}, + {Name(""), `123`, (*int)(nil), &InvalidUnmarshalError{reflect.TypeFor[*int]()}}, + {Name(""), `123`, new(net.IP), &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[net.IP](), Offset: len64(``), Err: errors.New("JSON value must be string type")}}, + } + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + switch gotErr := Unmarshal([]byte(tt.in), tt.v); { + case gotErr == nil: + t.Fatalf("%s: Unmarshal error: got nil, want non-nil", tt.Where) + case !reflect.DeepEqual(gotErr, tt.wantErr): + t.Errorf("%s: Unmarshal error:\n\tgot: %#v\n\twant: %#v", tt.Where, gotErr, tt.wantErr) + } + }) + } +} + +// Test that string option is ignored for invalid types. +// Issue 9812. +func TestInvalidStringOption(t *testing.T) { + num := 0 + item := struct { + T time.Time `json:",string"` + M map[string]string `json:",string"` + S []string `json:",string"` + A [1]string `json:",string"` + I any `json:",string"` + P *int `json:",string"` + }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} + + data, err := Marshal(item) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + err = Unmarshal(data, &item) + if err != nil { + t.Fatalf("Unmarshal error: %v", err) + } +} + +// Test unmarshal behavior with regards to embedded unexported structs. +// +// (Issue 21357) If the embedded struct is a pointer and is unallocated, +// this returns an error because unmarshal cannot set the field. +// +// (Issue 24152) If the embedded struct is given an explicit name, +// ensure that the normal unmarshal logic does not panic in reflect. +// +// (Issue 28145) If the embedded struct is given an explicit name and has +// exported methods, don't cause a panic trying to get its value. +func TestUnmarshalEmbeddedUnexported(t *testing.T) { + type ( + embed1 struct{ Q int } + embed2 struct{ Q int } + embed3 struct { + Q int64 `json:",string"` + } + S1 struct { + *embed1 + R int + } + S2 struct { + *embed1 + Q int + } + S3 struct { + embed1 + R int + } + S4 struct { + *embed1 + embed2 + } + S5 struct { + *embed3 + R int + } + S6 struct { + embed1 `json:"embed1"` + } + S7 struct { + embed1 `json:"embed1"` + embed2 + } + S8 struct { + embed1 `json:"embed1"` + embed2 `json:"embed2"` + Q int + } + S9 struct { + unexportedWithMethods `json:"embed"` + } + ) + + tests := []struct { + CaseName + in string + ptr any + out any + err error + }{{ + // Error since we cannot set S1.embed1, but still able to set S1.R. + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S1), + out: &S1{R: 2}, + err: &UnmarshalTypeError{ + Type: reflect.TypeFor[S1](), + Offset: len64(`{"R":2,"Q":`), + Struct: "S1", + Field: "Q", + Err: errors.New("cannot set embedded pointer to unexported struct type"), + }, + }, { + // The top level Q field takes precedence. + CaseName: Name(""), + in: `{"Q":1}`, + ptr: new(S2), + out: &S2{Q: 1}, + }, { + // No issue with non-pointer variant. + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S3), + out: &S3{embed1: embed1{Q: 1}, R: 2}, + }, { + // No error since both embedded structs have field R, which annihilate each other. + // Thus, no attempt is made at setting S4.embed1. + CaseName: Name(""), + in: `{"R":2}`, + ptr: new(S4), + out: new(S4), + }, { + // Error since we cannot set S5.embed1, but still able to set S5.R. + CaseName: Name(""), + in: `{"R":2,"Q":1}`, + ptr: new(S5), + out: &S5{R: 2}, + err: &UnmarshalTypeError{ + Type: reflect.TypeFor[S5](), + Offset: len64(`{"R":2,"Q":`), + Struct: "S5", + Field: "Q", + Err: errors.New("cannot set embedded pointer to unexported struct type"), + }, + }, { + // Issue 24152, ensure decodeState.indirect does not panic. + CaseName: Name(""), + in: `{"embed1": {"Q": 1}}`, + ptr: new(S6), + out: &S6{embed1{1}}, + }, { + // Issue 24153, check that we can still set forwarded fields even in + // the presence of a name conflict. + // + // This relies on obscure behavior of reflect where it is possible + // to set a forwarded exported field on an unexported embedded struct + // even though there is a name conflict, even when it would have been + // impossible to do so according to Go visibility rules. + // Go forbids this because it is ambiguous whether S7.Q refers to + // S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported, + // it should be impossible for an external package to set either Q. + // + // It is probably okay for a future reflect change to break this. + CaseName: Name(""), + in: `{"embed1": {"Q": 1}, "Q": 2}`, + ptr: new(S7), + out: &S7{embed1{1}, embed2{2}}, + }, { + // Issue 24153, similar to the S7 case. + CaseName: Name(""), + in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, + ptr: new(S8), + out: &S8{embed1{1}, embed2{2}, 3}, + }, { + // Issue 228145, similar to the cases above. + CaseName: Name(""), + in: `{"embed": {}}`, + ptr: new(S9), + out: &S9{}, + }} + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.in), tt.ptr) + if !equalError(err, tt.err) { + t.Errorf("%s: Unmarshal error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("%s: Unmarshal:\n\tgot: %#+v\n\twant: %#+v", tt.Where, tt.ptr, tt.out) + } + }) + } +} + +func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { + tests := []struct { + CaseName + in string + err error + }{{ + CaseName: Name(""), + in: `1 false null :`, + err: &SyntaxError{"invalid character ':' looking for beginning of value", len64(`1 false null `)}, + }, { + CaseName: Name(""), + in: `1 [] [,]`, + err: &SyntaxError{"invalid character ',' looking for beginning of value", len64(`1 [] [`)}, + }, { + CaseName: Name(""), + in: `1 [] [true:]`, + err: &SyntaxError{"invalid character ':' after array element", len64(`1 [] [true`)}, + }, { + CaseName: Name(""), + in: `1 {} {"x"=}`, + err: &SyntaxError{"invalid character '=' after object key", len64(`1 {} {"x"`)}, + }, { + CaseName: Name(""), + in: `falsetruenul#`, + err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", len64(`falsetruenul`)}, + }} + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + dec := NewDecoder(strings.NewReader(tt.in)) + var err error + for err == nil { + var v any + err = dec.Decode(&v) + } + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("%s: Decode error:\n\tgot: %v\n\twant: %v", tt.Where, err, tt.err) + } + }) + } +} + +type unmarshalPanic struct{} + +func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } + +func TestUnmarshalPanic(t *testing.T) { + defer func() { + if got := recover(); !reflect.DeepEqual(got, 0xdead) { + t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) + } + }() + Unmarshal([]byte("{}"), &unmarshalPanic{}) + t.Fatalf("Unmarshal should have panicked") +} + +type textUnmarshalerString string + +func (m *textUnmarshalerString) UnmarshalText(text []byte) error { + *m = textUnmarshalerString(strings.ToLower(string(text))) + return nil +} + +// Test unmarshal to a map, where the map key is a user defined type. +// See golang.org/issues/34437. +func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { + var p map[textUnmarshalerString]string + if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + if _, ok := p["foo"]; !ok { + t.Errorf(`key "foo" missing in map: %v`, p) + } +} + +func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { + // See golang.org/issues/38105. + var p map[textUnmarshalerString]string + if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if _, ok := p["开源"]; !ok { + t.Errorf(`key "开源" missing in map: %v`, p) + } + + // See golang.org/issues/38126. + type T struct { + F1 string `json:"F1,string"` + } + wantT := T{"aaa\tbbb"} + + b, err := Marshal(wantT) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + var gotT T + if err := Unmarshal(b, &gotT); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + if gotT != wantT { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) + } + + // See golang.org/issues/39555. + input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} + + encoded, err := Marshal(input) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + var got map[textUnmarshalerString]string + if err := Unmarshal(encoded, &got); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + want := map[textUnmarshalerString]string{"foo": "", `"`: ""} + if !maps.Equal(got, want) { + t.Errorf("Marshal/Unmarshal roundtrip:\n\tgot: %q\n\twant: %q", gotT, wantT) + } +} + +func TestUnmarshalMaxDepth(t *testing.T) { + tests := []struct { + CaseName + data string + errMaxDepth bool + }{{ + CaseName: Name("ArrayUnderMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, + errMaxDepth: false, + }, { + CaseName: Name("ArrayOverMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ArrayOverStackDepth"), + data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ObjectUnderMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, + errMaxDepth: false, + }, { + CaseName: Name("ObjectOverMaxNestingDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, + errMaxDepth: true, + }, { + CaseName: Name("ObjectOverStackDepth"), + data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, + errMaxDepth: true, + }} + + targets := []struct { + CaseName + newValue func() any + }{{ + CaseName: Name("unstructured"), + newValue: func() any { + var v any + return &v + }, + }, { + CaseName: Name("typed named field"), + newValue: func() any { + v := struct { + A any `json:"a"` + }{} + return &v + }, + }, { + CaseName: Name("typed missing field"), + newValue: func() any { + v := struct { + B any `json:"b"` + }{} + return &v + }, + }, { + CaseName: Name("custom unmarshaler"), + newValue: func() any { + v := unmarshaler{} + return &v + }, + }} + + for _, tt := range tests { + for _, target := range targets { + t.Run(target.Name+"-"+tt.Name, func(t *testing.T) { + err := Unmarshal([]byte(tt.data), target.newValue()) + if !tt.errMaxDepth { + if err != nil { + t.Errorf("%s: %s: Unmarshal error: %v", tt.Where, target.Where, err) + } + } else { + if err == nil || !strings.Contains(err.Error(), "exceeded max depth") { + t.Errorf("%s: %s: Unmarshal error:\n\tgot: %v\n\twant: exceeded max depth", tt.Where, target.Where, err) + } + } + }) + } + } +} diff --git a/src/encoding/json/v2_diff_test.go b/src/encoding/json/v2_diff_test.go new file mode 100644 index 0000000000..871be49776 --- /dev/null +++ b/src/encoding/json/v2_diff_test.go @@ -0,0 +1,1129 @@ +// Copyright 2020 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 + +package json_test + +import ( + "errors" + "path" + "reflect" + "strings" + "testing" + "time" + + jsonv1 "encoding/json" + "encoding/json/jsontext" + jsonv2 "encoding/json/v2" +) + +// NOTE: This file serves as a list of semantic differences between v1 and v2. +// Each test explains how v1 behaves, how v2 behaves, and +// a rationale for why the behavior was changed. + +var jsonPackages = []struct { + Version string + Marshal func(any) ([]byte, error) + Unmarshal func([]byte, any) error +}{ + {"v1", jsonv1.Marshal, jsonv1.Unmarshal}, + {"v2", + func(in any) ([]byte, error) { return jsonv2.Marshal(in) }, + func(in []byte, out any) error { return jsonv2.Unmarshal(in, out) }}, +} + +// In v1, unmarshal matches struct fields using a case-insensitive match. +// In v2, unmarshal matches struct fields using a case-sensitive match. +// +// Case-insensitive matching is a surprising default and +// incurs significant performance cost when unmarshaling unknown fields. +// In v2, we can opt into v1-like behavior with the `case:ignore` tag option. +// The case-insensitive matching performed by v2 is looser than that of v1 +// where it also ignores dashes and underscores. +// This allows v2 to match fields regardless of whether the name is in +// snake_case, camelCase, or kebab-case. +// +// Related issue: +// +// https://go.dev/issue/14750 +func TestCaseSensitivity(t *testing.T) { + type Fields struct { + FieldA bool + FieldB bool `json:"fooBar"` + FieldC bool `json:"fizzBuzz,case:ignore"` // `case:ignore` is used by v2 to explicitly enable case-insensitive matching + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + // This is a mapping from Go field names to JSON member names to + // whether the JSON member name would match the Go field name. + type goName = string + type jsonName = string + onlyV1 := json.Version == "v1" + onlyV2 := json.Version == "v2" + allMatches := map[goName]map[jsonName]bool{ + "FieldA": { + "FieldA": true, // exact match + "fielda": onlyV1, // v1 is case-insensitive by default + "fieldA": onlyV1, // v1 is case-insensitive by default + "FIELDA": onlyV1, // v1 is case-insensitive by default + "FieldB": false, + "FieldC": false, + }, + "FieldB": { + "fooBar": true, // exact match for explicitly specified JSON name + "FooBar": onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided + "foobar": onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided + "FOOBAR": onlyV1, // v1 is case-insensitive even if an explicit JSON name is provided + "fizzBuzz": false, + "FieldA": false, + "FieldB": false, // explicit JSON name means that the Go field name is not used for matching + "FieldC": false, + }, + "FieldC": { + "fizzBuzz": true, // exact match for explicitly specified JSON name + "fizzbuzz": true, // v2 is case-insensitive due to `case:ignore` tag + "FIZZBUZZ": true, // v2 is case-insensitive due to `case:ignore` tag + "fizz_buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores + "fizz-buzz": onlyV2, // case-insensitivity in v2 ignores dashes and underscores + "fooBar": false, + "FieldA": false, + "FieldC": false, // explicit JSON name means that the Go field name is not used for matching + "FieldB": false, + }, + } + + for goFieldName, matches := range allMatches { + for jsonMemberName, wantMatch := range matches { + in := `{"` + jsonMemberName + `":true}` + var s Fields + if err := json.Unmarshal([]byte(in), &s); err != nil { + t.Fatalf("json.Unmarshal error: %v", err) + } + gotMatch := reflect.ValueOf(s).FieldByName(goFieldName).Bool() + if gotMatch != wantMatch { + t.Fatalf("%T.%s = %v, want %v", s, goFieldName, gotMatch, wantMatch) + } + } + } + }) + } +} + +// In v1, the "omitempty" option specifies that a struct field is omitted +// when marshaling if it 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 v2, the "omitempty" option specifies that a struct field is omitted +// when marshaling if it is an empty JSON value, which is defined as +// a JSON null or empty JSON string, object, or array. +// +// In v2, we also provide the "omitzero" option which specifies that a field +// is omitted if it is the zero Go value or if it implements an "IsZero() bool" +// method that reports true. Together, "omitzero" and "omitempty" can cover +// all the prior use cases of the v1 definition of "omitempty". +// Note that "omitempty" is defined in terms of the Go type system in v1, +// but now defined in terms of the JSON type system in v2. +// +// Related issues: +// +// https://go.dev/issue/11939 +// https://go.dev/issue/22480 +// https://go.dev/issue/29310 +// https://go.dev/issue/32675 +// https://go.dev/issue/45669 +// https://go.dev/issue/45787 +// https://go.dev/issue/50480 +// https://go.dev/issue/52803 +func TestOmitEmptyOption(t *testing.T) { + type Struct struct { + Foo string `json:",omitempty"` + Bar []int `json:",omitempty"` + Baz *Struct `json:",omitempty"` + } + type Types struct { + Bool bool `json:",omitempty"` + StringA string `json:",omitempty"` + StringB string `json:",omitempty"` + BytesA []byte `json:",omitempty"` + BytesB []byte `json:",omitempty"` + BytesC []byte `json:",omitempty"` + Int int `json:",omitempty"` + MapA map[string]string `json:",omitempty"` + MapB map[string]string `json:",omitempty"` + MapC map[string]string `json:",omitempty"` + StructA Struct `json:",omitempty"` + StructB Struct `json:",omitempty"` + StructC Struct `json:",omitempty"` + SliceA []string `json:",omitempty"` + SliceB []string `json:",omitempty"` + SliceC []string `json:",omitempty"` + Array [1]string `json:",omitempty"` + PointerA *string `json:",omitempty"` + PointerB *string `json:",omitempty"` + PointerC *string `json:",omitempty"` + InterfaceA any `json:",omitempty"` + InterfaceB any `json:",omitempty"` + InterfaceC any `json:",omitempty"` + InterfaceD any `json:",omitempty"` + } + + something := "something" + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + in := Types{ + Bool: false, + StringA: "", + StringB: something, + BytesA: nil, + BytesB: []byte{}, + BytesC: []byte(something), + Int: 0, + MapA: nil, + MapB: map[string]string{}, + MapC: map[string]string{something: something}, + StructA: Struct{}, + StructB: Struct{Bar: []int{}, Baz: new(Struct)}, + StructC: Struct{Foo: something}, + SliceA: nil, + SliceB: []string{}, + SliceC: []string{something}, + Array: [1]string{something}, + PointerA: nil, + PointerB: new(string), + PointerC: &something, + InterfaceA: nil, + InterfaceB: (*string)(nil), + InterfaceC: new(string), + InterfaceD: &something, + } + b, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + var out map[string]any + if err := json.Unmarshal(b, &out); err != nil { + t.Fatalf("json.Unmarshal error: %v", err) + } + + onlyV1 := json.Version == "v1" + onlyV2 := json.Version == "v2" + wantPresent := map[string]bool{ + "Bool": onlyV2, // false is an empty Go bool, but is NOT an empty JSON value + "StringA": false, + "StringB": true, + "BytesA": false, + "BytesB": false, + "BytesC": true, + "Int": onlyV2, // 0 is an empty Go integer, but NOT an empty JSON value + "MapA": false, + "MapB": false, + "MapC": true, + "StructA": onlyV1, // Struct{} is NOT an empty Go value, but {} is an empty JSON value + "StructB": onlyV1, // Struct{...} is NOT an empty Go value, but {} is an empty JSON value + "StructC": true, + "SliceA": false, + "SliceB": false, + "SliceC": true, + "Array": true, + "PointerA": false, + "PointerB": onlyV1, // new(string) is NOT a nil Go pointer, but "" is an empty JSON value + "PointerC": true, + "InterfaceA": false, + "InterfaceB": onlyV1, // (*string)(nil) is NOT a nil Go interface, but null is an empty JSON value + "InterfaceC": onlyV1, // new(string) is NOT a nil Go interface, but "" is an empty JSON value + "InterfaceD": true, + } + for field, want := range wantPresent { + _, got := out[field] + if got != want { + t.Fatalf("%T.%s = %v, want %v", in, field, got, want) + } + } + }) + } +} + +func addr[T any](v T) *T { + return &v +} + +// In v1, the "string" option specifies that Go strings, bools, and numeric +// values are encoded within a JSON string when marshaling and +// are unmarshaled from its native representation escaped within a JSON string. +// The "string" option is not applied recursively, and so does not affect +// strings, bools, and numeric values within a Go slice or map, but +// does have special handling to affect the underlying value within a pointer. +// When unmarshaling, the "string" option permits decoding from a JSON null +// escaped within a JSON string in some inconsistent cases. +// +// In v2, the "string" option specifies that only numeric values are encoded as +// a JSON number within a JSON string when marshaling and are unmarshaled +// from either a JSON number or a JSON string containing a JSON number. +// The "string" option is applied recursively to all numeric sub-values, +// and thus affects numeric values within a Go slice or map. +// There is no support for escaped JSON nulls within a JSON string. +// +// The main utility for stringifying JSON numbers is because JSON parsers +// often represents numbers as IEEE 754 floating-point numbers. +// This results in a loss of precision representing 64-bit integer values. +// Consequently, many JSON-based APIs actually requires that such values +// be encoded within a JSON string. Since the main utility of stringification +// is for numeric values, v2 limits the effect of the "string" option +// to just numeric Go types. According to all code known by the Go module proxy, +// there are close to zero usages of the "string" option on a Go string or bool. +// +// Regarding the recursive application of the "string" option, +// there have been a number of issues filed about users being surprised that +// the "string" option does not recursively affect numeric values +// within a composite type like a Go map, slice, or interface value. +// In v1, specifying the "string" option on composite type has no effect +// and so this would be a largely backwards compatible change. +// +// The ability to decode from a JSON null wrapped within a JSON string +// is removed in v2 because this behavior was surprising and inconsistent in v1. +// +// Related issues: +// +// https://go.dev/issue/15624 +// https://go.dev/issue/20651 +// https://go.dev/issue/22177 +// https://go.dev/issue/32055 +// https://go.dev/issue/32117 +// https://go.dev/issue/50997 +func TestStringOption(t *testing.T) { + type Types struct { + String string `json:",string"` + Bool bool `json:",string"` + Int int `json:",string"` + Float float64 `json:",string"` + Map map[string]int `json:",string"` + Struct struct{ Field int } `json:",string"` + Slice []int `json:",string"` + Array [1]int `json:",string"` + PointerA *int `json:",string"` + PointerB *int `json:",string"` + PointerC **int `json:",string"` + InterfaceA any `json:",string"` + InterfaceB any `json:",string"` + } + + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + in := Types{ + String: "string", + Bool: true, + Int: 1, + Float: 1, + Map: map[string]int{"Name": 1}, + Struct: struct{ Field int }{1}, + Slice: []int{1}, + Array: [1]int{1}, + PointerA: nil, + PointerB: addr(1), + PointerC: addr(addr(1)), + InterfaceA: nil, + InterfaceB: 1, + } + quote := func(s string) string { + b, _ := jsontext.AppendQuote(nil, s) + return string(b) + } + quoteOnlyV1 := func(s string) string { + if json.Version == "v1" { + s = quote(s) + } + return s + } + quoteOnlyV2 := func(s string) string { + if json.Version == "v2" { + s = quote(s) + } + return s + } + want := strings.Join([]string{ + `{`, + `"String":` + quoteOnlyV1(`"string"`) + `,`, // in v1, Go strings are also stringified + `"Bool":` + quoteOnlyV1("true") + `,`, // in v1, Go bools are also stringified + `"Int":` + quote("1") + `,`, + `"Float":` + quote("1") + `,`, + `"Map":{"Name":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified + `"Struct":{"Field":` + quoteOnlyV2("1") + `},`, // in v2, numbers are recursively stringified + `"Slice":[` + quoteOnlyV2("1") + `],`, // in v2, numbers are recursively stringified + `"Array":[` + quoteOnlyV2("1") + `],`, // in v2, numbers are recursively stringified + `"PointerA":null,`, + `"PointerB":` + quote("1") + `,`, // in v1, numbers are stringified after a single pointer indirection + `"PointerC":` + quoteOnlyV2("1") + `,`, // in v2, numbers are recursively stringified + `"InterfaceA":null,`, + `"InterfaceB":` + quoteOnlyV2("1") + ``, // in v2, numbers are recursively stringified + `}`}, "") + got, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + if string(got) != want { + t.Fatalf("json.Marshal = %s, want %s", got, want) + } + }) + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal/Null", json.Version), func(t *testing.T) { + var got Types + err := json.Unmarshal([]byte(`{ + "Bool": "null", + "Int": "null", + "PointerA": "null" + }`), &got) + switch { + case !reflect.DeepEqual(got, Types{}): + t.Fatalf("json.Unmarshal = %v, want %v", got, Types{}) + case json.Version == "v1" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case json.Version == "v2" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + } + }) + + t.Run(path.Join("Unmarshal/Bool", json.Version), func(t *testing.T) { + var got Types + want := map[string]Types{ + "v1": {Bool: true}, + "v2": {Bool: false}, + }[json.Version] + err := json.Unmarshal([]byte(`{"Bool": "true"}`), &got) + switch { + case !reflect.DeepEqual(got, want): + t.Fatalf("json.Unmarshal = %v, want %v", got, want) + case json.Version == "v1" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case json.Version == "v2" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + } + }) + + t.Run(path.Join("Unmarshal/Shallow", json.Version), func(t *testing.T) { + var got Types + want := Types{Int: 1, PointerB: addr(1)} + err := json.Unmarshal([]byte(`{ + "Int": "1", + "PointerB": "1" + }`), &got) + switch { + case !reflect.DeepEqual(got, want): + t.Fatalf("json.Unmarshal = %v, want %v", got, want) + case err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + } + }) + + t.Run(path.Join("Unmarshal/Deep", json.Version), func(t *testing.T) { + var got Types + want := map[string]Types{ + "v1": { + Map: map[string]int{"Name": 0}, + Slice: []int{0}, + PointerC: addr(addr(0)), + }, + "v2": { + Map: map[string]int{"Name": 1}, + Struct: struct{ Field int }{1}, + Slice: []int{1}, + Array: [1]int{1}, + PointerC: addr(addr(1)), + }, + }[json.Version] + err := json.Unmarshal([]byte(`{ + "Map": {"Name":"1"}, + "Struct": {"Field":"1"}, + "Slice": ["1"], + "Array": ["1"], + "PointerC": "1" + }`), &got) + switch { + case !reflect.DeepEqual(got, want): + t.Fatalf("json.Unmarshal =\n%v, want\n%v", got, want) + case json.Version == "v1" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + case json.Version == "v2" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + } + }) + } +} + +// In v1, nil slices and maps are marshaled as a JSON null. +// In v2, nil slices and maps are marshaled as an empty JSON object or array. +// +// Users of v2 can opt into the v1 behavior by setting +// the "format:emitnull" option in the `json` struct field tag: +// +// struct { +// S []string `json:",format:emitnull"` +// M map[string]string `json:",format:emitnull"` +// } +// +// JSON is a language-agnostic data interchange format. +// The fact that maps and slices are nil-able in Go is a semantic detail of the +// Go language. We should avoid leaking such details to the JSON representation. +// When JSON implementations leak language-specific details, +// it complicates transition to/from languages with different type systems. +// +// Furthermore, consider two related Go types: string and []byte. +// It's an asymmetric oddity of v1 that zero values of string and []byte marshal +// as an empty JSON string for the former, while the latter as a JSON null. +// The non-zero values of those types always marshal as JSON strings. +// +// Related issues: +// +// https://go.dev/issue/27589 +// https://go.dev/issue/37711 +func TestNilSlicesAndMaps(t *testing.T) { + type Composites struct { + B []byte // always encoded in v2 as a JSON string + S []string // always encoded in v2 as a JSON array + M map[string]string // always encoded in v2 as a JSON object + } + + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + in := []Composites{ + {B: []byte(nil), S: []string(nil), M: map[string]string(nil)}, + {B: []byte{}, S: []string{}, M: map[string]string{}}, + } + want := map[string]string{ + "v1": `[{"B":null,"S":null,"M":null},{"B":"","S":[],"M":{}}]`, + "v2": `[{"B":"","S":[],"M":{}},{"B":"","S":[],"M":{}}]`, // v2 emits nil slices and maps as empty JSON objects and arrays + }[json.Version] + got, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + if string(got) != want { + t.Fatalf("json.Marshal = %s, want %s", got, want) + } + }) + } +} + +// In v1, unmarshaling into a Go array permits JSON arrays with any length. +// In v2, unmarshaling into a Go array requires that the JSON array +// have the exact same number of elements as the Go array. +// +// Go arrays are often used because the exact length has significant meaning. +// Ignoring this detail seems like a mistake. Also, the v1 behavior leads to +// silent data loss when excess JSON array elements are discarded. +func TestArrays(t *testing.T) { + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal/TooFew", json.Version), func(t *testing.T) { + var got [2]int + err := json.Unmarshal([]byte(`[1]`), &got) + switch { + case got != [2]int{1, 0}: + t.Fatalf(`json.Unmarshal = %v, want [1 0]`, got) + case json.Version == "v1" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case json.Version == "v2" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + } + }) + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal/TooMany", json.Version), func(t *testing.T) { + var got [2]int + err := json.Unmarshal([]byte(`[1,2,3]`), &got) + switch { + case got != [2]int{1, 2}: + t.Fatalf(`json.Unmarshal = %v, want [1 2]`, got) + case json.Version == "v1" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case json.Version == "v2" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + } + }) + } +} + +// In v1, byte arrays are treated as arrays of unsigned integers. +// In v2, byte arrays are treated as binary values (similar to []byte). +// This is to make the behavior of [N]byte and []byte more consistent. +// +// Users of v2 can opt into the v1 behavior by setting +// the "format:array" option in the `json` struct field tag: +// +// struct { +// B [32]byte `json:",format:array"` +// } +func TestByteArrays(t *testing.T) { + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + in := [4]byte{1, 2, 3, 4} + got, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + want := map[string]string{ + "v1": `[1,2,3,4]`, + "v2": `"AQIDBA=="`, + }[json.Version] + if string(got) != want { + t.Fatalf("json.Marshal = %s, want %s", got, want) + } + }) + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + in := map[string]string{ + "v1": `[1,2,3,4]`, + "v2": `"AQIDBA=="`, + }[json.Version] + var got [4]byte + err := json.Unmarshal([]byte(in), &got) + switch { + case err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case got != [4]byte{1, 2, 3, 4}: + t.Fatalf("json.Unmarshal = %v, want [1 2 3 4]", got) + } + }) + } +} + +// CallCheck implements json.{Marshaler,Unmarshaler} on a pointer receiver. +type CallCheck string + +// MarshalJSON always returns a JSON string with the literal "CALLED". +func (*CallCheck) MarshalJSON() ([]byte, error) { + return []byte(`"CALLED"`), nil +} + +// UnmarshalJSON always stores a string with the literal "CALLED". +func (v *CallCheck) UnmarshalJSON([]byte) error { + *v = `CALLED` + return nil +} + +// In v1, the implementation is inconsistent about whether it calls +// MarshalJSON and UnmarshalJSON methods declared on pointer receivers +// when it has an unaddressable value (per reflect.Value.CanAddr) on hand. +// When marshaling, it never boxes the value on the heap to make it addressable, +// while it sometimes boxes values (e.g., for map entries) when unmarshaling. +// +// In v2, the implementation always calls MarshalJSON and UnmarshalJSON methods +// by boxing the value on the heap if necessary. +// +// The v1 behavior is surprising at best and buggy at worst. +// Unfortunately, it cannot be changed without breaking existing usages. +// +// Related issues: +// +// https://go.dev/issue/27722 +// https://go.dev/issue/33993 +// https://go.dev/issue/42508 +func TestPointerReceiver(t *testing.T) { + type Values struct { + S []CallCheck + A [1]CallCheck + M map[string]CallCheck + V CallCheck + I any + } + + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + var cc CallCheck + in := Values{ + S: []CallCheck{cc}, + A: [1]CallCheck{cc}, // MarshalJSON not called on v1 + M: map[string]CallCheck{"": cc}, // MarshalJSON not called on v1 + V: cc, // MarshalJSON not called on v1 + I: cc, // MarshalJSON not called on v1 + } + want := map[string]string{ + "v1": `{"S":["CALLED"],"A":[""],"M":{"":""},"V":"","I":""}`, + "v2": `{"S":["CALLED"],"A":["CALLED"],"M":{"":"CALLED"},"V":"CALLED","I":"CALLED"}`, + }[json.Version] + got, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + if string(got) != want { + t.Fatalf("json.Marshal = %s, want %s", got, want) + } + }) + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + in := `{"S":[""],"A":[""],"M":{"":""},"V":"","I":""}` + called := CallCheck("CALLED") // resulting state if UnmarshalJSON is called + want := map[string]Values{ + "v1": { + S: []CallCheck{called}, + A: [1]CallCheck{called}, + M: map[string]CallCheck{"": called}, + V: called, + I: "", // UnmarshalJSON not called on v1; replaced with Go string + }, + "v2": { + S: []CallCheck{called}, + A: [1]CallCheck{called}, + M: map[string]CallCheck{"": called}, + V: called, + I: called, + }, + }[json.Version] + got := Values{ + A: [1]CallCheck{CallCheck("")}, + S: []CallCheck{CallCheck("")}, + M: map[string]CallCheck{"": CallCheck("")}, + V: CallCheck(""), + I: CallCheck(""), + } + if err := json.Unmarshal([]byte(in), &got); err != nil { + t.Fatalf("json.Unmarshal error: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("json.Unmarshal = %v, want %v", got, want) + } + }) + } +} + +// In v1, maps are marshaled in a deterministic order. +// In v2, maps are marshaled in a non-deterministic order. +// +// The reason for the change is that v2 prioritizes performance and +// the guarantee that marshaling operates primarily in a streaming manner. +// +// The v2 API provides jsontext.Value.Canonicalize if stability is needed: +// +// (*jsontext.Value)(&b).Canonicalize() +// +// Related issue: +// +// https://go.dev/issue/7872 +// https://go.dev/issue/33714 +func TestMapDeterminism(t *testing.T) { + const iterations = 10 + in := map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9} + + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + outs := make(map[string]bool) + for range iterations { + b, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + outs[string(b)] = true + } + switch { + case json.Version == "v1" && len(outs) != 1: + t.Fatalf("json.Marshal encoded to %d unique forms, expected 1", len(outs)) + case json.Version == "v2" && len(outs) == 1: + t.Logf("json.Marshal encoded to 1 unique form by chance; are you feeling lucky?") + } + }) + } +} + +// In v1, JSON string encoding escapes special characters related to HTML. +// In v2, JSON string encoding uses a normalized representation (per RFC 8785). +// +// Users of v2 can opt into the v1 behavior by setting EscapeForHTML and EscapeForJS. +// +// Escaping HTML-specific characters in a JSON library is a layering violation. +// It presumes that JSON is always used with HTML and ignores other +// similar classes of injection attacks (e.g., SQL injection). +// Users of JSON with HTML should either manually ensure that embedded JSON is +// properly escaped or be relying on a module like "github.com/google/safehtml" +// to handle safe interoperability of JSON and HTML. +func TestEscapeHTML(t *testing.T) { + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + const in = `` + got, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal error: %v", err) + } + want := map[string]string{ + "v1": `"\u003cscript\u003e console.log(\"Hello, world!\"); \u003c/script\u003e"`, + "v2": `""`, + }[json.Version] + if string(got) != want { + t.Fatalf("json.Marshal = %s, want %s", got, want) + } + }) + } +} + +// In v1, JSON serialization silently ignored invalid UTF-8 by +// replacing such bytes with the Unicode replacement character. +// In v2, JSON serialization reports an error if invalid UTF-8 is encountered. +// +// Users of v2 can opt into the v1 behavior by setting [AllowInvalidUTF8]. +// +// Silently allowing invalid UTF-8 causes data corruption that can be difficult +// to detect until it is too late. Once it has been discovered, strict UTF-8 +// behavior sometimes cannot be enabled since other logic may be depending +// on the current behavior due to Hyrum's Law. +// +// Tim Bray, the author of RFC 8259 recommends that implementations should +// go beyond RFC 8259 and instead target compliance with RFC 7493, +// which makes strict decisions about behavior left undefined in RFC 8259. +// In particular, RFC 7493 rejects the presence of invalid UTF-8. +// See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90 +func TestInvalidUTF8(t *testing.T) { + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + got, err := json.Marshal("\xff") + switch { + case json.Version == "v1" && err != nil: + t.Fatalf("json.Marshal error: %v", err) + case json.Version == "v1" && string(got) != `"\ufffd"`: + t.Fatalf(`json.Marshal = %s, want "\ufffd"`, got) + case json.Version == "v2" && err == nil: + t.Fatal("json.Marshal error is nil, want non-nil") + } + }) + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + const in = "\"\xff\"" + var got string + err := json.Unmarshal([]byte(in), &got) + switch { + case json.Version == "v1" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case json.Version == "v1" && got != "\ufffd": + t.Fatalf(`json.Unmarshal = %q, want "\ufffd"`, got) + case json.Version == "v2" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + } + }) + } +} + +// In v1, duplicate JSON object names are permitted by default where +// they follow the inconsistent and difficult-to-explain merge semantics of v1. +// In v2, duplicate JSON object names are rejected by default where +// they follow the merge semantics of v2 based on RFC 7396. +// +// Users of v2 can opt into the v1 behavior by setting [AllowDuplicateNames]. +// +// Per RFC 8259, the handling of duplicate names is left as undefined behavior. +// Rejecting such inputs is within the realm of valid behavior. +// Tim Bray, the author of RFC 8259 recommends that implementations should +// go beyond RFC 8259 and instead target compliance with RFC 7493, +// which makes strict decisions about behavior left undefined in RFC 8259. +// In particular, RFC 7493 rejects the presence of duplicate object names. +// See https://www.tbray.org/ongoing/When/201x/2017/12/14/RFC-8259-STD-90 +// +// The lack of duplicate name rejection has correctness implications where +// roundtrip unmarshal/marshal do not result in semantically equivalent JSON. +// This is surprising behavior for users when they accidentally +// send JSON objects with duplicate names. +// +// The lack of duplicate name rejection may have security implications since it +// becomes difficult for a security tool to validate the semantic meaning of a +// JSON object since meaning is undefined in the presence of duplicate names. +// See https://labs.bishopfox.com/tech-blog/an-exploration-of-json-interoperability-vulnerabilities +// +// Related issue: +// +// https://go.dev/issue/48298 +func TestDuplicateNames(t *testing.T) { + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + const in = `{"Name":1,"Name":2}` + var got struct{ Name int } + err := json.Unmarshal([]byte(in), &got) + switch { + case json.Version == "v1" && err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case json.Version == "v1" && got != struct{ Name int }{2}: + t.Fatalf(`json.Unmarshal = %v, want {2}`, got) + case json.Version == "v2" && err == nil: + t.Fatal("json.Unmarshal error is nil, want non-nil") + } + }) + } +} + +// In v1, unmarshaling a JSON null into a non-empty value was inconsistent +// in that sometimes it would be ignored and other times clear the value. +// In v2, unmarshaling a JSON null into a non-empty value would consistently +// always clear the value regardless of the value's type. +// +// The purpose of this change is to have consistent behavior with how JSON nulls +// are handled during Unmarshal. This semantic detail has no effect +// when Unmarshaling into a empty value. +// +// Related issues: +// +// https://go.dev/issue/22177 +// https://go.dev/issue/33835 +func TestMergeNull(t *testing.T) { + type Types struct { + Bool bool + String string + Bytes []byte + Int int + Map map[string]string + Struct struct{ Field string } + Slice []string + Array [1]string + Pointer *string + Interface any + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + // Start with a non-empty value where all fields are populated. + in := Types{ + Bool: true, + String: "old", + Bytes: []byte("old"), + Int: 1234, + Map: map[string]string{"old": "old"}, + Struct: struct{ Field string }{"old"}, + Slice: []string{"old"}, + Array: [1]string{"old"}, + Pointer: new(string), + Interface: "old", + } + + // Unmarshal a JSON null into every field. + if err := json.Unmarshal([]byte(`{ + "Bool": null, + "String": null, + "Bytes": null, + "Int": null, + "Map": null, + "Struct": null, + "Slice": null, + "Array": null, + "Pointer": null, + "Interface": null + }`), &in); err != nil { + t.Fatalf("json.Unmarshal error: %v", err) + } + + want := map[string]Types{ + "v1": { + Bool: true, + String: "old", + Int: 1234, + Struct: struct{ Field string }{"old"}, + Array: [1]string{"old"}, + }, + "v2": {}, // all fields are zeroed + }[json.Version] + if !reflect.DeepEqual(in, want) { + t.Fatalf("json.Unmarshal = %+v, want %+v", in, want) + } + }) + } +} + +// In v1, merge semantics are inconsistent and difficult to explain. +// In v2, merge semantics replaces the destination value for anything +// other than a JSON object, and recursively merges JSON objects. +// +// Merge semantics in v1 are inconsistent and difficult to explain +// largely because the behavior came about organically, rather than +// having a principled approach to how the semantics should operate. +// In v2, merging follows behavior based on RFC 7396. +// +// Related issues: +// +// https://go.dev/issue/21092 +// https://go.dev/issue/26946 +// https://go.dev/issue/27172 +// https://go.dev/issue/30701 +// https://go.dev/issue/31924 +// https://go.dev/issue/43664 +func TestMergeComposite(t *testing.T) { + type Tuple struct{ Old, New bool } + type Composites struct { + Slice []Tuple + Array [1]Tuple + Map map[string]Tuple + MapPointer map[string]*Tuple + Struct struct{ Tuple Tuple } + StructPointer *struct{ Tuple Tuple } + Interface any + InterfacePointer any + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + // Start with a non-empty value where all fields are populated. + in := Composites{ + Slice: []Tuple{{Old: true}, {Old: true}}[:1], + Array: [1]Tuple{{Old: true}}, + Map: map[string]Tuple{"Tuple": {Old: true}}, + MapPointer: map[string]*Tuple{"Tuple": {Old: true}}, + Struct: struct{ Tuple Tuple }{Tuple{Old: true}}, + StructPointer: &struct{ Tuple Tuple }{Tuple{Old: true}}, + Interface: Tuple{Old: true}, + InterfacePointer: &Tuple{Old: true}, + } + + // Unmarshal into every pre-populated field. + if err := json.Unmarshal([]byte(`{ + "Slice": [{"New":true}, {"New":true}], + "Array": [{"New":true}], + "Map": {"Tuple": {"New":true}}, + "MapPointer": {"Tuple": {"New":true}}, + "Struct": {"Tuple": {"New":true}}, + "StructPointer": {"Tuple": {"New":true}}, + "Interface": {"New":true}, + "InterfacePointer": {"New":true} + }`), &in); err != nil { + t.Fatalf("json.Unmarshal error: %v", err) + } + + merged := Tuple{Old: true, New: true} + replaced := Tuple{Old: false, New: true} + want := map[string]Composites{ + "v1": { + Slice: []Tuple{merged, merged}, // merged + Array: [1]Tuple{merged}, // merged + Map: map[string]Tuple{"Tuple": replaced}, // replaced + MapPointer: map[string]*Tuple{"Tuple": &replaced}, // replaced + Struct: struct{ Tuple Tuple }{merged}, // merged (same as v2) + StructPointer: &struct{ Tuple Tuple }{merged}, // merged (same as v2) + Interface: map[string]any{"New": true}, // replaced + InterfacePointer: &merged, // merged (same as v2) + }, + "v2": { + Slice: []Tuple{replaced, replaced}, // replaced + Array: [1]Tuple{replaced}, // replaced + Map: map[string]Tuple{"Tuple": merged}, // merged + MapPointer: map[string]*Tuple{"Tuple": &merged}, // merged + Struct: struct{ Tuple Tuple }{merged}, // merged (same as v1) + StructPointer: &struct{ Tuple Tuple }{merged}, // merged (same as v1) + Interface: merged, // merged + InterfacePointer: &merged, // merged (same as v1) + }, + }[json.Version] + if !reflect.DeepEqual(in, want) { + t.Fatalf("json.Unmarshal = %+v, want %+v", in, want) + } + }) + } +} + +// In v1, there was no special support for time.Duration, +// which resulted in that type simply being treated as a signed integer. +// In v2, there is now first-class support for time.Duration, where the type is +// formatted and parsed using time.Duration.String and time.ParseDuration. +// +// Users of v2 can opt into the v1 behavior by setting +// the "format:nano" option in the `json` struct field tag: +// +// struct { +// Duration time.Duration `json:",format:nano"` +// } +// +// Related issue: +// +// https://go.dev/issue/10275 +func TestTimeDurations(t *testing.T) { + for _, json := range jsonPackages { + t.Run(path.Join("Marshal", json.Version), func(t *testing.T) { + got, err := json.Marshal(time.Minute) + switch { + case err != nil: + t.Fatalf("json.Marshal error: %v", err) + case json.Version == "v1" && string(got) != "60000000000": + t.Fatalf("json.Marshal = %s, want 60000000000", got) + case json.Version == "v2" && string(got) != `"1m0s"`: + t.Fatalf(`json.Marshal = %s, want "1m0s"`, got) + } + }) + } + + for _, json := range jsonPackages { + t.Run(path.Join("Unmarshal", json.Version), func(t *testing.T) { + in := map[string]string{ + "v1": "60000000000", + "v2": `"1m0s"`, + }[json.Version] + var got time.Duration + err := json.Unmarshal([]byte(in), &got) + switch { + case err != nil: + t.Fatalf("json.Unmarshal error: %v", err) + case got != time.Minute: + t.Fatalf("json.Unmarshal = %v, want 1m0s", got) + } + }) + } +} + +// In v1, non-empty structs without any JSON serializable fields are permitted. +// In v2, non-empty structs without any JSON serializable fields are rejected. +// +// The purpose of this change is to avoid a common pitfall for new users +// where they expect JSON serialization to handle unexported fields. +// However, this does not work since Go reflection does not +// provide the package the ability to mutate such fields. +// Rejecting unserializable structs in v2 is intended to be a clear signal +// that the type is not supposed to be serialized. +func TestEmptyStructs(t *testing.T) { + never := func(string) bool { return false } + onlyV2 := func(v string) bool { return v == "v2" } + values := []struct { + in any + wantError func(string) bool + }{ + // It is okay to marshal a truly empty struct in v1 and v2. + {in: addr(struct{}{}), wantError: never}, + // In v1, a non-empty struct without exported fields + // is equivalent to an empty struct, but is rejected in v2. + // Note that errors.errorString type has only unexported fields. + {in: errors.New("error"), wantError: onlyV2}, + // A mix of exported and unexported fields is permitted. + {in: addr(struct{ Exported, unexported int }{}), wantError: never}, + } + + for _, json := range jsonPackages { + t.Run("Marshal", func(t *testing.T) { + for _, value := range values { + wantError := value.wantError(json.Version) + _, err := json.Marshal(value.in) + switch { + case (err == nil) && wantError: + t.Fatalf("json.Marshal error is nil, want non-nil") + case (err != nil) && !wantError: + t.Fatalf("json.Marshal error: %v", err) + } + } + }) + } + + for _, json := range jsonPackages { + t.Run("Unmarshal", func(t *testing.T) { + for _, value := range values { + wantError := value.wantError(json.Version) + out := reflect.New(reflect.TypeOf(value.in).Elem()).Interface() + err := json.Unmarshal([]byte("{}"), out) + switch { + case (err == nil) && wantError: + t.Fatalf("json.Unmarshal error is nil, want non-nil") + case (err != nil) && !wantError: + t.Fatalf("json.Unmarshal error: %v", err) + } + } + }) + } +} diff --git a/src/encoding/json/v2_encode.go b/src/encoding/json/v2_encode.go new file mode 100644 index 0000000000..c8f35d4281 --- /dev/null +++ b/src/encoding/json/v2_encode.go @@ -0,0 +1,240 @@ +// 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 + +// 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: +// https://golang.org/doc/articles/json_and_go.html +package json + +import ( + "reflect" + "strconv" + + jsonv2 "encoding/json/v2" +) + +// Marshal returns the JSON encoding of v. +// +// Marshal traverses the value v recursively. +// If an encountered value implements [Marshaler] +// and is not a nil pointer, Marshal calls [Marshaler.MarshalJSON] +// to produce JSON. If no [Marshaler.MarshalJSON] method is present but the +// value implements [encoding.TextMarshaler] instead, Marshal calls +// [encoding.TextMarshaler.MarshalText] and encodes the result as a JSON string. +// The nil pointer exception is not strictly necessary +// but mimics a similar, necessary exception in the behavior of +// [Unmarshaler.UnmarshalJSON]. +// +// 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