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