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