FormatNilSliceAsNull // marshal only
OmitZeroStructFields // marshal only
MatchCaseInsensitiveNames // marshal or unmarshal
- DiscardUnknownMembers // marshal only
RejectUnknownMembers // unmarshal only
Marshalers // marshal only; non-boolean flag
Unmarshalers // unmarshal only; non-boolean flag
)
// bitsUsed is the number of bits used in the 64-bit boolean flags
-const bitsUsed = 42
+const bitsUsed = 41
// Static compile check that bitsUsed and maxArshalV1Flag are in sync.
const _ = uint64((1<<bitsUsed)-maxArshalV1Flag) + uint64(maxArshalV1Flag-(1<<bitsUsed))
}
prevIdx = f.id
}
- if fields.inlinedFallback != nil && !(mo.Flags.Get(jsonflags.DiscardUnknownMembers) && fields.inlinedFallback.unknown) {
+ if fields.inlinedFallback != nil {
var insertUnquotedName func([]byte) bool
if !mo.Flags.Get(jsonflags.AllowDuplicateNames) {
insertUnquotedName = func(name []byte) bool {
}
}
if f == nil {
- if uo.Flags.Get(jsonflags.RejectUnknownMembers) && (fields.inlinedFallback == nil || fields.inlinedFallback.unknown) {
+ if uo.Flags.Get(jsonflags.RejectUnknownMembers) && fields.inlinedFallback == nil {
err := newUnmarshalErrorAfter(dec, t, ErrUnknownName)
if !uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
return err
X *structInlinedL2 `json:",inline"`
StructEmbed1 `json:",inline"`
}
- structInlinedL2 struct{ A, B, C string }
- StructEmbed1 struct{ C, D, E string }
- StructEmbed2 struct{ E, F, G string }
- structUnknownTextValue struct {
- A int `json:",omitzero"`
- X jsontext.Value `json:",unknown"`
- B int `json:",omitzero"`
- }
+ structInlinedL2 struct{ A, B, C string }
+ StructEmbed1 struct{ C, D, E string }
+ StructEmbed2 struct{ E, F, G string }
structInlineTextValue struct {
A int `json:",omitzero"`
X jsontext.Value `json:",inline"`
},
in: structInlineMapNamedStringAny{X: map[namedString]any{"fizz": 3.14159}},
want: `{"fizz":"3.14159"}`,
- }, {
- name: jsontest.Name("Structs/InlinedFallback/DiscardUnknownMembers"),
- opts: []Options{DiscardUnknownMembers(true)},
- in: structInlineTextValue{
- A: 1,
- X: jsontext.Value(` { "fizz" : "buzz" } `),
- B: 2,
- },
- // NOTE: DiscardUnknownMembers has no effect since this is "inline".
- want: `{"A":1,"B":2,"fizz":"buzz"}`,
- }, {
- name: jsontest.Name("Structs/UnknownFallback/DiscardUnknownMembers"),
- opts: []Options{DiscardUnknownMembers(true)},
- in: structUnknownTextValue{
- A: 1,
- X: jsontext.Value(` { "fizz" : "buzz" } `),
- B: 2,
- },
- want: `{"A":1,"B":2}`,
- }, {
- name: jsontest.Name("Structs/UnknownFallback"),
- in: structUnknownTextValue{
- A: 1,
- X: jsontext.Value(` { "fizz" : "buzz" } `),
- B: 2,
- },
- want: `{"A":1,"B":2,"fizz":"buzz"}`,
}, {
name: jsontest.Name("Structs/DuplicateName/NoCaseInlineTextValue/Other"),
in: structNoCaseInlineTextValue{
opts: []Options{RejectUnknownMembers(true)},
inBuf: `{"A":1,"fizz":"buzz","B":2}`,
inVal: new(structInlineTextValue),
- // NOTE: DiscardUnknownMembers has no effect since this is "inline".
want: addr(structInlineTextValue{
A: 1,
X: jsontext.Value(`{"fizz":"buzz"}`),
B: 2,
}),
- }, {
- name: jsontest.Name("Structs/UnknownFallback/RejectUnknownMembers"),
- opts: []Options{RejectUnknownMembers(true)},
- inBuf: `{"A":1,"fizz":"buzz","B":2}`,
- inVal: new(structUnknownTextValue),
- want: addr(structUnknownTextValue{A: 1}),
- wantErr: EU(ErrUnknownName).withPos(`{"A":1,`, "/fizz").withType('"', T[structUnknownTextValue]()),
- }, {
- name: jsontest.Name("Structs/UnknownFallback"),
- inBuf: `{"A":1,"fizz":"buzz","B":2}`,
- inVal: new(structUnknownTextValue),
- want: addr(structUnknownTextValue{
- A: 1,
- X: jsontext.Value(`{"fizz":"buzz"}`),
- B: 2,
- }),
}, {
name: jsontest.Name("Structs/UnknownIgnored"),
opts: []Options{RejectUnknownMembers(false)},
// while many non-fallback fields may be specified. This option
// must not be specified with any other option (including the JSON name).
//
-// - unknown: The "unknown" option is a specialized variant
-// of the inlined fallback to indicate that this Go struct field
-// contains any number of unknown JSON object members. The field type must
-// be a [jsontext.Value], map[~string]T, or an unnamed pointer to such types.
-// If [DiscardUnknownMembers] is specified when marshaling,
-// the contents of this field are ignored.
-// If [RejectUnknownMembers] is specified when unmarshaling,
-// any unknown object members are rejected regardless of whether
-// an inlined fallback with the "unknown" option exists. This option
-// must not be specified with any other option (including the JSON name).
-//
// - format: The "format" option specifies a format flag
// used to specialize the formatting of the field value.
// The option is a key-value pair specified as "format:value" where
import (
"bytes"
- "errors"
"fmt"
"log"
"math"
// }
}
-// Due to version skew, the set of JSON object members known at compile-time
-// may differ from the set of members encountered at execution-time.
-// As such, it may be useful to have finer grain handling of unknown members.
-// This package supports preserving, rejecting, or discarding such members.
-func Example_unknownMembers() {
- const input = `{
- "Name": "Teal",
- "Value": "#008080",
- "WebSafe": false
- }`
- type Color struct {
- Name string
- Value string
-
- // Unknown is a Go struct field that holds unknown JSON object members.
- // It is marked as having this behavior with the "unknown" tag option.
- //
- // The type may be a jsontext.Value or map[string]T.
- Unknown jsontext.Value `json:",unknown"`
- }
-
- // By default, unknown members are stored in a Go field marked as "unknown"
- // or ignored if no such field exists.
- var color Color
- err := json.Unmarshal([]byte(input), &color)
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Unknown members:", string(color.Unknown))
-
- // Specifying RejectUnknownMembers causes Unmarshal
- // to reject the presence of any unknown members.
- err = json.Unmarshal([]byte(input), new(Color), json.RejectUnknownMembers(true))
- serr, ok := errors.AsType[*json.SemanticError](err)
- if ok && serr.Err == json.ErrUnknownName {
- fmt.Println("Unmarshal error:", serr.Err, strconv.Quote(serr.JSONPointer.LastToken()))
- }
-
- // By default, Marshal preserves unknown members stored in
- // a Go struct field marked as "unknown".
- b, err := json.Marshal(color)
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Output with unknown members: ", string(b))
-
- // Specifying DiscardUnknownMembers causes Marshal
- // to discard any unknown members.
- b, err = json.Marshal(color, json.DiscardUnknownMembers(true))
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Output without unknown members:", string(b))
-
- // Output:
- // Unknown members: {"WebSafe":false}
- // Unmarshal error: unknown object member name "WebSafe"
- // Output with unknown members: {"Name":"Teal","Value":"#008080","WebSafe":false}
- // Output without unknown members: {"Name":"Teal","Value":"#008080"}
-}
-
// The "format" tag option can be used to alter the formatting of certain types.
func Example_formatFlags() {
value := struct {
f.inline = true // implied by use of Go embedding without an explicit name
}
}
- if f.inline || f.unknown {
+ if f.inline {
// Handle an inlined field that serializes to/from
// zero or more JSON object members.
- switch f.fieldOptions {
- case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}:
- case fieldOptions{name: f.name, quotedName: f.quotedName, unknown: true}:
- case fieldOptions{name: f.name, quotedName: f.quotedName, inline: true, unknown: true}:
- serr = orErrorf(serr, t, "Go struct field %s cannot have both `inline` and `unknown` specified", sf.Name)
- f.inline = false // let `unknown` take precedence
- default:
- serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `inline` or `unknown` specified", sf.Name)
+ if f.fieldOptions != (fieldOptions{name: f.name, quotedName: f.quotedName, inline: true}) {
+ serr = orErrorf(serr, t, "Go struct field %s cannot have any options other than `inline` specified", sf.Name)
if f.hasName {
continue // invalid inlined field; treat as ignored
}
- f.fieldOptions = fieldOptions{name: f.name, quotedName: f.quotedName, inline: f.inline, unknown: f.unknown}
- if f.inline && f.unknown {
- f.inline = false // let `unknown` take precedence
- }
+ f.fieldOptions = fieldOptions{name: f.name, quotedName: f.quotedName, inline: f.inline}
}
// Reject any types with custom serialization otherwise
// Handle an inlined field that serializes to/from
// a finite number of JSON object members backed by a Go struct.
if tf.Kind() == reflect.Struct {
- if f.unknown {
- serr = orErrorf(serr, t, "inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value", sf.Name, tf)
- continue // invalid inlined field; treat as ignored
- }
if qe.visitChildren {
queue = append(queue, queueEntry{tf, f.index, !seen[tf]})
}
nameNeedEscape bool
casing int8 // either 0, caseIgnore, or caseStrict
inline bool
- unknown bool
omitzero bool
omitempty bool
string bool
}
case "inline":
out.inline = true
- case "unknown":
- out.unknown = true
case "omitzero":
out.omitzero = true
case "omitempty":
// This catches invalid mutants such as "omitEmpty" or "omit_empty".
normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "")
switch normOpt {
- case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format":
+ case "case", "inline", "omitzero", "omitempty", "string", "format":
err = cmp.Or(err, fmt.Errorf("Go struct field %s has invalid appearance of `%s` tag option; specify `%s` instead", sf.Name, opt, normOpt))
}
X jsontext.Value `json:",inline"`
} `json:",inline"`
X2 struct {
- X map[string]any `json:",unknown"`
+ X map[string]any `json:",inline"`
} `json:",inline"`
}{},
want: structFields{},
X jsontext.Value `json:",inline"`
} `json:",inline"`
X2 struct {
- X map[string]any `json:",unknown"`
+ X map[string]any `json:",inline"`
} `json:",inline"`
- X map[string]jsontext.Value `json:",unknown"`
+ X map[string]jsontext.Value `json:",inline"`
}{},
want: structFields{
- inlinedFallback: &structField{id: 0, index: []int{2}, typ: T[map[string]jsontext.Value](), fieldOptions: fieldOptions{name: "X", quotedName: `"X"`, unknown: true}},
+ inlinedFallback: &structField{id: 0, index: []int{2}, typ: T[map[string]jsontext.Value](), fieldOptions: fieldOptions{name: "X", quotedName: `"X"`, inline: true}},
},
}, {
name: jsontest.Name("InlinedFallback/InvalidImplicit"),
}{},
want: structFields{flattened: []structField{}},
wantErr: errors.New(`Go struct fields A and B conflict over JSON object name "same"`),
- }, {
- name: jsontest.Name("BothInlineAndUnknown"),
- in: struct {
- A struct{} `json:",inline,unknown"`
- }{},
- wantErr: errors.New("Go struct field A cannot have both `inline` and `unknown` specified"),
}, {
name: jsontest.Name("InlineWithOptions"),
in: struct {
A struct{} `json:",inline,omitempty"`
}{},
- wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"),
+ wantErr: errors.New("Go struct field A cannot have any options other than `inline` specified"),
}, {
name: jsontest.Name("UnknownWithOptions"),
in: struct {
inline: true,
},
}},
- wantErr: errors.New("Go struct field A cannot have any options other than `inline` or `unknown` specified"),
+ wantErr: errors.New("Go struct field A cannot have any options other than `inline` specified"),
}, {
name: jsontest.Name("InlineTextMarshaler"),
in: struct {
}}},
wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextAppender } must not implement marshal or unmarshal methods`),
}, {
- name: jsontest.Name("UnknownJSONMarshaler"),
+ name: jsontest.Name("InlineJSONMarshaler"),
in: struct {
- A struct{ Marshaler } `json:",unknown"`
+ A struct{ Marshaler } `json:",inline"`
}{},
+ want: structFields{flattened: []structField{{
+ index: []int{0, 0},
+ typ: reflect.TypeFor[Marshaler](),
+ fieldOptions: fieldOptions{
+ name: "Marshaler",
+ quotedName: `"Marshaler"`,
+ },
+ }}},
wantErr: errors.New(`inlined Go struct field A of type struct { json.Marshaler } must not implement marshal or unmarshal methods`),
}, {
name: jsontest.Name("InlineJSONMarshalerTo"),
}}},
wantErr: errors.New(`inlined Go struct field A of type struct { json.MarshalerTo } must not implement marshal or unmarshal methods`),
}, {
- name: jsontest.Name("UnknownTextUnmarshaler"),
+ name: jsontest.Name("InlineTextUnmarshaler"),
in: struct {
- A *struct{ encoding.TextUnmarshaler } `json:",unknown"`
+ A *struct{ encoding.TextUnmarshaler } `json:",inline"`
}{},
+ want: structFields{flattened: []structField{{
+ index: []int{0, 0},
+ typ: reflect.TypeFor[encoding.TextUnmarshaler](),
+ fieldOptions: fieldOptions{
+ name: "TextUnmarshaler",
+ quotedName: `"TextUnmarshaler"`,
+ },
+ }}},
wantErr: errors.New(`inlined Go struct field A of type struct { encoding.TextUnmarshaler } must not implement marshal or unmarshal methods`),
}, {
name: jsontest.Name("InlineJSONUnmarshaler"),
}}},
wantErr: errors.New(`inlined Go struct field A of type struct { json.Unmarshaler } must not implement marshal or unmarshal methods`),
}, {
- name: jsontest.Name("UnknownJSONUnmarshalerFrom"),
+ name: jsontest.Name("InlineJSONUnmarshalerFrom"),
in: struct {
- A struct{ UnmarshalerFrom } `json:",unknown"`
+ A struct{ UnmarshalerFrom } `json:",inline"`
}{},
+ want: structFields{flattened: []structField{{
+ index: []int{0, 0},
+ typ: reflect.TypeFor[UnmarshalerFrom](),
+ fieldOptions: fieldOptions{
+ name: "UnmarshalerFrom",
+ quotedName: `"UnmarshalerFrom"`,
+ },
+ }}},
wantErr: errors.New(`inlined Go struct field A of type struct { json.UnmarshalerFrom } must not implement marshal or unmarshal methods`),
- }, {
- name: jsontest.Name("UnknownStruct"),
- in: struct {
- A struct {
- X, Y, Z string
- } `json:",unknown"`
- }{},
- wantErr: errors.New("inlined Go struct field A of type struct { X string; Y string; Z string } with `unknown` tag must be a Go map of string key or a jsontext.Value"),
}, {
name: jsontest.Name("InlineUnsupported/MapIntKey"),
in: struct {
- A map[int]any `json:",unknown"`
+ A map[int]any `json:",inline"`
}{},
wantErr: errors.New(`inlined Go struct field A of type map[int]interface {} must be a Go struct, Go map of string key, or jsontext.Value`),
}, {
}, {
name: jsontest.Name("SuperfluousCommas"),
in: struct {
- V int `json:",,,,\"\",,inline,unknown,,,,"`
+ V int `json:",,,,\"\",,inline,,,,,"`
}{},
- wantOpts: fieldOptions{name: "V", quotedName: `"V"`, inline: true, unknown: true},
+ wantOpts: fieldOptions{name: "V", quotedName: `"V"`, inline: true},
wantErr: errors.New("Go struct field V has malformed `json` tag: invalid character ',' at start of option (expecting Unicode letter or single quote)"),
}, {
name: jsontest.Name("CaseAloneOption"),
FieldName int `json:",inline"`
}{},
wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, inline: true},
- }, {
- name: jsontest.Name("UnknownOption"),
- in: struct {
- FieldName int `json:",unknown"`
- }{},
- wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`, unknown: true},
}, {
name: jsontest.Name("OmitZeroOption"),
in: struct {
}, {
name: jsontest.Name("AllOptions"),
in: struct {
- FieldName int `json:",case:ignore,inline,unknown,omitzero,omitempty,string,format:format"`
+ FieldName int `json:",case:ignore,inline,omitzero,omitempty,string,format:format"`
}{},
wantOpts: fieldOptions{
name: "FieldName",
quotedName: `"FieldName"`,
casing: caseIgnore,
inline: true,
- unknown: true,
omitzero: true,
omitempty: true,
string: true,
}, {
name: jsontest.Name("AllOptionsQuoted"),
in: struct {
- FieldName int `json:",'case':'ignore','inline','unknown','omitzero','omitempty','string','format':'format'"`
+ FieldName int `json:",'case':'ignore','inline','omitzero','omitempty','string','format':'format'"`
}{},
wantOpts: fieldOptions{
name: "FieldName",
quotedName: `"FieldName"`,
casing: caseIgnore,
inline: true,
- unknown: true,
omitzero: true,
omitempty: true,
string: true,
}, {
name: jsontest.Name("AllOptionsSpaceSensitive"),
in: struct {
- FieldName int `json:", case:ignore , inline , unknown , omitzero , omitempty , string , format:format "`
+ FieldName int `json:", case:ignore , inline , omitzero , omitempty , string , format:format "`
}{},
wantOpts: fieldOptions{name: "FieldName", quotedName: `"FieldName"`},
wantErr: errors.New("Go struct field FieldName has malformed `json` tag: invalid character ' ' at start of option (expecting Unicode letter or single quote)"),
// - [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
}
}
-// 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.
+// when unmarshaling a JSON object.
//
// This only affects unmarshaling and is ignored when marshaling.
func RejectUnknownMembers(v bool) Options {