Skip to content

Commit

Permalink
Reject JSON numbers with StringifyNumbers when unmarshaling (#60)
Browse files Browse the repository at this point in the history
WARNING: This commit includes breaking changes.

Previously, if StringifyNumbers was specified,
we would permit unmarshaling of a JSON number into a Go number.
However, this is not how the v1 equivalent functionality operated.
In v1, once the `string` tag option was specified,
it would only unmarshal a JSON string containing a JSON number,
but would reject a JSON number.

It may be reasonable to provide a separate option that allows
the more flexible unmarshal behavior that this commit breaks,
but that can be a future change. For now, we are working towards
implementing v1 entirely in terms of v2.
  • Loading branch information
dsnet authored Nov 20, 2024
1 parent d2b1a22 commit e27260c
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 13 deletions.
9 changes: 9 additions & 0 deletions arshal_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ func makeIntArshaler(t reflect.Type) *arshaler {
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
fallthrough
case '0':
if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' {
break
}
var negOffset int
neg := len(val) > 0 && val[0] == '-'
if neg {
Expand Down Expand Up @@ -486,6 +489,9 @@ func makeUintArshaler(t reflect.Type) *arshaler {
val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
fallthrough
case '0':
if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' {
break
}
n, ok := jsonwire.ParseUint(val)
maxUint := uint64(1) << bits
overflow := n > maxUint-1
Expand Down Expand Up @@ -590,6 +596,9 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
}
fallthrough
case '0':
if uo.Flags.Get(jsonflags.StringifyNumbers) && k == '0' {
break
}
fv, ok := jsonwire.ParseFloat(val, bits)
if !ok && uo.Flags.Get(jsonflags.RejectFloatOverflow) {
return &SemanticError{action: "unmarshal", JSONKind: k, GoType: t, Err: strconv.ErrRange}
Expand Down
47 changes: 34 additions & 13 deletions arshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4685,6 +4685,13 @@ func TestUnmarshal(t *testing.T) {
inBuf: `"-6464"`,
inVal: new(int),
want: addr(int(-6464)),
}, {
name: jsontest.Name("Ints/Stringified/Invalid"),
opts: []Options{StringifyNumbers(true)},
inBuf: `-6464`,
inVal: new(int),
want: new(int),
wantErr: &SemanticError{action: "unmarshal", JSONKind: '0', GoType: intType},
}, {
name: jsontest.Name("Ints/Stringified/LeadingZero"),
opts: []Options{StringifyNumbers(true)},
Expand Down Expand Up @@ -4869,6 +4876,13 @@ func TestUnmarshal(t *testing.T) {
inBuf: `"6464"`,
inVal: new(uint),
want: addr(uint(6464)),
}, {
name: jsontest.Name("Uints/Stringified/Invalid"),
opts: []Options{StringifyNumbers(true)},
inBuf: `6464`,
inVal: new(uint),
want: new(uint),
wantErr: &SemanticError{action: "unmarshal", JSONKind: '0', GoType: uintType},
}, {
name: jsontest.Name("Uints/Stringified/LeadingZero"),
opts: []Options{StringifyNumbers(true)},
Expand Down Expand Up @@ -5042,6 +5056,13 @@ func TestUnmarshal(t *testing.T) {
inBuf: `"64.64"`,
inVal: new(float64),
want: addr(float64(64.64)),
}, {
name: jsontest.Name("Floats/Stringified/Invalid"),
opts: []Options{StringifyNumbers(true)},
inBuf: `64.64`,
inVal: new(float64),
want: new(float64),
wantErr: &SemanticError{action: "unmarshal", JSONKind: '0', GoType: float64Type},
}, {
name: jsontest.Name("Floats/Escaped"),
opts: []Options{StringifyNumbers(true)},
Expand Down Expand Up @@ -5488,33 +5509,33 @@ func TestUnmarshal(t *testing.T) {
"Bool": true,
"String": "hello",
"Bytes": "AQID",
"Int": -64,
"Uint": 64,
"Float": 3.14159,
"Int": "-64",
"Uint": "64",
"Float": "3.14159",
"Map": {"key": "value"},
"StructScalars": {
"Bool": true,
"String": "hello",
"Bytes": "AQID",
"Int": -64,
"Uint": 64,
"Float": 3.14159
"Int": "-64",
"Uint": "64",
"Float": "3.14159"
},
"StructMaps": {
"MapBool": {"": true},
"MapString": {"": "hello"},
"MapBytes": {"": "AQID"},
"MapInt": {"": -64},
"MapUint": {"": 64},
"MapFloat": {"": 3.14159}
"MapInt": {"": "-64"},
"MapUint": {"": "64"},
"MapFloat": {"": "3.14159"}
},
"StructSlices": {
"SliceBool": [true],
"SliceString": ["hello"],
"SliceBytes": ["AQID"],
"SliceInt": [-64],
"SliceUint": [64],
"SliceFloat": [3.14159]
"SliceInt": ["-64"],
"SliceUint": ["64"],
"SliceFloat": ["3.14159"]
},
"Slice": ["fizz","buzz"],
"Array": ["goodbye"],
Expand Down Expand Up @@ -6318,7 +6339,7 @@ func TestUnmarshal(t *testing.T) {
}, {
name: jsontest.Name("Structs/InlinedFallback/MapStringInt/StringifiedNumbers"),
opts: []Options{StringifyNumbers(true)},
inBuf: `{"zero": 0, "one": "1", "two": 2}`,
inBuf: `{"zero": "0", "one": "1", "two": "2"}`,
inVal: new(structInlineMapStringInt),
want: addr(structInlineMapStringInt{
X: map[string]int{"zero": 0, "one": 1, "two": 2},
Expand Down

0 comments on commit e27260c

Please sign in to comment.