Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement legacy support for quoted nulls #86

Merged
merged 1 commit into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion arshal_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,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 @@ -245,6 +251,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 @@ -455,6 +467,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 @@ -535,6 +553,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 @@ -638,6 +662,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 @@ -1575,7 +1605,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 correctly handle escaped nulls
// (e.g., "\u006e\u0075\u006c\u006c"), but is good enough
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be really, really surprised if anyone did this...

// 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,14 +4,12 @@ TestUnmarshal/#107
TestUnmarshal/#109
TestUnmarshal/#111
TestUnmarshal/#113
TestUnmarshal/#138
TestEncodeRenamedByteSlice
TestNilMarshal
TestNilMarshal/#08
TestNilMarshal/#11
TestNilMarshalerTextMapKey
TestStringOption
TestStringOption/Unmarshal/Null/v1
TestStringOption/Unmarshal/Deep/v1
TestPointerReceiver
TestPointerReceiver/Marshal/v1
9 changes: 7 additions & 2 deletions v1/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,16 @@ func ReportLegacyErrorValues(v bool) Options {
// or numeric kinds within a composite data type
// (e.g., array, slice, struct, map, or interface).
//
// When marshaling, such Go values are serialized as their usual
// JSON representation, but quoted within a JSON string.
// When unmarshaling, such Go values must be deserialized from
// a JSON string containing their usual JSON representation.
// A JSON null quoted in a JSON string is a valid substitute for JSON null
// while unmarshaling into a Go value 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
Loading