diff --git a/arshal_default.go b/arshal_default.go index c6c5c96..b0f5bf8 100644 --- a/arshal_default.go +++ b/arshal_default.go @@ -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 @@ -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) @@ -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' { @@ -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' { @@ -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) } @@ -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 } diff --git a/v1/failing.txt b/v1/failing.txt index 623a34f..16f3d83 100644 --- a/v1/failing.txt +++ b/v1/failing.txt @@ -4,7 +4,6 @@ TestUnmarshal/#107 TestUnmarshal/#109 TestUnmarshal/#111 TestUnmarshal/#113 -TestUnmarshal/#138 TestNullString TestInterfaceSet TestInterfaceSet/#01 @@ -25,7 +24,6 @@ TestEncoderSetEscapeHTML TestEncoderSetEscapeHTML/stringOption TestRawMessage TestStringOption -TestStringOption/Unmarshal/Null/v1 TestStringOption/Unmarshal/Deep/v1 TestPointerReceiver TestPointerReceiver/Marshal/v1 diff --git a/v1/options.go b/v1/options.go index a626723..3d3178e 100644 --- a/v1/options.go +++ b/v1/options.go @@ -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 {