mirror of
https://github.com/golang/go.git
synced 2026-06-27 03:11:23 +00:00
encoding/json/v2: support format tag option behind goexperiment
WARNING: This commit contains breaking changes. The `format` tag option is no longer supported by default. Similarly, the ability to use a single-quoted string for field names or for the `format` tag value is no longer supported by default. To enable both features, specify GOEXPERIMENT=jsonformat. Fixes #79071 Change-Id: I2cfdbd89dc84639e7423bee0e867d965d92a267b Reviewed-on: https://go-review.googlesource.com/c/go/+/773980 Reviewed-by: Michael Pratt <mpratt@google.com> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
d5d2bde748
commit
0b54a75319
16 changed files with 370 additions and 205 deletions
9
src/encoding/json/internal/exp_jsonformat_off.go
Normal file
9
src/encoding/json/internal/exp_jsonformat_off.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2 && !goexperiment.jsonformat
|
||||
|
||||
package internal
|
||||
|
||||
const ExpJSONFormat = false
|
||||
9
src/encoding/json/internal/exp_jsonformat_on.go
Normal file
9
src/encoding/json/internal/exp_jsonformat_on.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2 && goexperiment.jsonformat
|
||||
|
||||
package internal
|
||||
|
||||
const ExpJSONFormat = true
|
||||
|
|
@ -72,51 +72,30 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
|
|||
// - Otherwise, the value is encoded according to the value's type
|
||||
// as described in detail below.
|
||||
//
|
||||
// Most Go types have a default JSON representation.
|
||||
// Certain types support specialized formatting according to
|
||||
// a format flag optionally specified in the Go struct tag
|
||||
// for the struct field that contains the current value
|
||||
// (see the “JSON Representation of Go structs” section for more details).
|
||||
//
|
||||
// The representation of each type is as follows:
|
||||
// Most Go types have a default JSON representation as follows:
|
||||
//
|
||||
// - A Go boolean is encoded as a JSON boolean (e.g., true or false).
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go string is encoded as a JSON string.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go []byte or [N]byte is encoded as a JSON string containing
|
||||
// the binary value encoded using RFC 4648.
|
||||
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
|
||||
// If the format is "base64url", then this uses RFC 4648, section 5.
|
||||
// If the format is "base32", then this uses RFC 4648, section 6.
|
||||
// If the format is "base32hex", then this uses RFC 4648, section 7.
|
||||
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
|
||||
// If the format is "array", then the bytes value is encoded as a JSON array
|
||||
// where each byte is recursively JSON-encoded as each JSON array element.
|
||||
// a binary value using Base 64 Encoding per RFC 4648, section 4.
|
||||
//
|
||||
// - A Go integer is encoded as a JSON number without fractions or exponents.
|
||||
// If [StringifyNumbers] is specified or encoding a JSON object name,
|
||||
// then the JSON number is encoded within a JSON string.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go float is encoded as a JSON number.
|
||||
// If [StringifyNumbers] is specified or encoding a JSON object name,
|
||||
// then the JSON number is encoded within a JSON string.
|
||||
// If the format is "nonfinite", then NaN, +Inf, and -Inf are encoded as
|
||||
// the JSON strings "NaN", "Infinity", and "-Infinity", respectively.
|
||||
// Otherwise, the presence of non-finite numbers results in a [SemanticError].
|
||||
//
|
||||
// - A Go map is encoded as a JSON object, where each Go map key and value
|
||||
// is recursively encoded as a name and value pair in the JSON object.
|
||||
// The Go map key must encode as a JSON string, otherwise this results
|
||||
// in a [SemanticError]. The Go map is traversed in a non-deterministic order.
|
||||
// For deterministic encoding, consider using the [Deterministic] option.
|
||||
// If the format is "emitnull", then a nil map is encoded as a JSON null.
|
||||
// If the format is "emitempty", then a nil map is encoded as an empty JSON object,
|
||||
// regardless of whether [FormatNilMapAsNull] is specified.
|
||||
// Otherwise by default, a nil map is encoded as an empty JSON object.
|
||||
// By default, a nil map is encoded as an empty JSON object,
|
||||
// unless the [FormatNilMapAsNull] option is specified.
|
||||
//
|
||||
// - A Go struct is encoded as a JSON object.
|
||||
// See the “JSON Representation of Go structs” section
|
||||
|
|
@ -124,46 +103,26 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
|
|||
//
|
||||
// - A Go slice is encoded as a JSON array, where each Go slice element
|
||||
// is recursively JSON-encoded as the elements of the JSON array.
|
||||
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
|
||||
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
|
||||
// regardless of whether [FormatNilSliceAsNull] is specified.
|
||||
// Otherwise by default, a nil slice is encoded as an empty JSON array.
|
||||
// By default, a nil slice is encoded as an empty JSON array,
|
||||
// unless the [FormatNilSliceAsNull] option is specified.
|
||||
//
|
||||
// - A Go array is encoded as a JSON array, where each Go array element
|
||||
// is recursively JSON-encoded as the elements of the JSON array.
|
||||
// The JSON array length is always identical to the Go array length.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go pointer is encoded as a JSON null if nil, otherwise it is
|
||||
// the recursively JSON-encoded representation of the underlying value.
|
||||
// Format flags are forwarded to the encoding of the underlying value.
|
||||
//
|
||||
// - A Go interface is encoded as a JSON null if nil, otherwise it is
|
||||
// the recursively JSON-encoded representation of the underlying value.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go [time.Time] is encoded as a JSON string containing the timestamp
|
||||
// formatted in RFC 3339 with nanosecond precision.
|
||||
// If the format matches one of the format constants declared
|
||||
// in the time package (e.g., RFC1123), then that format is used.
|
||||
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
|
||||
// then the timestamp is encoded as a possibly fractional JSON number
|
||||
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
|
||||
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
|
||||
// To avoid a fractional component, round the timestamp to the relevant unit.
|
||||
// Otherwise, the format is used as-is with [time.Time.Format] if non-empty.
|
||||
//
|
||||
// - A Go [time.Duration] currently has no default representation and
|
||||
// requires an explicit format to be specified.
|
||||
// If the format is "sec", "milli", "micro", or "nano",
|
||||
// then the duration is encoded as a possibly fractional JSON number
|
||||
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
|
||||
// To avoid a fractional component, round the duration to the relevant unit.
|
||||
// If the format is "units", it is encoded as a JSON string formatted using
|
||||
// [time.Duration.String] (e.g., "1h30m" for 1 hour 30 minutes).
|
||||
// If the format is "iso8601", it is encoded as a JSON string using the
|
||||
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
|
||||
// using only accurate units of hours, minutes, and seconds.
|
||||
// results in a [SemanticError], unless the [encoding/json.FormatDurationAsNano]
|
||||
// option is specified, in which case it is encoded as a JSON number
|
||||
// without fractions or exponents, representing the duration in nanoseconds.
|
||||
//
|
||||
// - All other Go types (e.g., complex numbers, channels, and functions)
|
||||
// have no default representation and result in a [SemanticError].
|
||||
|
|
@ -287,10 +246,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
|
|||
// as described in detail below.
|
||||
//
|
||||
// Most Go types have a default JSON representation.
|
||||
// Certain types support specialized formatting according to
|
||||
// a format flag optionally specified in the Go struct tag
|
||||
// for the struct field that contains the current value
|
||||
// (see the “JSON Representation of Go structs” section for more details).
|
||||
// A JSON null may be decoded into every supported Go value where
|
||||
// it is equivalent to storing the zero value of the Go value.
|
||||
// If the input JSON kind is not handled by the current Go value type,
|
||||
|
|
@ -300,20 +255,11 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
|
|||
// The representation of each type is as follows:
|
||||
//
|
||||
// - A Go boolean is decoded from a JSON boolean (e.g., true or false).
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go string is decoded from a JSON string.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go []byte or [N]byte is decoded from a JSON string
|
||||
// containing the binary value encoded using RFC 4648.
|
||||
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
|
||||
// If the format is "base64url", then this uses RFC 4648, section 5.
|
||||
// If the format is "base32", then this uses RFC 4648, section 6.
|
||||
// If the format is "base32hex", then this uses RFC 4648, section 7.
|
||||
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
|
||||
// If the format is "array", then the Go slice or array is decoded from a
|
||||
// JSON array where each JSON element is recursively decoded for each byte.
|
||||
// - A Go []byte or [N]byte is decoded from a JSON string containing
|
||||
// a binary value using Base 64 Encoding per RFC 4648, section 4.
|
||||
// When decoding into a non-nil []byte, the slice length is reset to zero
|
||||
// and the decoded input is appended to it.
|
||||
// When decoding into a [N]byte, the input must decode to exactly N bytes,
|
||||
|
|
@ -325,15 +271,11 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
|
|||
// It fails with a [SemanticError] if the JSON number
|
||||
// has a fractional or exponent component.
|
||||
// It also fails if it overflows the representation of the Go integer type.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go float is decoded from a JSON number.
|
||||
// It must be decoded from a JSON string containing a JSON number
|
||||
// if [StringifyNumbers] is specified or decoding a JSON object name.
|
||||
// It fails if it overflows the representation of the Go float type.
|
||||
// If the format is "nonfinite", then the JSON strings
|
||||
// "NaN", "Infinity", and "-Infinity" are decoded as NaN, +Inf, and -Inf.
|
||||
// Otherwise, the presence of such strings results in a [SemanticError].
|
||||
//
|
||||
// - A Go map is decoded from a JSON object,
|
||||
// where each JSON object name and value pair is recursively decoded
|
||||
|
|
@ -341,7 +283,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
|
|||
// If the Go map is nil, then a new map is allocated to decode into.
|
||||
// If the decoded key matches an existing Go map entry, the entry value
|
||||
// is reused by decoding the JSON object value into it.
|
||||
// The formats "emitnull" and "emitempty" have no effect when decoding.
|
||||
//
|
||||
// - A Go struct is decoded from a JSON object.
|
||||
// See the “JSON Representation of Go structs” section
|
||||
|
|
@ -351,20 +292,17 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
|
|||
// is recursively decoded and appended to the Go slice.
|
||||
// Before appending into a Go slice, a new slice is allocated if it is nil,
|
||||
// otherwise the slice length is reset to zero.
|
||||
// The formats "emitnull" and "emitempty" have no effect when decoding.
|
||||
//
|
||||
// - A Go array is decoded from a JSON array, where each JSON array element
|
||||
// is recursively decoded as each corresponding Go array element.
|
||||
// Each Go array element is zeroed before decoding into it.
|
||||
// It fails with a [SemanticError] if the JSON array does not contain
|
||||
// the exact same number of elements as the Go array.
|
||||
// It does not support any custom format flags.
|
||||
//
|
||||
// - A Go pointer is decoded based on the JSON kind and underlying Go type.
|
||||
// If the input is a JSON null, then this stores a nil pointer.
|
||||
// Otherwise, it allocates a new underlying value if the pointer is nil,
|
||||
// and recursively JSON decodes into the underlying value.
|
||||
// Format flags are forwarded to the decoding of the underlying type.
|
||||
//
|
||||
// - A Go interface is decoded based on the JSON kind and underlying Go type.
|
||||
// If the input is a JSON null, then this stores a nil interface value.
|
||||
|
|
@ -376,28 +314,15 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
|
|||
// For example, unmarshaling into a nil io.Reader fails since
|
||||
// there is no concrete type to populate the interface value with.
|
||||
// Otherwise an underlying value exists and it recursively decodes
|
||||
// the JSON input into it. It does not support any custom format flags.
|
||||
// the JSON input into it.
|
||||
//
|
||||
// - A Go [time.Time] is decoded from a JSON string containing the time
|
||||
// formatted in RFC 3339 with nanosecond precision.
|
||||
// If the format matches one of the format constants declared in
|
||||
// the time package (e.g., RFC1123), then that format is used for parsing.
|
||||
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
|
||||
// then the timestamp is decoded from an optionally fractional JSON number
|
||||
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
|
||||
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
|
||||
// Otherwise, the format is used as-is with [time.Time.Parse] if non-empty.
|
||||
//
|
||||
// - A Go [time.Duration] currently has no default representation and
|
||||
// requires an explicit format to be specified.
|
||||
// If the format is "sec", "milli", "micro", or "nano",
|
||||
// then the duration is decoded from an optionally fractional JSON number
|
||||
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
|
||||
// If the format is "units", it is decoded from a JSON string parsed using
|
||||
// [time.ParseDuration] (e.g., "1h30m" for 1 hour 30 minutes).
|
||||
// If the format is "iso8601", it is decoded from a JSON string using the
|
||||
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
|
||||
// accepting only accurate units of hours, minutes, or seconds.
|
||||
// results in a [SemanticError], unless the [encoding/json.FormatDurationAsNano]
|
||||
// option is specified, in which case it is decoded as a JSON number
|
||||
// without fractions or exponents, representing the duration in nanoseconds.
|
||||
//
|
||||
// - All other Go types (e.g., complex numbers, channels, and functions)
|
||||
// have no default representation and result in a [SemanticError].
|
||||
|
|
|
|||
|
|
@ -728,6 +728,7 @@ func TestMarshal(t *testing.T) {
|
|||
|
||||
canonicalize bool // canonicalize the output before comparing?
|
||||
useWriter bool // call MarshalWrite instead of Marshal
|
||||
skip bool
|
||||
}{{
|
||||
name: jsontest.Name("Nil"),
|
||||
in: nil,
|
||||
|
|
@ -1120,6 +1121,7 @@ func TestMarshal(t *testing.T) {
|
|||
name: jsontest.Name("Structs/WeirdNames"),
|
||||
in: structWeirdNames{Empty: "empty", Comma: "comma", Quote: "quote"},
|
||||
want: `{"":"empty",",":"comma","\"":"quote"}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/EscapedNames"),
|
||||
opts: []Options{jsontext.EscapeForHTML(true), jsontext.EscapeForJS(true)},
|
||||
|
|
@ -1133,6 +1135,7 @@ func TestMarshal(t *testing.T) {
|
|||
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"}}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/NoCase"),
|
||||
in: structNoCase{AaA: "AaA", AAa: "AAa", Aaa: "Aaa", AAA: "AAA", AA_A: "AA_A"},
|
||||
|
|
@ -2123,7 +2126,9 @@ func TestMarshal(t *testing.T) {
|
|||
3,
|
||||
4
|
||||
]
|
||||
}`}, {
|
||||
}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/ArrayBytes"),
|
||||
opts: []Options{jsontext.Multiline(true)},
|
||||
in: structFormatArrayBytes{
|
||||
|
|
@ -2148,7 +2153,9 @@ func TestMarshal(t *testing.T) {
|
|||
4
|
||||
],
|
||||
"Default": "AQIDBA=="
|
||||
}`}, {
|
||||
}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/ArrayBytes/Legacy"),
|
||||
opts: []Options{jsontext.Multiline(true), jsonflags.FormatByteArrayAsArray | jsonflags.FormatBytesWithLegacySemantics | 1},
|
||||
in: structFormatArrayBytes{
|
||||
|
|
@ -2178,7 +2185,9 @@ func TestMarshal(t *testing.T) {
|
|||
3,
|
||||
4
|
||||
]
|
||||
}`}, {
|
||||
}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Array"),
|
||||
opts: []Options{
|
||||
WithMarshalers(MarshalFunc(func(in byte) ([]byte, error) {
|
||||
|
|
@ -2195,6 +2204,7 @@ func TestMarshal(t *testing.T) {
|
|||
Array: []byte{1, 6, 2, 5, 3, 4},
|
||||
},
|
||||
want: `{"Array":[false,true,false,true,false,true]}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Floats"),
|
||||
opts: []Options{jsontext.Multiline(true)},
|
||||
|
|
@ -2222,6 +2232,7 @@ func TestMarshal(t *testing.T) {
|
|||
"PointerNonFinite": "Infinity"
|
||||
}
|
||||
]`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Maps"),
|
||||
opts: []Options{jsontext.Multiline(true)},
|
||||
|
|
@ -2276,6 +2287,7 @@ func TestMarshal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
]`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Maps/FormatNilMapAsNull"),
|
||||
opts: []Options{
|
||||
|
|
@ -2333,6 +2345,7 @@ func TestMarshal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
]`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Slices"),
|
||||
opts: []Options{jsontext.Multiline(true)},
|
||||
|
|
@ -2387,61 +2400,73 @@ func TestMarshal(t *testing.T) {
|
|||
]
|
||||
}
|
||||
]`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Bool"),
|
||||
in: structFormatInvalid{Bool: true},
|
||||
want: `{"Bool"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, boolType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/String"),
|
||||
in: structFormatInvalid{String: "string"},
|
||||
want: `{"String"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"String":`, "/String").withType(0, stringType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Bytes"),
|
||||
in: structFormatInvalid{Bytes: []byte("bytes")},
|
||||
want: `{"Bytes"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Bytes":`, "/Bytes").withType(0, bytesType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Int"),
|
||||
in: structFormatInvalid{Int: 1},
|
||||
want: `{"Int"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Int":`, "/Int").withType(0, T[int64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Uint"),
|
||||
in: structFormatInvalid{Uint: 1},
|
||||
want: `{"Uint"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Uint":`, "/Uint").withType(0, T[uint64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Float"),
|
||||
in: structFormatInvalid{Float: 1},
|
||||
want: `{"Float"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Float":`, "/Float").withType(0, T[float64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Slice"),
|
||||
in: structFormatInvalid{Slice: []string{}},
|
||||
want: `{"Slice"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Slice":`, "/Slice").withType(0, T[[]string]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Interface"),
|
||||
in: structFormatInvalid{Interface: "anything"},
|
||||
want: `{"Interface"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"Interface":`, "/Interface").withType(0, T[any]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Inline/Zero"),
|
||||
in: structInlined{},
|
||||
|
|
@ -4306,6 +4331,7 @@ func TestMarshal(t *testing.T) {
|
|||
D2 time.Duration `json:",format:nano"`
|
||||
}{0, 0},
|
||||
want: `{"D1":"0s","D2":0}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Positive"),
|
||||
in: struct {
|
||||
|
|
@ -4316,6 +4342,7 @@ func TestMarshal(t *testing.T) {
|
|||
123456789123456789,
|
||||
},
|
||||
want: `{"D1":"34293h33m9.123456789s","D2":123456789123456789}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Negative"),
|
||||
in: struct {
|
||||
|
|
@ -4326,6 +4353,7 @@ func TestMarshal(t *testing.T) {
|
|||
-123456789123456789,
|
||||
},
|
||||
want: `{"D1":"-34293h33m9.123456789s","D2":-123456789123456789}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Nanos/String"),
|
||||
in: struct {
|
||||
|
|
@ -4338,6 +4366,7 @@ func TestMarshal(t *testing.T) {
|
|||
math.MaxInt64,
|
||||
},
|
||||
want: `{"D1":"-9223372036854775808","D2":"0","D3":"9223372036854775807"}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Format/Invalid"),
|
||||
in: struct {
|
||||
|
|
@ -4345,6 +4374,7 @@ func TestMarshal(t *testing.T) {
|
|||
}{},
|
||||
want: `{"D"`,
|
||||
wantErr: EM(errInvalidFormatFlag).withPos(`{"D":`, "/D").withType(0, T[time.Duration]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
/* TODO(https://go.dev/issue/71631): Re-enable this test case.
|
||||
name: jsontest.Name("Duration/IgnoreInvalidFormat"),
|
||||
|
|
@ -4380,6 +4410,7 @@ func TestMarshal(t *testing.T) {
|
|||
"D10": "45296078090012",
|
||||
"D11": "PT12H34M56.078090012S"
|
||||
}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
/* TODO(https://go.dev/issue/71631): Re-enable this test case.
|
||||
name: jsontest.Name("Duration/Format/Legacy"),
|
||||
|
|
@ -4417,6 +4448,7 @@ func TestMarshal(t *testing.T) {
|
|||
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"}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format"),
|
||||
opts: []Options{jsontext.Multiline(true)},
|
||||
|
|
@ -4482,6 +4514,7 @@ func TestMarshal(t *testing.T) {
|
|||
"T28": -23225777754999999994,
|
||||
"T29": "-23225777754999999994"
|
||||
}`,
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format/Invalid"),
|
||||
in: struct {
|
||||
|
|
@ -4489,6 +4522,7 @@ func TestMarshal(t *testing.T) {
|
|||
}{},
|
||||
want: `{"T"`,
|
||||
wantErr: EM(errors.New(`invalid format flag "UndefinedConstant"`)).withPos(`{"T":`, "/T").withType(0, timeTimeType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format/YearOverflow"),
|
||||
in: struct {
|
||||
|
|
@ -4538,6 +4572,9 @@ func TestMarshal(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
if tt.skip {
|
||||
continue
|
||||
}
|
||||
t.Run(tt.name.Name, func(t *testing.T) {
|
||||
var got []byte
|
||||
var gotErr error
|
||||
|
|
@ -4569,6 +4606,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
inVal any
|
||||
want any
|
||||
wantErr error
|
||||
skip bool
|
||||
}{{
|
||||
name: jsontest.Name("Nil"),
|
||||
inBuf: `null`,
|
||||
|
|
@ -6181,6 +6219,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
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},
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/ArrayBytes"),
|
||||
inBuf: `{
|
||||
|
|
@ -6202,6 +6241,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
Array: [4]byte{1, 2, 3, 4},
|
||||
Default: [4]byte{1, 2, 3, 4},
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/ArrayBytes/Legacy"),
|
||||
opts: []Options{jsonflags.FormatBytesWithLegacySemantics | 1},
|
||||
|
|
@ -6224,6 +6264,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
Array: [4]byte{1, 2, 3, 4},
|
||||
Default: [4]byte{1, 2, 3, 4},
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Array"),
|
||||
opts: []Options{
|
||||
|
|
@ -6245,11 +6286,13 @@ func TestUnmarshal(t *testing.T) {
|
|||
}{
|
||||
Array: []byte{0, 1, 0, 1, 0, 1},
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/AllPadding"),
|
||||
inBuf: `{"Base16": "===="}`,
|
||||
|
|
@ -6258,6 +6301,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := hex.Decode(make([]byte, 2), []byte("====="))
|
||||
return err
|
||||
}()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/EvenPadding"),
|
||||
inBuf: `{"Base16": "0123456789abcdef="}`,
|
||||
|
|
@ -6266,6 +6310,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := hex.Decode(make([]byte, 8), []byte("0123456789abcdef="))
|
||||
return err
|
||||
}()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/OddPadding"),
|
||||
inBuf: `{"Base16": "0123456789abcdef0="}`,
|
||||
|
|
@ -6274,6 +6319,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := hex.Decode(make([]byte, 9), []byte("0123456789abcdef0="))
|
||||
return err
|
||||
}()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/LineFeed"),
|
||||
inBuf: `{"Base16": "aa\naa"}`,
|
||||
|
|
@ -6282,6 +6328,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := hex.Decode(make([]byte, 9), []byte("aa\naa"))
|
||||
return err
|
||||
}()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/CarriageReturn"),
|
||||
inBuf: `{"Base16": "aa\raa"}`,
|
||||
|
|
@ -6290,6 +6337,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := hex.Decode(make([]byte, 9), []byte("aa\raa"))
|
||||
return err
|
||||
}()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base16/NonAlphabet/Space"),
|
||||
inBuf: `{"Base16": "aa aa"}`,
|
||||
|
|
@ -6298,6 +6346,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := hex.Decode(make([]byte, 9), []byte("aa aa"))
|
||||
return err
|
||||
}()).withPos(`{"Base16": `, "/Base16").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/Padding"),
|
||||
inBuf: `[
|
||||
|
|
@ -6315,6 +6364,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
{Base32: []byte("hell")},
|
||||
{Base32: []byte("hello")},
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/Invalid/NoPadding"),
|
||||
inBuf: `[
|
||||
|
|
@ -6329,6 +6379,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := base32.StdEncoding.Decode(make([]byte, 1), []byte("NA"))
|
||||
return err
|
||||
}()).withPos(`[`+"\n\t\t\t\t"+`{"Base32": `, "/0/Base32").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32/WrongAlphabet"),
|
||||
inBuf: `{"Base32": "0123456789ABCDEFGHIJKLMNOPQRSTUV"}`,
|
||||
|
|
@ -6337,6 +6388,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := base32.StdEncoding.Decode(make([]byte, 20), []byte("0123456789ABCDEFGHIJKLMNOPQRSTUV"))
|
||||
return err
|
||||
}()).withPos(`{"Base32": `, "/Base32").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base32Hex/WrongAlphabet"),
|
||||
inBuf: `{"Base32Hex": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"}`,
|
||||
|
|
@ -6345,21 +6397,25 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := base32.HexEncoding.Decode(make([]byte, 20), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"))
|
||||
return err
|
||||
}()).withPos(`{"Base32Hex": `, "/Base32Hex").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64/WrongAlphabet"),
|
||||
inBuf: `{"Base64": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"}`,
|
||||
|
|
@ -6368,6 +6424,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := base64.StdEncoding.Decode(make([]byte, 48), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"))
|
||||
return err
|
||||
}()).withPos(`{"Base64": `, "/Base64").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Invalid/Base64URL/WrongAlphabet"),
|
||||
inBuf: `{"Base64URL": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"}`,
|
||||
|
|
@ -6376,27 +6433,32 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := base64.URLEncoding.Decode(make([]byte, 48), []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"))
|
||||
return err
|
||||
}()).withPos(`{"Base64URL": `, "/Base64URL").withType('"', T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Bytes/Base64/NonAlphabet/Ignored"),
|
||||
opts: []Options{jsonflags.ParseBytesWithLooseRFC4648 | 1},
|
||||
inBuf: `{"Base64": "aa=\r\n="}`,
|
||||
inVal: new(structFormatBytes),
|
||||
want: &structFormatBytes{Base64: []byte{105}},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Floats"),
|
||||
inBuf: `[
|
||||
|
|
@ -6410,26 +6472,31 @@ func TestUnmarshal(t *testing.T) {
|
|||
{NonFinite: math.Inf(-1), PointerNonFinite: addr(math.Inf(-1))},
|
||||
{NonFinite: math.Inf(+1), PointerNonFinite: addr(math.Inf(+1))},
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Floats/NaN"),
|
||||
inBuf: `{"NonFinite": "NaN"}`,
|
||||
inVal: new(structFormatFloats),
|
||||
// Avoid checking want since reflect.DeepEqual fails for NaNs.
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Floats/Invalid/NaN"),
|
||||
inBuf: `{"NonFinite": "nan"}`,
|
||||
inVal: new(structFormatFloats),
|
||||
wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Floats/Invalid/PositiveInfinity"),
|
||||
inBuf: `{"NonFinite": "+Infinity"}`,
|
||||
inVal: new(structFormatFloats),
|
||||
wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Floats/Invalid/NegativeInfinitySpace"),
|
||||
inBuf: `{"NonFinite": "-Infinity "}`,
|
||||
inVal: new(structFormatFloats),
|
||||
wantErr: EU(nil).withPos(`{"NonFinite": `, "/NonFinite").withType('"', T[float64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Maps"),
|
||||
inBuf: `[
|
||||
|
|
@ -6451,6 +6518,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
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"}),
|
||||
}}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Slices"),
|
||||
inBuf: `[
|
||||
|
|
@ -6472,61 +6540,73 @@ func TestUnmarshal(t *testing.T) {
|
|||
EmitEmpty: []string{"v"}, PointerEmitEmpty: addr([]string{"v"}),
|
||||
EmitDefault: []string{"v"}, PointerEmitDefault: addr([]string{"v"}),
|
||||
}}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Bool"),
|
||||
inBuf: `{"Bool":true}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Bool":`, "/Bool").withType(0, T[bool]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/String"),
|
||||
inBuf: `{"String": "string"}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"String": `, "/String").withType(0, T[string]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Bytes"),
|
||||
inBuf: `{"Bytes": "bytes"}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Bytes": `, "/Bytes").withType(0, T[[]byte]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Int"),
|
||||
inBuf: `{"Int": 1}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Int": `, "/Int").withType(0, T[int64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Uint"),
|
||||
inBuf: `{"Uint": 1}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Uint": `, "/Uint").withType(0, T[uint64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Float"),
|
||||
inBuf: `{"Float" : 1}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Float" : `, "/Float").withType(0, T[float64]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Map"),
|
||||
inBuf: `{"Map":{}}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Map":`, "/Map").withType(0, T[map[string]string]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Struct"),
|
||||
inBuf: `{"Struct": {}}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Struct": `, "/Struct").withType(0, T[structAll]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Slice"),
|
||||
inBuf: `{"Slice": {}}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Slice": `, "/Slice").withType(0, T[[]string]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Array"),
|
||||
inBuf: `{"Array": []}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Array": `, "/Array").withType(0, T[[1]string]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Format/Invalid/Interface"),
|
||||
inBuf: `{"Interface": "anything"}`,
|
||||
inVal: new(structFormatInvalid),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"Interface": `, "/Interface").withType(0, T[any]()),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/Inline/Zero"),
|
||||
inBuf: `{"D":""}`,
|
||||
|
|
@ -7016,6 +7096,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
inBuf: `{"":"empty",",":"comma","\"":"quote"}`,
|
||||
inVal: new(structWeirdNames),
|
||||
want: addr(structWeirdNames{Empty: "empty", Comma: "comma", Quote: "quote"}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Structs/NoCase/Exact"),
|
||||
inBuf: `{"Aaa":"Aaa","AA_A":"AA_A","AaA":"AaA","AAa":"AAa","AAA":"AAA"}`,
|
||||
|
|
@ -8709,6 +8790,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag.
|
||||
D2 time.Duration `json:",format:nano"`
|
||||
}{0, 0}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Zero"),
|
||||
inBuf: `{"D1":"0s","D2":0}`,
|
||||
|
|
@ -8720,6 +8802,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D1 time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag.
|
||||
D2 time.Duration `json:",format:nano"`
|
||||
}{0, 0}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Positive"),
|
||||
inBuf: `{"D1":"34293h33m9.123456789s","D2":123456789123456789}`,
|
||||
|
|
@ -8734,6 +8817,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
123456789123456789,
|
||||
123456789123456789,
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Negative"),
|
||||
inBuf: `{"D1":"-34293h33m9.123456789s","D2":-123456789123456789}`,
|
||||
|
|
@ -8748,6 +8832,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
-123456789123456789,
|
||||
-123456789123456789,
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Nanos/String"),
|
||||
inBuf: `{"D":"12345"}`,
|
||||
|
|
@ -8757,6 +8842,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
want: addr(struct {
|
||||
D time.Duration `json:",string,format:nano"`
|
||||
}{12345}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Nanos/String/Invalid"),
|
||||
inBuf: `{"D":"+12345"}`,
|
||||
|
|
@ -8767,6 +8853,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D time.Duration `json:",string,format:nano"`
|
||||
}{1}),
|
||||
wantErr: EU(fmt.Errorf(`invalid duration "+12345": %w`, strconv.ErrSyntax)).withPos(`{"D":`, "/D").withType('"', timeDurationType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Nanos/Mismatch"),
|
||||
inBuf: `{"D":"34293h33m9.123456789s"}`,
|
||||
|
|
@ -8777,6 +8864,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D time.Duration `json:",format:nano"`
|
||||
}{1}),
|
||||
wantErr: EU(nil).withPos(`{"D":`, "/D").withType('"', timeDurationType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Nanos"),
|
||||
inBuf: `{"D":1.324}`,
|
||||
|
|
@ -8786,6 +8874,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
want: addr(struct {
|
||||
D time.Duration `json:",format:nano"`
|
||||
}{1}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/String/Mismatch"),
|
||||
inBuf: `{"D":-123456789123456789}`,
|
||||
|
|
@ -8796,6 +8885,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag.
|
||||
}{1}),
|
||||
wantErr: EU(nil).withPos(`{"D":`, "/D").withType('0', timeDurationType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/String/Invalid"),
|
||||
inBuf: `{"D":"5minkutes"}`,
|
||||
|
|
@ -8809,6 +8899,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
_, err := time.ParseDuration("5minkutes")
|
||||
return err
|
||||
}()).withPos(`{"D":`, "/D").withType('"', timeDurationType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Syntax/Invalid"),
|
||||
inBuf: `{"D":x}`,
|
||||
|
|
@ -8819,6 +8910,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag.
|
||||
}{1}),
|
||||
wantErr: newInvalidCharacterError("x", "at start of value", len64(`{"D":`), "/D"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Format"),
|
||||
inBuf: `{
|
||||
|
|
@ -8848,6 +8940,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
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,
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Duration/Format/Invalid"),
|
||||
inBuf: `{"D":"0s"}`,
|
||||
|
|
@ -8858,6 +8951,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
D time.Duration `json:",format:invalid"`
|
||||
}{1}),
|
||||
wantErr: EU(errInvalidFormatFlag).withPos(`{"D":`, "/D").withType(0, timeDurationType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
/* TODO(https://go.dev/issue/71631): Re-enable this test case.
|
||||
name: jsontest.Name("Duration/Format/Legacy"),
|
||||
|
|
@ -8880,6 +8974,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
inBuf: `{"1000000000":""}`,
|
||||
inVal: new(map[time.Duration]string),
|
||||
want: addr(map[time.Duration]string{time.Second: ""}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
/* TODO(https://go.dev/issue/71631): Re-enable this test case.
|
||||
name: jsontest.Name("Duration/IgnoreInvalidFormat"),
|
||||
|
|
@ -8910,6 +9005,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"),
|
||||
mustParseTime(time.RFC3339Nano, "0001-01-01T00:00:00Z"),
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format"),
|
||||
inBuf: `{
|
||||
|
|
@ -8975,6 +9071,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
time.Unix(-23225777755, 6).UTC(),
|
||||
time.Unix(-23225777755, 6).UTC(),
|
||||
}),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format/UnixString/InvalidNumber"),
|
||||
inBuf: `{
|
||||
|
|
@ -8986,6 +9083,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
inVal: new(structTimeFormat),
|
||||
want: new(structTimeFormat),
|
||||
wantErr: EU(nil).withPos(`{`+"\n\t\t\t"+`"T23": `, "/T23").withType('0', timeTimeType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format/UnixString/InvalidString"),
|
||||
inBuf: `{
|
||||
|
|
@ -8997,6 +9095,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
inVal: new(structTimeFormat),
|
||||
want: new(structTimeFormat),
|
||||
wantErr: EU(nil).withPos(`{`+"\n\t\t\t"+`"T22": `, "/T22").withType('"', timeTimeType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
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}`,
|
||||
|
|
@ -9032,6 +9131,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
time.Unix(-23225777755, 6).UTC(),
|
||||
}),
|
||||
want: new(structTimeFormat),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/RFC3339/Mismatch"),
|
||||
inBuf: `{"T":1234}`,
|
||||
|
|
@ -9056,6 +9156,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
T time.Time `json:",format:UndefinedConstant"`
|
||||
}),
|
||||
wantErr: EU(errors.New(`invalid format flag "UndefinedConstant"`)).withPos(`{"T":`, "/T").withType(0, timeTimeType),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("Time/Format/SingleDigitHour"),
|
||||
inBuf: `{"T":"2000-01-01T1:12:34Z"}`,
|
||||
|
|
@ -9092,6 +9193,9 @@ func TestUnmarshal(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
if tt.skip {
|
||||
continue
|
||||
}
|
||||
t.Run(tt.name.Name, func(t *testing.T) {
|
||||
got := tt.inVal
|
||||
gotErr := Unmarshal([]byte(tt.inBuf), got, tt.opts...)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,11 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
|||
return marshalNano(enc, va, mo)
|
||||
} else {
|
||||
// TODO(https://go.dev/issue/71631): Decide on default duration representation.
|
||||
return newMarshalErrorBefore(enc, t, errors.New("no default representation; specify an explicit format"))
|
||||
var workaround string
|
||||
if internal.ExpJSONFormat {
|
||||
workaround = "; specify an explicit format"
|
||||
}
|
||||
return newMarshalErrorBefore(enc, t, errors.New("no default representation"+workaround))
|
||||
}
|
||||
|
||||
m.td, _ = reflect.TypeAssert[time.Duration](va.Value)
|
||||
|
|
@ -79,7 +83,11 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
|||
return unmarshalNano(dec, va, uo)
|
||||
} else {
|
||||
// TODO(https://go.dev/issue/71631): Decide on default duration representation.
|
||||
return newUnmarshalErrorBeforeWithSkipping(dec, t, errors.New("no default representation; specify an explicit format"))
|
||||
var workaround string
|
||||
if internal.ExpJSONFormat {
|
||||
workaround = "; specify an explicit format"
|
||||
}
|
||||
return newUnmarshalErrorBeforeWithSkipping(dec, t, errors.New("no default representation"+workaround))
|
||||
}
|
||||
|
||||
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
|
||||
|
|
|
|||
|
|
@ -66,11 +66,7 @@
|
|||
//
|
||||
// The first option is the JSON object name override for the Go struct field.
|
||||
// If the name is not specified, then the Go struct field name
|
||||
// is used as the JSON object name. JSON names containing commas or quotes,
|
||||
// or names identical to "" or "-", can be specified using
|
||||
// a single-quoted string literal, where the syntax is identical to
|
||||
// the Go grammar for a double-quoted string literal,
|
||||
// but instead uses single quotes as the delimiters.
|
||||
// is used as the JSON object name.
|
||||
// By default, unmarshaling uses case-sensitive matching to identify
|
||||
// the Go struct field associated with a JSON object name.
|
||||
//
|
||||
|
|
@ -121,14 +117,6 @@
|
|||
// while many non-fallback fields may be specified. 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.
|
||||
|
|
|
|||
76
src/encoding/json/v2/doc_format.go
Normal file
76
src/encoding/json/v2/doc_format.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build goexperiment.jsonv2 && goexperiment.jsonformat
|
||||
|
||||
// # Format Tag Option
|
||||
//
|
||||
// The `format` tag option is experimental,
|
||||
// and not subject to the Go 1 compatibility promise.
|
||||
// It only exists when building with the GOEXPERIMENT=jsonformat environment variable set.
|
||||
//
|
||||
// Some Go types support alternative JSON representations as specified below.
|
||||
// The `format` tag 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 option
|
||||
// is determined by the struct field type.
|
||||
//
|
||||
// Go types with alternative representations are as follows:
|
||||
//
|
||||
// - A Go []byte or [N]byte is usually represented as a JSON string
|
||||
// containing the binary value encoded using RFC 4648.
|
||||
// If the format is "base64" or unspecified, then this uses RFC 4648, section 4.
|
||||
// If the format is "base64url", then this uses RFC 4648, section 5.
|
||||
// If the format is "base32", then this uses RFC 4648, section 6.
|
||||
// If the format is "base32hex", then this uses RFC 4648, section 7.
|
||||
// If the format is "base16" or "hex", then this uses RFC 4648, section 8.
|
||||
// If the format is "array", then the bytes value is represented as a JSON array
|
||||
// where each element recursively uses the JSON representation of each byte.
|
||||
//
|
||||
// - A Go float is usually represented as a JSON number.
|
||||
// If the format is "nonfinite", then NaN, +Inf, and -Inf are represented as
|
||||
// the JSON strings "NaN", "Infinity", and "-Infinity", respectively.
|
||||
// Without the use of this format, such string values result in a [SemanticError].
|
||||
//
|
||||
// - A nil Go map is usually encoded using an empty JSON object.
|
||||
// If the format is "emitnull", then a nil map is encoded as a JSON null.
|
||||
// If the format is "emitempty", then a nil map is encoded as an empty JSON object,
|
||||
// regardless of whether [FormatNilMapAsNull] is specified.
|
||||
//
|
||||
// - A nil Go slice is usually encoded using an empty JSON array.
|
||||
// If the format is "emitnull", then a nil slice is encoded as a JSON null.
|
||||
// If the format is "emitempty", then a nil slice is encoded as an empty JSON array,
|
||||
// regardless of whether [FormatNilSliceAsNull] is specified.
|
||||
//
|
||||
// - A Go pointer usually uses the JSON representation of the underlying value.
|
||||
// The format is forwarded to the marshaling and unmarshaling of the underlying type.
|
||||
//
|
||||
// - A Go [time.Time] is usually represented as a JSON string containing
|
||||
// the timestamp formatted in RFC 3339 with nanosecond precision.
|
||||
// If the format matches one of the format constants declared
|
||||
// in the time package (e.g., RFC1123), then that format is used.
|
||||
// If the format is "unix", "unixmilli", "unixmicro", or "unixnano",
|
||||
// then the timestamp is represented as a possibly fractional JSON number
|
||||
// of the number of seconds (or milliseconds, microseconds, or nanoseconds)
|
||||
// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC.
|
||||
// To avoid a fractional component when encoding,
|
||||
// round the timestamp to the relevant unit.
|
||||
// Otherwise if non-empty, the format is used as-is and
|
||||
// encoded using [time.Time.Format] and
|
||||
// decoded using [time.Time.Parse].
|
||||
//
|
||||
// - A Go [time.Duration] usually has no default representation.
|
||||
// If the format is "sec", "milli", "micro", or "nano",
|
||||
// then the duration is represented as a possibly fractional JSON number
|
||||
// of the number of seconds (or milliseconds, microseconds, or nanoseconds).
|
||||
// To avoid a fractional component when encoding,
|
||||
// round the duration to the relevant unit.
|
||||
// If the format is "units", it is represented as a JSON string
|
||||
// encoded using [time.Duration.String] and decoded using [time.ParseDuration]
|
||||
// (e.g., "1h30m" for 1 hour 30 minutes).
|
||||
// If the format is "iso8601", it is represented as a JSON string using the
|
||||
// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes)
|
||||
// using only accurate units of hours, minutes, and seconds.
|
||||
package json
|
||||
76
src/encoding/json/v2/example_format_test.go
Normal file
76
src/encoding/json/v2/example_format_test.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// 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 && goexperiment.jsonformat
|
||||
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"encoding/json/jsontext"
|
||||
"encoding/json/v2"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
DurationISO8601 time.Duration `json:",format:iso8601"`
|
||||
}{
|
||||
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,
|
||||
DurationISO8601: 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,
|
||||
// "DurationISO8601": "PT12H34M56.007008009S"
|
||||
// }
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"os"
|
||||
|
|
@ -77,14 +76,6 @@ func Example_fieldNames() {
|
|||
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
|
||||
}
|
||||
|
|
@ -100,11 +91,7 @@ func Example_fieldNames() {
|
|||
// {
|
||||
// "GoName": null,
|
||||
// "jsonName": null,
|
||||
// "Option": null,
|
||||
// "": null,
|
||||
// "-": null,
|
||||
// ",": null,
|
||||
// "\"'": null
|
||||
// "Option": null
|
||||
// }
|
||||
}
|
||||
|
||||
|
|
@ -338,66 +325,6 @@ func Example_inlinedFields() {
|
|||
// }
|
||||
}
|
||||
|
||||
// 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"`
|
||||
DurationISO8601 time.Duration `json:",format:iso8601"`
|
||||
}{
|
||||
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,
|
||||
DurationISO8601: 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,
|
||||
// "DurationISO8601": "PT12H34M56.007008009S"
|
||||
// }
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"encoding/json/internal"
|
||||
"encoding/json/internal/jsonflags"
|
||||
"encoding/json/internal/jsonwire"
|
||||
)
|
||||
|
|
@ -519,6 +520,10 @@ func parseFieldOptions(sf reflect.StructField) (out fieldOptions, ignored bool,
|
|||
case "string":
|
||||
out.string = true
|
||||
case "format":
|
||||
if !internal.ExpJSONFormat {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid `format` tag option without GOEXPERIMENT=jsonformat", sf.Name))
|
||||
break
|
||||
}
|
||||
if !strings.HasPrefix(tag, ":") {
|
||||
err = cmp.Or(err, fmt.Errorf("Go struct field %s is missing value for `format` tag option", sf.Name))
|
||||
break
|
||||
|
|
@ -576,6 +581,10 @@ func consumeTagOption(in string) (string, int, error) {
|
|||
return in[:n], n, nil
|
||||
// Option as a single-quoted string.
|
||||
case r == '\'':
|
||||
if !internal.ExpJSONFormat {
|
||||
return in[:i], i, fmt.Errorf("invalid use of single-quoted tag option without GOEXPERIMENT=jsonformat")
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"encoding/json/internal"
|
||||
"encoding/json/internal/jsontest"
|
||||
"encoding/json/jsontext"
|
||||
)
|
||||
|
|
@ -33,6 +34,7 @@ func TestMakeStructFields(t *testing.T) {
|
|||
in any
|
||||
want structFields
|
||||
wantErr error
|
||||
skip bool
|
||||
}{{
|
||||
name: jsontest.Name("Names"),
|
||||
in: struct {
|
||||
|
|
@ -223,6 +225,7 @@ func TestMakeStructFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
wantErr: errors.New(`Go struct field Name has JSON object name "ޭ\xbe\xef" with invalid UTF-8`),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("DuplicateName"),
|
||||
in: struct {
|
||||
|
|
@ -391,6 +394,9 @@ func TestMakeStructFields(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
if tt.skip {
|
||||
continue
|
||||
}
|
||||
t.Run(tt.name.Name, func(t *testing.T) {
|
||||
got, err := makeStructFields(reflect.TypeOf(tt.in))
|
||||
|
||||
|
|
@ -455,6 +461,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
wantOpts fieldOptions
|
||||
wantIgnored bool
|
||||
wantErr error
|
||||
skip bool
|
||||
}{{
|
||||
name: jsontest.Name("GoName"),
|
||||
in: struct {
|
||||
|
|
@ -525,12 +532,14 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V int `json:"'-',omitempty"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "-", quotedName: `"-"`, omitempty: true},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("QuotedDashName"),
|
||||
in: struct {
|
||||
V int `json:"'-'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "-", quotedName: `"-"`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("LatinPunctuationName"),
|
||||
in: struct {
|
||||
|
|
@ -543,6 +552,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V int `json:"'$%-/'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "$%-/", quotedName: `"$%-/"`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("LatinDigitsName"),
|
||||
in: struct {
|
||||
|
|
@ -555,6 +565,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V int `json:"'0123456789'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "0123456789", quotedName: `"0123456789"`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("LatinUppercaseName"),
|
||||
in: struct {
|
||||
|
|
@ -579,6 +590,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V string `json:"'Ελλάδα'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "Ελλάδα", quotedName: `"Ελλάδα"`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("ChineseName"),
|
||||
in: struct {
|
||||
|
|
@ -591,6 +603,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V string `json:"'世界'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "世界", quotedName: `"世界"`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("PercentSlashName"),
|
||||
in: struct {
|
||||
|
|
@ -603,6 +616,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V int `json:"'text/html%'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "text/html%", quotedName: `"text/html%"`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("PunctuationName"),
|
||||
in: struct {
|
||||
|
|
@ -615,24 +629,28 @@ func TestParseTagOptions(t *testing.T) {
|
|||
V string `json:"'!#$%&()*+-./:;<=>?@[]^_{|}~ '"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "!#$%&()*+-./:;<=>?@[]^_{|}~ ", quotedName: `"!#$%&()*+-./:;<=>?@[]^_{|}~ "`, nameNeedEscape: true},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("EmptyName"),
|
||||
in: struct {
|
||||
V int `json:"''"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: "", quotedName: `""`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("SpaceName"),
|
||||
in: struct {
|
||||
V int `json:"' '"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: " ", quotedName: `" "`},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("CommaQuotes"),
|
||||
in: struct {
|
||||
V int `json:"',\\'\"\\\"'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{hasName: true, name: `,'""`, quotedName: `",'\"\""`, nameNeedEscape: true},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("SingleComma"),
|
||||
in: struct {
|
||||
|
|
@ -680,6 +698,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
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"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("BothCaseOptions"),
|
||||
in: struct {
|
||||
|
|
@ -718,18 +737,21 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`},
|
||||
wantErr: errors.New("Go struct field FieldName is missing value for `format` tag option"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("FormatOptionColon"),
|
||||
in: struct {
|
||||
FieldName int `json:",format:fizzbuzz"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "fizzbuzz"},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("FormatOptionQuoted"),
|
||||
in: struct {
|
||||
FieldName int `json:",format:'2006-01-02'"`
|
||||
}{},
|
||||
wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "2006-01-02"},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("FormatOptionInvalid"),
|
||||
in: struct {
|
||||
|
|
@ -737,6 +759,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
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..."),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("FormatOptionNotLast"),
|
||||
in: struct {
|
||||
|
|
@ -744,6 +767,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, format: "alpha"},
|
||||
wantErr: errors.New("Go struct field FieldName has `format` tag option that was not specified last"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("AllOptions"),
|
||||
in: struct {
|
||||
|
|
@ -759,6 +783,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
string: true,
|
||||
format: "format",
|
||||
},
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("AllOptionsQuoted"),
|
||||
in: struct {
|
||||
|
|
@ -775,6 +800,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
format: "format",
|
||||
},
|
||||
wantErr: errors.New("Go struct field FieldName has unnecessarily quoted appearance of `'case'` tag option; specify `case` instead"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("AllOptionsCaseSensitive"),
|
||||
in: struct {
|
||||
|
|
@ -802,6 +828,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
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..."),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("MalformedQuotedString/MissingComma"),
|
||||
in: struct {
|
||||
|
|
@ -809,6 +836,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
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 ',')"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("MalformedQuotedString/InvalidEscape"),
|
||||
in: struct {
|
||||
|
|
@ -816,6 +844,7 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}{},
|
||||
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####'"),
|
||||
skip: !internal.ExpJSONFormat,
|
||||
}, {
|
||||
name: jsontest.Name("MisnamedTag"),
|
||||
in: struct {
|
||||
|
|
@ -825,6 +854,9 @@ func TestParseTagOptions(t *testing.T) {
|
|||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
if tt.skip {
|
||||
continue
|
||||
}
|
||||
t.Run(tt.name.Name, func(t *testing.T) {
|
||||
fs := reflect.TypeOf(tt.in).Field(0)
|
||||
gotOpts, gotIgnored, gotErr := parseFieldOptions(fs)
|
||||
|
|
|
|||
|
|
@ -139,8 +139,6 @@ func Deterministic(v bool) Options {
|
|||
// FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
|
||||
// JSON null instead of the default representation as an empty JSON array
|
||||
// (or an empty JSON string in the case of ~[]byte).
|
||||
// Slice fields explicitly marked with `format:emitempty` still marshal
|
||||
// as an empty JSON array.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func FormatNilSliceAsNull(v bool) Options {
|
||||
|
|
@ -153,8 +151,6 @@ func FormatNilSliceAsNull(v bool) Options {
|
|||
|
||||
// FormatNilMapAsNull specifies that a nil Go map should marshal as a
|
||||
// JSON null instead of the default representation as an empty JSON object.
|
||||
// Map fields explicitly marked with `format:emitempty` still marshal
|
||||
// as an empty JSON object.
|
||||
//
|
||||
// This only affects marshaling and is ignored when unmarshaling.
|
||||
func FormatNilMapAsNull(v bool) Options {
|
||||
|
|
|
|||
|
|
@ -458,13 +458,8 @@ func TestStringOption(t *testing.T) {
|
|||
// 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"`
|
||||
// }
|
||||
// Users of v2 can opt into the v1 behavior by setting the
|
||||
// [jsonv2.FormatNilSliceAsNull] and [jsonv2.FormatNilMapAsNull] options.
|
||||
//
|
||||
// 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
|
||||
|
|
@ -552,12 +547,8 @@ func TestArrays(t *testing.T) {
|
|||
// 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"`
|
||||
// }
|
||||
// Users of v2 can opt into the v1 behavior by setting the
|
||||
// [jsonv1.FormatByteArrayAsArray] option.
|
||||
func TestByteArrays(t *testing.T) {
|
||||
for _, json := range jsonPackages {
|
||||
t.Run(path.Join("Marshal", json.Version), func(t *testing.T) {
|
||||
|
|
@ -1027,12 +1018,8 @@ func TestMergeComposite(t *testing.T) {
|
|||
// 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"`
|
||||
// }
|
||||
// Users of v2 can opt into the v1 behavior by setting the
|
||||
// [jsonv1.FormatDurationAsNano] option.
|
||||
//
|
||||
// Related issue:
|
||||
//
|
||||
|
|
|
|||
8
src/internal/goexperiment/exp_jsonformat_off.go
Normal file
8
src/internal/goexperiment/exp_jsonformat_off.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||
|
||||
//go:build !goexperiment.jsonformat
|
||||
|
||||
package goexperiment
|
||||
|
||||
const JSONFormat = false
|
||||
const JSONFormatInt = 0
|
||||
8
src/internal/goexperiment/exp_jsonformat_on.go
Normal file
8
src/internal/goexperiment/exp_jsonformat_on.go
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||
|
||||
//go:build goexperiment.jsonformat
|
||||
|
||||
package goexperiment
|
||||
|
||||
const JSONFormat = true
|
||||
const JSONFormatInt = 1
|
||||
|
|
@ -106,6 +106,9 @@ type Flags struct {
|
|||
// JSONv2 enables the json/v2 package.
|
||||
JSONv2 bool
|
||||
|
||||
// JSONFormat enables use of the `format` tag option with the json packages.
|
||||
JSONFormat bool
|
||||
|
||||
// GreenTeaGC enables the Green Tea GC implementation.
|
||||
GreenTeaGC bool
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue