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