| // 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 the set of options in [encoding/json.DefaultOptionsV1] |
| // all being set to false. All other options are not present. |
| func DefaultOptionsV2() Options { |
| return &jsonopts.DefaultOptionsV2 |
| } |
| |
| // StringifyNumbers specifies that numeric Go types should be marshaled |
| // as a JSON string containing the equivalent JSON number value. |
| // When unmarshaling, numeric Go types are parsed from a JSON string |
| // 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) jsonopts.Struct { |
| 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)) |
| } |
| return dst |
| } |
| } |