mirror of
https://github.com/golang/go.git
synced 2025-10-19 11:03:18 +00:00
encoding/json: fix regression in quoted numbers under goexperiment.jsonv2
The legacy parsing of quoted numbers in v1 was according to the Go grammar for a number, rather than the JSON grammar for a number. The former is a superset of the latter. This is a historical mistake, but usages exist that depend on it. We already have branches for StringifyWithLegacySemantics to handle quoted nulls, so we can expand it to handle this. Fixes #75619 Change-Id: Ic07802539b7cbe0e1f53bd0f7e9bb344a8447203 Reviewed-on: https://go-review.googlesource.com/c/go/+/709615 Reviewed-by: Damien Neil <dneil@google.com> Auto-Submit: Joseph Tsai <joetsai@digital-static.net> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
48bb7a6114
commit
b497a29d25
4 changed files with 157 additions and 10 deletions
|
@ -1237,6 +1237,62 @@ var unmarshalTests = []struct {
|
|||
out: (chan int)(nil),
|
||||
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[chan int](), Offset: 1},
|
||||
},
|
||||
|
||||
// #75619
|
||||
{
|
||||
CaseName: Name("QuotedInt/GoSyntax"),
|
||||
in: `{"X": "-0000123"}`,
|
||||
ptr: new(struct {
|
||||
X int64 `json:",string"`
|
||||
}),
|
||||
out: struct {
|
||||
X int64 `json:",string"`
|
||||
}{-123},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedInt/Invalid"),
|
||||
in: `{"X": "123 "}`,
|
||||
ptr: new(struct {
|
||||
X int64 `json:",string"`
|
||||
}),
|
||||
err: &UnmarshalTypeError{Value: "number 123 ", Type: reflect.TypeFor[int64](), Field: "X", Offset: int64(len(`{"X": "123 "`))},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedUint/GoSyntax"),
|
||||
in: `{"X": "0000123"}`,
|
||||
ptr: new(struct {
|
||||
X uint64 `json:",string"`
|
||||
}),
|
||||
out: struct {
|
||||
X uint64 `json:",string"`
|
||||
}{123},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedUint/Invalid"),
|
||||
in: `{"X": "0x123"}`,
|
||||
ptr: new(struct {
|
||||
X uint64 `json:",string"`
|
||||
}),
|
||||
err: &UnmarshalTypeError{Value: "number 0x123", Type: reflect.TypeFor[uint64](), Field: "X", Offset: int64(len(`{"X": "0x123"`))},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedFloat/GoSyntax"),
|
||||
in: `{"X": "0x1_4p-2"}`,
|
||||
ptr: new(struct {
|
||||
X float64 `json:",string"`
|
||||
}),
|
||||
out: struct {
|
||||
X float64 `json:",string"`
|
||||
}{0x1_4p-2},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedFloat/Invalid"),
|
||||
in: `{"X": "1.5e1_"}`,
|
||||
ptr: new(struct {
|
||||
X float64 `json:",string"`
|
||||
}),
|
||||
err: &UnmarshalTypeError{Value: "number 1.5e1_", Type: reflect.TypeFor[float64](), Field: "X", Offset: int64(len(`{"X": "1.5e1_"`))},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
|
|
|
@ -474,10 +474,21 @@ 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)
|
||||
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
|
||||
// For historical reasons, v1 parsed a quoted number
|
||||
// according to the Go syntax and permitted a quoted null.
|
||||
// See https://go.dev/issue/75619
|
||||
n, err := strconv.ParseInt(string(val), 10, bits)
|
||||
if err != nil {
|
||||
if string(val) == "null" {
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
va.SetInt(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
|
||||
}
|
||||
va.SetInt(n)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
|
@ -561,10 +572,21 @@ 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)
|
||||
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
|
||||
// For historical reasons, v1 parsed a quoted number
|
||||
// according to the Go syntax and permitted a quoted null.
|
||||
// See https://go.dev/issue/75619
|
||||
n, err := strconv.ParseUint(string(val), 10, bits)
|
||||
if err != nil {
|
||||
if string(val) == "null" {
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
va.SetUint(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
|
||||
}
|
||||
va.SetUint(n)
|
||||
return nil
|
||||
}
|
||||
fallthrough
|
||||
|
@ -671,10 +693,21 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
|
|||
if !stringify {
|
||||
break
|
||||
}
|
||||
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) && string(val) == "null" {
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
va.SetFloat(0)
|
||||
if uo.Flags.Get(jsonflags.StringifyWithLegacySemantics) {
|
||||
// For historical reasons, v1 parsed a quoted number
|
||||
// according to the Go syntax and permitted a quoted null.
|
||||
// See https://go.dev/issue/75619
|
||||
n, err := strconv.ParseFloat(string(val), bits)
|
||||
if err != nil {
|
||||
if string(val) == "null" {
|
||||
if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
|
||||
va.SetFloat(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
|
||||
}
|
||||
va.SetFloat(n)
|
||||
return nil
|
||||
}
|
||||
if n, err := jsonwire.ConsumeNumber(val); n != len(val) || err != nil {
|
||||
|
|
|
@ -1243,6 +1243,62 @@ var unmarshalTests = []struct {
|
|||
out: (chan int)(nil),
|
||||
err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeFor[chan int]()},
|
||||
},
|
||||
|
||||
// #75619
|
||||
{
|
||||
CaseName: Name("QuotedInt/GoSyntax"),
|
||||
in: `{"X": "-0000123"}`,
|
||||
ptr: new(struct {
|
||||
X int64 `json:",string"`
|
||||
}),
|
||||
out: struct {
|
||||
X int64 `json:",string"`
|
||||
}{-123},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedInt/Invalid"),
|
||||
in: `{"X": "123 "}`,
|
||||
ptr: new(struct {
|
||||
X int64 `json:",string"`
|
||||
}),
|
||||
err: &UnmarshalTypeError{Value: "number 123 ", Type: reflect.TypeFor[int64](), Field: "X", Offset: int64(len(`{"X": `))},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedUint/GoSyntax"),
|
||||
in: `{"X": "0000123"}`,
|
||||
ptr: new(struct {
|
||||
X uint64 `json:",string"`
|
||||
}),
|
||||
out: struct {
|
||||
X uint64 `json:",string"`
|
||||
}{123},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedUint/Invalid"),
|
||||
in: `{"X": "0x123"}`,
|
||||
ptr: new(struct {
|
||||
X uint64 `json:",string"`
|
||||
}),
|
||||
err: &UnmarshalTypeError{Value: "number 0x123", Type: reflect.TypeFor[uint64](), Field: "X", Offset: int64(len(`{"X": `))},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedFloat/GoSyntax"),
|
||||
in: `{"X": "0x1_4p-2"}`,
|
||||
ptr: new(struct {
|
||||
X float64 `json:",string"`
|
||||
}),
|
||||
out: struct {
|
||||
X float64 `json:",string"`
|
||||
}{0x1_4p-2},
|
||||
},
|
||||
{
|
||||
CaseName: Name("QuotedFloat/Invalid"),
|
||||
in: `{"X": "1.5e1_"}`,
|
||||
ptr: new(struct {
|
||||
X float64 `json:",string"`
|
||||
}),
|
||||
err: &UnmarshalTypeError{Value: "number 1.5e1_", Type: reflect.TypeFor[float64](), Field: "X", Offset: int64(len(`{"X": `))},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
|
|
|
@ -506,7 +506,9 @@ func ReportErrorsWithLegacySemantics(v bool) Options {
|
|||
// 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 string containing their usual JSON representation or
|
||||
// Go number representation for that numeric kind.
|
||||
// Note that the Go number grammar is a superset of the JSON number grammar.
|
||||
// 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.
|
||||
//
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue