Skip to content

Commit

Permalink
Implement legacy support for quoted nulls
Browse files Browse the repository at this point in the history
When the `string` option is used, a JSON null quoted within a JSON string
is a valid substitute for a JSON null.
  • Loading branch information
dsnet committed Dec 23, 2024
1 parent 5815faa commit 09400ea
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 5 deletions.
47 changes: 46 additions & 1 deletion arshal_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ func makeBoolArshaler(t reflect.Type) *arshaler {
case "false":
va.SetBool(false)
default:
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && tok.String() == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetBool(false)
}
return nil
}
return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax)
}
return nil
Expand Down Expand Up @@ -238,6 +244,12 @@ func makeStringArshaler(t reflect.Type) *arshaler {
if err != nil {
return newUnmarshalErrorAfter(dec, t, err)
}
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetString("")
}
return nil
}
}
if xd.StringCache == nil {
xd.StringCache = new(stringCache)
Expand Down Expand Up @@ -444,6 +456,12 @@ func makeIntArshaler(t reflect.Type) *arshaler {
break
}
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetInt(0)
}
return nil
}
fallthrough
case '0':
if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' {
Expand Down Expand Up @@ -522,6 +540,12 @@ func makeUintArshaler(t reflect.Type) *arshaler {
break
}
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetUint(0)
}
return nil
}
fallthrough
case '0':
if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' {
Expand Down Expand Up @@ -623,6 +647,12 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
if !uo.Flags.Get(jsonflags.StringifyNumbers) {
break
}
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
va.SetFloat(0)
}
return nil
}
if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil {
return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrSyntax)
}
Expand Down Expand Up @@ -1553,7 +1583,22 @@ func makePointerArshaler(t reflect.Type) *arshaler {
va.Set(reflect.New(t.Elem()))
}
v := addressableValue{va.Elem()} // dereferenced pointer is always addressable
return unmarshal(dec, v, uo)
if err := unmarshal(dec, v, uo); err != nil {
return err
}
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) &&
(uo.Flags.Get(jsonflags.StringifyNumbers) || uo.Flags.Get(jsonflags.StringifyBoolsAndStrings)) {
// A JSON null quoted within a JSON string should take effect
// within the pointer value, rather than the indirect value.
//
// TODO: This does not handle correctly handle escaped nulls
// (e.g., "\u006e\u0075\u006c\u006c"), but is good enough
// for such an esoteric use case of the `string` option.
if string(export.Decoder(dec).PreviousTokenOrValue()) == `"null"` {
va.SetZero()
}
}
return nil
}
return &fncs
}
Expand Down
2 changes: 0 additions & 2 deletions v1/failing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ TestUnmarshal/#107
TestUnmarshal/#109
TestUnmarshal/#111
TestUnmarshal/#113
TestUnmarshal/#138
TestNullString
TestInterfaceSet
TestInterfaceSet/#01
Expand All @@ -25,7 +24,6 @@ TestEncoderSetEscapeHTML
TestEncoderSetEscapeHTML/stringOption
TestRawMessage
TestStringOption
TestStringOption/Unmarshal/Null/v1
TestStringOption/Unmarshal/Deep/v1
TestPointerReceiver
TestPointerReceiver/Marshal/v1
Expand Down
4 changes: 2 additions & 2 deletions v1/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ func ReportLegacyErrorValues(v bool) Options {
// such a kind. Specifically, `string` will not stringify bool, string,
// or numeric kinds within a composite data type
// (e.g., array, slice, struct, map, or interface).
// A JSON null quoted in a JSON string is a valid substitute for JSON null
// while unmarshaling into fields that `string` takes effect on.
//
// This affects either marshaling or unmarshaling.
// The v1 default is true.
func StringifyWithLegacySemantics(v bool) Options {
// TODO: In v1, we would permit unmarshaling "null" (i.e., a quoted null)
// as if it were just null. We do not support this in v2. Should we?
if v {
return jsonflags.StringifyWithLegacySemantics | 1
} else {
Expand Down

0 comments on commit 09400ea

Please sign in to comment.