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