File size: 10,138 Bytes
e36aeda | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | // Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build goexperiment.jsonv2
package json
import (
"fmt"
"encoding/json/internal"
"encoding/json/internal/jsonflags"
"encoding/json/internal/jsonopts"
)
// Options configure [Marshal], [MarshalWrite], [MarshalEncode],
// [Unmarshal], [UnmarshalRead], and [UnmarshalDecode] with specific features.
// Each function takes in a variadic list of options, where properties
// set in later options override the value of previously set properties.
//
// The Options type is identical to [encoding/json.Options] and
// [encoding/json/jsontext.Options]. Options from the other packages can
// be used interchangeably with functionality in this package.
//
// Options represent either a singular option or a set of options.
// It can be functionally thought of as a Go map of option properties
// (even though the underlying implementation avoids Go maps for performance).
//
// The constructors (e.g., [Deterministic]) return a singular option value:
//
// opt := Deterministic(true)
//
// which is analogous to creating a single entry map:
//
// opt := Options{"Deterministic": true}
//
// [JoinOptions] composes multiple options values to together:
//
// out := JoinOptions(opts...)
//
// which is analogous to making a new map and copying the options over:
//
// out := make(Options)
// for _, m := range opts {
// for k, v := range m {
// out[k] = v
// }
// }
//
// [GetOption] looks up the value of options parameter:
//
// v, ok := GetOption(opts, Deterministic)
//
// which is analogous to a Go map lookup:
//
// v, ok := Options["Deterministic"]
//
// There is a single Options type, which is used with both marshal and unmarshal.
// Some options affect both operations, while others only affect one operation:
//
// - [StringifyNumbers] affects marshaling and unmarshaling
// - [Deterministic] affects marshaling only
// - [FormatNilSliceAsNull] affects marshaling only
// - [FormatNilMapAsNull] affects marshaling only
// - [OmitZeroStructFields] affects marshaling only
// - [MatchCaseInsensitiveNames] affects marshaling and unmarshaling
// - [DiscardUnknownMembers] affects marshaling only
// - [RejectUnknownMembers] affects unmarshaling only
// - [WithMarshalers] affects marshaling only
// - [WithUnmarshalers] affects unmarshaling only
//
// Options that do not affect a particular operation are ignored.
type Options = jsonopts.Options
// JoinOptions coalesces the provided list of options into a single Options.
// Properties set in later options override the value of previously set properties.
func JoinOptions(srcs ...Options) Options {
var dst jsonopts.Struct
dst.Join(srcs...)
return &dst
}
// GetOption returns the value stored in opts with the provided setter,
// reporting whether the value is present.
//
// Example usage:
//
// v, ok := json.GetOption(opts, json.Deterministic)
//
// Options are most commonly introspected to alter the JSON representation of
// [MarshalerTo.MarshalJSONTo] and [UnmarshalerFrom.UnmarshalJSONFrom] methods, and
// [MarshalToFunc] and [UnmarshalFromFunc] functions.
// In such cases, the presence bit should generally be ignored.
func GetOption[T any](opts Options, setter func(T) Options) (T, bool) {
return jsonopts.GetOption(opts, setter)
}
// DefaultOptionsV2 is the full set of all options that define v2 semantics.
// It is equivalent to the set of options in [encoding/json.DefaultOptionsV1]
// all being set to false. All other options are not present.
func DefaultOptionsV2() Options {
return &jsonopts.DefaultOptionsV2
}
// StringifyNumbers specifies that numeric Go types should be marshaled
// as a JSON string containing the equivalent JSON number value.
// When unmarshaling, numeric Go types are parsed from a JSON string
// containing the JSON number without any surrounding whitespace.
//
// According to RFC 8259, section 6, a JSON implementation may choose to
// limit the representation of a JSON number to an IEEE 754 binary64 value.
// This may cause decoders to lose precision for int64 and uint64 types.
// Quoting JSON numbers as a JSON string preserves the exact precision.
//
// This affects either marshaling or unmarshaling.
func StringifyNumbers(v bool) Options {
if v {
return jsonflags.StringifyNumbers | 1
} else {
return jsonflags.StringifyNumbers | 0
}
}
// Deterministic specifies that the same input value will be serialized
// as the exact same output bytes. Different processes of
// the same program will serialize equal values to the same bytes,
// but different versions of the same program are not guaranteed
// to produce the exact same sequence of bytes.
//
// This only affects marshaling and is ignored when unmarshaling.
func Deterministic(v bool) Options {
if v {
return jsonflags.Deterministic | 1
} else {
return jsonflags.Deterministic | 0
}
}
// FormatNilSliceAsNull specifies that a nil Go slice should marshal as a
// JSON null instead of the default representation as an empty JSON array
// (or an empty JSON string in the case of ~[]byte).
// Slice fields explicitly marked with `format:emitempty` still marshal
// as an empty JSON array.
//
// This only affects marshaling and is ignored when unmarshaling.
func FormatNilSliceAsNull(v bool) Options {
if v {
return jsonflags.FormatNilSliceAsNull | 1
} else {
return jsonflags.FormatNilSliceAsNull | 0
}
}
// FormatNilMapAsNull specifies that a nil Go map should marshal as a
// JSON null instead of the default representation as an empty JSON object.
// Map fields explicitly marked with `format:emitempty` still marshal
// as an empty JSON object.
//
// This only affects marshaling and is ignored when unmarshaling.
func FormatNilMapAsNull(v bool) Options {
if v {
return jsonflags.FormatNilMapAsNull | 1
} else {
return jsonflags.FormatNilMapAsNull | 0
}
}
// OmitZeroStructFields specifies that a Go struct should marshal in such a way
// that all struct fields that are zero are omitted from the marshaled output
// if the value is zero as determined by the "IsZero() bool" method if present,
// otherwise based on whether the field is the zero Go value.
// This is semantically equivalent to specifying the `omitzero` tag option
// on every field in a Go struct.
//
// This only affects marshaling and is ignored when unmarshaling.
func OmitZeroStructFields(v bool) Options {
if v {
return jsonflags.OmitZeroStructFields | 1
} else {
return jsonflags.OmitZeroStructFields | 0
}
}
// MatchCaseInsensitiveNames specifies that JSON object members are matched
// against Go struct fields using a case-insensitive match of the name.
// Go struct fields explicitly marked with `case:strict` or `case:ignore`
// always use case-sensitive (or case-insensitive) name matching,
// regardless of the value of this option.
//
// This affects either marshaling or unmarshaling.
// For marshaling, this option may alter the detection of duplicate names
// (assuming [jsontext.AllowDuplicateNames] is false) from inlined fields
// if it matches one of the declared fields in the Go struct.
func MatchCaseInsensitiveNames(v bool) Options {
if v {
return jsonflags.MatchCaseInsensitiveNames | 1
} else {
return jsonflags.MatchCaseInsensitiveNames | 0
}
}
// DiscardUnknownMembers specifies that marshaling should ignore any
// JSON object members stored in Go struct fields dedicated to storing
// unknown JSON object members.
//
// This only affects marshaling and is ignored when unmarshaling.
func DiscardUnknownMembers(v bool) Options {
if v {
return jsonflags.DiscardUnknownMembers | 1
} else {
return jsonflags.DiscardUnknownMembers | 0
}
}
// RejectUnknownMembers specifies that unknown members should be rejected
// when unmarshaling a JSON object, regardless of whether there is a field
// to store unknown members.
//
// This only affects unmarshaling and is ignored when marshaling.
func RejectUnknownMembers(v bool) Options {
if v {
return jsonflags.RejectUnknownMembers | 1
} else {
return jsonflags.RejectUnknownMembers | 0
}
}
// WithMarshalers specifies a list of type-specific marshalers to use,
// which can be used to override the default marshal behavior for values
// of particular types.
//
// This only affects marshaling and is ignored when unmarshaling.
func WithMarshalers(v *Marshalers) Options {
return (*marshalersOption)(v)
}
// WithUnmarshalers specifies a list of type-specific unmarshalers to use,
// which can be used to override the default unmarshal behavior for values
// of particular types.
//
// This only affects unmarshaling and is ignored when marshaling.
func WithUnmarshalers(v *Unmarshalers) Options {
return (*unmarshalersOption)(v)
}
// These option types are declared here instead of "jsonopts"
// to avoid a dependency on "reflect" from "jsonopts".
type (
marshalersOption Marshalers
unmarshalersOption Unmarshalers
)
func (*marshalersOption) JSONOptions(internal.NotForPublicUse) {}
func (*unmarshalersOption) JSONOptions(internal.NotForPublicUse) {}
// Inject support into "jsonopts" to handle these types.
func init() {
jsonopts.GetUnknownOption = func(src jsonopts.Struct, zero jsonopts.Options) (any, bool) {
switch zero.(type) {
case *marshalersOption:
if !src.Flags.Has(jsonflags.Marshalers) {
return (*Marshalers)(nil), false
}
return src.Marshalers.(*Marshalers), true
case *unmarshalersOption:
if !src.Flags.Has(jsonflags.Unmarshalers) {
return (*Unmarshalers)(nil), false
}
return src.Unmarshalers.(*Unmarshalers), true
default:
panic(fmt.Sprintf("unknown option %T", zero))
}
}
jsonopts.JoinUnknownOption = func(dst jsonopts.Struct, src jsonopts.Options) jsonopts.Struct {
switch src := src.(type) {
case *marshalersOption:
dst.Flags.Set(jsonflags.Marshalers | 1)
dst.Marshalers = (*Marshalers)(src)
case *unmarshalersOption:
dst.Flags.Set(jsonflags.Unmarshalers | 1)
dst.Unmarshalers = (*Unmarshalers)(src)
default:
panic(fmt.Sprintf("unknown option %T", src))
}
return dst
}
}
|