Source file src/encoding/json/v2/options.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json
     8  
     9  import (
    10  	"fmt"
    11  
    12  	"encoding/json/internal"
    13  	"encoding/json/internal/jsonflags"
    14  	"encoding/json/internal/jsonopts"
    15  )
    16  
    17  // Options configure [Marshal], [MarshalWrite], [MarshalEncode],
    18  // [Unmarshal], [UnmarshalRead], and [UnmarshalDecode] with specific features.
    19  // Each function takes in a variadic list of options, where properties
    20  // set in later options override the value of previously set properties.
    21  //
    22  // The Options type is identical to [encoding/json.Options] and
    23  // [encoding/json/jsontext.Options]. Options from the other packages can
    24  // be used interchangeably with functionality in this package.
    25  //
    26  // An Options value represents either a single option or a set of options.
    27  // It can be thought of as a Go map of option properties
    28  // (even though the underlying implementation avoids Go maps for performance).
    29  //
    30  // The constructors (e.g., [Deterministic]) return a value for a single option:
    31  //
    32  //	opt := Deterministic(true)
    33  //
    34  // which is analogous to creating a single entry map:
    35  //
    36  //	opt := Options{"Deterministic": true}
    37  //
    38  // [JoinOptions] composes multiple options values together:
    39  //
    40  //	out := JoinOptions(opts...)
    41  //
    42  // which is analogous to making a new map and copying the options over:
    43  //
    44  //	out := make(Options)
    45  //	for _, m := range opts {
    46  //		for k, v := range m {
    47  //			out[k] = v
    48  //		}
    49  //	}
    50  //
    51  // [GetOption] looks up the value of an options parameter:
    52  //
    53  //	v, ok := GetOption(opts, Deterministic)
    54  //
    55  // which is analogous to a Go map lookup:
    56  //
    57  //	v, ok := Options["Deterministic"]
    58  //
    59  // There is a single Options type, which is used with both marshal and unmarshal.
    60  // Some options affect both operations, while others only affect one operation:
    61  //
    62  //   - [StringifyNumbers] affects marshaling and unmarshaling
    63  //   - [Deterministic] affects marshaling only
    64  //   - [FormatNilSliceAsNull] affects marshaling only
    65  //   - [FormatNilMapAsNull] affects marshaling only
    66  //   - [OmitZeroStructFields] affects marshaling only
    67  //   - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling
    68  //   - [RejectUnknownMembers] affects unmarshaling only
    69  //   - [WithMarshalers] affects marshaling only
    70  //   - [WithUnmarshalers] affects unmarshaling only
    71  //
    72  // Options that do not affect a particular operation are ignored.
    73  type Options = jsonopts.Options
    74  
    75  // JoinOptions coalesces the provided list of options into a single Options.
    76  // Properties set in later options override the value of previously set properties.
    77  func JoinOptions(srcs ...Options) Options {
    78  	var dst jsonopts.Struct
    79  	dst.Join(srcs...)
    80  	return &dst
    81  }
    82  
    83  // GetOption returns the value stored in opts with the provided setter,
    84  // reporting whether the value is present.
    85  // If not present, the returned value is the zero value for type T.
    86  //
    87  // Example usage:
    88  //
    89  //	v, ok := json.GetOption(opts, json.Deterministic)
    90  //
    91  // Options are most commonly introspected to alter the JSON representation of
    92  // [MarshalerTo.MarshalJSONTo] and [UnmarshalerFrom.UnmarshalJSONFrom] methods, and
    93  // [MarshalToFunc] and [UnmarshalFromFunc] functions.
    94  // In such cases, the presence bit should generally be ignored.
    95  func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
    96  	return jsonopts.GetOption(opts, setter)
    97  }
    98  
    99  // DefaultOptionsV2 is the full set of all options that define v2 semantics.
   100  // It is equivalent to the set of options in [encoding/json.DefaultOptionsV1]
   101  // all being set to false. All other options are not present.
   102  func DefaultOptionsV2() Options {
   103  	return &jsonopts.DefaultOptionsV2
   104  }
   105  
   106  // StringifyNumbers specifies that types that would normally be
   107  // encoded as a JSON number instead be encoded as a JSON string
   108  // containing the equivalent JSON number value.
   109  // When unmarshaling, the value is parsed from a JSON string
   110  // containing the JSON number without any surrounding whitespace.
   111  //
   112  // Specifying the `string` tag option on a Go struct field applies this option
   113  // to the top-level JSON value for that field. When applied via the `string`
   114  // tag option, StringifyNumbers option does not recursively apply to nested
   115  // JSON numbers within a JSON object or array.
   116  //
   117  // Like all options, explicitly specifying this option in a call to [Marshal],
   118  // [Unmarshal], etc, will apply recursively.
   119  //
   120  // A Go type with custom marshal/unmarshal that represents a JSON number
   121  // should respect the StringifyNumbers option and if specified
   122  // serialize as a JSON number within a JSON string.
   123  // Custom marshal/unmarshal should handle nested JSON objects using
   124  // [MarshalEncode]/[UnmarshalDecode], which will automatically apply the
   125  // non-recursive `string` tag option behavior.
   126  //
   127  // According to RFC 8259, section 6, a JSON implementation may choose to
   128  // limit the representation of a JSON number to an IEEE 754 binary64 value.
   129  // This may cause decoders to lose precision for int64 and uint64 types.
   130  // Quoting JSON numbers as a JSON string preserves the exact precision.
   131  //
   132  // This affects either marshaling or unmarshaling.
   133  func StringifyNumbers(v bool) Options {
   134  	if v {
   135  		return jsonflags.StringifyNumbers | 1
   136  	} else {
   137  		return jsonflags.StringifyNumbers | 0
   138  	}
   139  }
   140  
   141  // Deterministic specifies that marshaling the same input value will always
   142  // serialize as the same output bytes.
   143  //
   144  // For example, Go maps are marshaled sorted by key.
   145  //
   146  // For native Go types, Determinism is guaranteed across different instances of
   147  // identical binaries, but not across different builds of a program (such as
   148  // different source or toolchain version, different GOOS/GOARCH, different
   149  // build flags).
   150  //
   151  // A Go type with a custom marshaler should also respect the Deterministic
   152  // option and serialize deterministically if it is true.
   153  //
   154  // This only affects marshaling and is ignored when unmarshaling.
   155  func Deterministic(v bool) Options {
   156  	if v {
   157  		return jsonflags.Deterministic | 1
   158  	} else {
   159  		return jsonflags.Deterministic | 0
   160  	}
   161  }
   162  
   163  // FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
   164  // JSON null instead of the default representation as an empty JSON array
   165  // (or an empty JSON string in the case of ~[]byte).
   166  //
   167  // This only affects marshaling and is ignored when unmarshaling.
   168  func FormatNilSliceAsNull(v bool) Options {
   169  	if v {
   170  		return jsonflags.FormatNilSliceAsNull | 1
   171  	} else {
   172  		return jsonflags.FormatNilSliceAsNull | 0
   173  	}
   174  }
   175  
   176  // FormatNilMapAsNull specifies that a nil Go map should marshal as a
   177  // JSON null instead of the default representation as an empty JSON object.
   178  //
   179  // This only affects marshaling and is ignored when unmarshaling.
   180  func FormatNilMapAsNull(v bool) Options {
   181  	if v {
   182  		return jsonflags.FormatNilMapAsNull | 1
   183  	} else {
   184  		return jsonflags.FormatNilMapAsNull | 0
   185  	}
   186  }
   187  
   188  // OmitZeroStructFields specifies that zero-valued fields of Go struct should be
   189  // omitted from the marshaled output.
   190  // A value is considered zero if its type has an "IsZero() bool" method that returns true,
   191  // or if it lacks such a method and the value is a Go zero value.
   192  // This option is equivalent to specifying the `omitzero` tag option
   193  // on every field in a Go struct.
   194  //
   195  // This only affects marshaling and is ignored when unmarshaling.
   196  func OmitZeroStructFields(v bool) Options {
   197  	if v {
   198  		return jsonflags.OmitZeroStructFields | 1
   199  	} else {
   200  		return jsonflags.OmitZeroStructFields | 0
   201  	}
   202  }
   203  
   204  // MatchCaseInsensitiveNames specifies that JSON object members are matched
   205  // against Go struct fields using a case-insensitive match of the name.
   206  // If a name matches multiple fields, the field whose name matches exactly is chosen.
   207  // If there is none, an error is reported.
   208  // Go struct fields explicitly marked with `case:strict` or `case:ignore`
   209  // always use case-sensitive (or case-insensitive) name matching,
   210  // regardless of the value of this option.
   211  //
   212  // This affects either marshaling or unmarshaling.
   213  //
   214  // Matching names case-insensitively also affects duplicate name detection
   215  // (assuming [jsontext.AllowDuplicateNames] is false) since
   216  // variations of the same name may match the same Go struct field.
   217  // For example, when unmarshaling, the names "foo" and "Foo" may both
   218  // match the same Go struct field and therefore be considered a duplicate name.
   219  // When marshaling, normally it is impossible for any two Go struct fields to
   220  // serialize in a way where they unmarshal into the same Go struct field
   221  // since they all have unique exact names.
   222  // However, it is possible for an
   223  // embedded fallback to contain a name that also matches the name for
   224  // a Go struct field, resulting in a duplicate name error.
   225  func MatchCaseInsensitiveNames(v bool) Options {
   226  	if v {
   227  		return jsonflags.MatchCaseInsensitiveNames | 1
   228  	} else {
   229  		return jsonflags.MatchCaseInsensitiveNames | 0
   230  	}
   231  }
   232  
   233  // RejectUnknownMembers specifies that unknown members should be rejected
   234  // when unmarshaling a JSON object.
   235  //
   236  // This only affects unmarshaling and is ignored when marshaling.
   237  func RejectUnknownMembers(v bool) Options {
   238  	if v {
   239  		return jsonflags.RejectUnknownMembers | 1
   240  	} else {
   241  		return jsonflags.RejectUnknownMembers | 0
   242  	}
   243  }
   244  
   245  // WithMarshalers specifies a list of type-specific marshalers to use,
   246  // which can be used to override the default marshal behavior for values
   247  // of particular types.
   248  //
   249  // This only affects marshaling and is ignored when unmarshaling.
   250  func WithMarshalers(v *Marshalers) Options {
   251  	return (*marshalersOption)(v)
   252  }
   253  
   254  // WithUnmarshalers specifies a list of type-specific unmarshalers to use,
   255  // which can be used to override the default unmarshal behavior for values
   256  // of particular types.
   257  //
   258  // This only affects unmarshaling and is ignored when marshaling.
   259  func WithUnmarshalers(v *Unmarshalers) Options {
   260  	return (*unmarshalersOption)(v)
   261  }
   262  
   263  // These option types are declared here instead of "jsonopts"
   264  // to avoid a dependency on "reflect" from "jsonopts".
   265  type (
   266  	marshalersOption   Marshalers
   267  	unmarshalersOption Unmarshalers
   268  )
   269  
   270  func (*marshalersOption) JSONOptions(internal.NotForPublicUse)   {}
   271  func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {}
   272  
   273  // Inject support into "jsonopts" to handle these types.
   274  func init() {
   275  	jsonopts.GetUnknownOption = func(src jsonopts.Struct, zero jsonopts.Options) (any, bool) {
   276  		switch zero.(type) {
   277  		case *marshalersOption:
   278  			if !src.Flags.Has(jsonflags.Marshalers) {
   279  				return (*Marshalers)(nil), false
   280  			}
   281  			return src.Marshalers.(*Marshalers), true
   282  		case *unmarshalersOption:
   283  			if !src.Flags.Has(jsonflags.Unmarshalers) {
   284  				return (*Unmarshalers)(nil), false
   285  			}
   286  			return src.Unmarshalers.(*Unmarshalers), true
   287  		default:
   288  			panic(fmt.Sprintf("unknown option %T", zero))
   289  		}
   290  	}
   291  	jsonopts.JoinUnknownOption = func(dst jsonopts.Struct, src jsonopts.Options) jsonopts.Struct {
   292  		switch src := src.(type) {
   293  		case *marshalersOption:
   294  			dst.Flags.Set(jsonflags.Marshalers | 1)
   295  			dst.Marshalers = (*Marshalers)(src)
   296  		case *unmarshalersOption:
   297  			dst.Flags.Set(jsonflags.Unmarshalers | 1)
   298  			dst.Unmarshalers = (*Unmarshalers)(src)
   299  		default:
   300  			panic(fmt.Sprintf("unknown option %T", src))
   301  		}
   302  		return dst
   303  	}
   304  }
   305  

View as plain text