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