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  // Options represent either a singular option or a set of options.
    27  // It can be functionally 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 singular option value:
    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 to 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 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  //
    86  // Example usage:
    87  //
    88  //	v, ok := json.GetOption(opts, json.Deterministic)
    89  //
    90  // Options are most commonly introspected to alter the JSON representation of
    91  // [MarshalerTo.MarshalJSONTo] and [UnmarshalerFrom.UnmarshalJSONFrom] methods, and
    92  // [MarshalToFunc] and [UnmarshalFromFunc] functions.
    93  // In such cases, the presence bit should generally be ignored.
    94  func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
    95  	return jsonopts.GetOption(opts, setter)
    96  }
    97  
    98  // DefaultOptionsV2 is the full set of all options that define v2 semantics.
    99  // It is equivalent to the set of options in [encoding/json.DefaultOptionsV1]
   100  // all being set to false. All other options are not present.
   101  func DefaultOptionsV2() Options {
   102  	return &jsonopts.DefaultOptionsV2
   103  }
   104  
   105  // StringifyNumbers specifies that numeric Go types should be marshaled
   106  // as a JSON string containing the equivalent JSON number value.
   107  // When unmarshaling, numeric Go types are parsed from a JSON string
   108  // containing the JSON number without any surrounding whitespace.
   109  //
   110  // According to RFC 8259, section 6, a JSON implementation may choose to
   111  // limit the representation of a JSON number to an IEEE 754 binary64 value.
   112  // This may cause decoders to lose precision for int64 and uint64 types.
   113  // Quoting JSON numbers as a JSON string preserves the exact precision.
   114  //
   115  // This affects either marshaling or unmarshaling.
   116  func StringifyNumbers(v bool) Options {
   117  	if v {
   118  		return jsonflags.StringifyNumbers | 1
   119  	} else {
   120  		return jsonflags.StringifyNumbers | 0
   121  	}
   122  }
   123  
   124  // Deterministic specifies that the same input value will be serialized
   125  // as the exact same output bytes. Different processes of
   126  // the same program will serialize equal values to the same bytes,
   127  // but different versions of the same program are not guaranteed
   128  // to produce the exact same sequence of bytes.
   129  //
   130  // This only affects marshaling and is ignored when unmarshaling.
   131  func Deterministic(v bool) Options {
   132  	if v {
   133  		return jsonflags.Deterministic | 1
   134  	} else {
   135  		return jsonflags.Deterministic | 0
   136  	}
   137  }
   138  
   139  // FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
   140  // JSON null instead of the default representation as an empty JSON array
   141  // (or an empty JSON string in the case of ~[]byte).
   142  // Slice fields explicitly marked with `format:emitempty` still marshal
   143  // as an empty JSON array.
   144  //
   145  // This only affects marshaling and is ignored when unmarshaling.
   146  func FormatNilSliceAsNull(v bool) Options {
   147  	if v {
   148  		return jsonflags.FormatNilSliceAsNull | 1
   149  	} else {
   150  		return jsonflags.FormatNilSliceAsNull | 0
   151  	}
   152  }
   153  
   154  // FormatNilMapAsNull specifies that a nil Go map should marshal as a
   155  // JSON null instead of the default representation as an empty JSON object.
   156  // Map fields explicitly marked with `format:emitempty` still marshal
   157  // as an empty JSON object.
   158  //
   159  // This only affects marshaling and is ignored when unmarshaling.
   160  func FormatNilMapAsNull(v bool) Options {
   161  	if v {
   162  		return jsonflags.FormatNilMapAsNull | 1
   163  	} else {
   164  		return jsonflags.FormatNilMapAsNull | 0
   165  	}
   166  }
   167  
   168  // OmitZeroStructFields specifies that a Go struct should marshal in such a way
   169  // that all struct fields that are zero are omitted from the marshaled output
   170  // if the value is zero as determined by the "IsZero() bool" method if present,
   171  // otherwise based on whether the field is the zero Go value.
   172  // This is semantically equivalent to specifying the `omitzero` tag option
   173  // on every field in a Go struct.
   174  //
   175  // This only affects marshaling and is ignored when unmarshaling.
   176  func OmitZeroStructFields(v bool) Options {
   177  	if v {
   178  		return jsonflags.OmitZeroStructFields | 1
   179  	} else {
   180  		return jsonflags.OmitZeroStructFields | 0
   181  	}
   182  }
   183  
   184  // MatchCaseInsensitiveNames specifies that JSON object members are matched
   185  // against Go struct fields using a case-insensitive match of the name.
   186  // Go struct fields explicitly marked with `case:strict` or `case:ignore`
   187  // always use case-sensitive (or case-insensitive) name matching,
   188  // regardless of the value of this option.
   189  //
   190  // This affects either marshaling or unmarshaling.
   191  // For marshaling, this option may alter the detection of duplicate names
   192  // (assuming [jsontext.AllowDuplicateNames] is false) from inlined fields
   193  // if it matches one of the declared fields in the Go struct.
   194  func MatchCaseInsensitiveNames(v bool) Options {
   195  	if v {
   196  		return jsonflags.MatchCaseInsensitiveNames | 1
   197  	} else {
   198  		return jsonflags.MatchCaseInsensitiveNames | 0
   199  	}
   200  }
   201  
   202  // RejectUnknownMembers specifies that unknown members should be rejected
   203  // when unmarshaling a JSON object.
   204  //
   205  // This only affects unmarshaling and is ignored when marshaling.
   206  func RejectUnknownMembers(v bool) Options {
   207  	if v {
   208  		return jsonflags.RejectUnknownMembers | 1
   209  	} else {
   210  		return jsonflags.RejectUnknownMembers | 0
   211  	}
   212  }
   213  
   214  // WithMarshalers specifies a list of type-specific marshalers to use,
   215  // which can be used to override the default marshal behavior for values
   216  // of particular types.
   217  //
   218  // This only affects marshaling and is ignored when unmarshaling.
   219  func WithMarshalers(v *Marshalers) Options {
   220  	return (*marshalersOption)(v)
   221  }
   222  
   223  // WithUnmarshalers specifies a list of type-specific unmarshalers to use,
   224  // which can be used to override the default unmarshal behavior for values
   225  // of particular types.
   226  //
   227  // This only affects unmarshaling and is ignored when marshaling.
   228  func WithUnmarshalers(v *Unmarshalers) Options {
   229  	return (*unmarshalersOption)(v)
   230  }
   231  
   232  // These option types are declared here instead of "jsonopts"
   233  // to avoid a dependency on "reflect" from "jsonopts".
   234  type (
   235  	marshalersOption   Marshalers
   236  	unmarshalersOption Unmarshalers
   237  )
   238  
   239  func (*marshalersOption) JSONOptions(internal.NotForPublicUse)   {}
   240  func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {}
   241  
   242  // Inject support into "jsonopts" to handle these types.
   243  func init() {
   244  	jsonopts.GetUnknownOption = func(src jsonopts.Struct, zero jsonopts.Options) (any, bool) {
   245  		switch zero.(type) {
   246  		case *marshalersOption:
   247  			if !src.Flags.Has(jsonflags.Marshalers) {
   248  				return (*Marshalers)(nil), false
   249  			}
   250  			return src.Marshalers.(*Marshalers), true
   251  		case *unmarshalersOption:
   252  			if !src.Flags.Has(jsonflags.Unmarshalers) {
   253  				return (*Unmarshalers)(nil), false
   254  			}
   255  			return src.Unmarshalers.(*Unmarshalers), true
   256  		default:
   257  			panic(fmt.Sprintf("unknown option %T", zero))
   258  		}
   259  	}
   260  	jsonopts.JoinUnknownOption = func(dst jsonopts.Struct, src jsonopts.Options) jsonopts.Struct {
   261  		switch src := src.(type) {
   262  		case *marshalersOption:
   263  			dst.Flags.Set(jsonflags.Marshalers | 1)
   264  			dst.Marshalers = (*Marshalers)(src)
   265  		case *unmarshalersOption:
   266  			dst.Flags.Set(jsonflags.Unmarshalers | 1)
   267  			dst.Unmarshalers = (*Unmarshalers)(src)
   268  		default:
   269  			panic(fmt.Sprintf("unknown option %T", src))
   270  		}
   271  		return dst
   272  	}
   273  }
   274  

View as plain text