Go version
go1.27.0
What did you do
Unmarshal a number that overflows the destination float, then inspect the destination.
package main
import (
"encoding/json"
"fmt"
"math"
)
func main() {
var f float64
err := json.Unmarshal([]byte("1e1000"), &f)
fmt.Printf("err=%v value=%v isInf=%v\n", err != nil, f, math.IsInf(f, 0))
var v any
json.Unmarshal([]byte(`{"x":1e1000}`), &v)
out, err := json.Marshal(v)
fmt.Printf("re-marshal: %q err=%v\n", out, err)
}
What did you see happen
go1.27.0:
err=true value=+Inf isInf=true
re-marshal: "" err=json: unsupported value: +Inf
go1.27.0 with GOEXPERIMENT=nojsonv2:
err=true value=0 isInf=false
re-marshal: "{\"x\":null}" err=<nil>
Both report the overflow. They differ in what they leave behind: the new implementation stores the clamped ±Inf that strconv.ParseFloat returns alongside ErrRange, the previous one left the destination alone.
What did you expect to see
0, as before, and a value that encoding/json can still marshal.
Unmarshal documents that it decodes as much as it can when it encounters an error, so the residue is observable rather than undefined, and encoding/json/v2 documents this specific value as unreachable (arshal.go):
It fails if it overflows the representation of the Go float type. Since JSON lacks a native representation for a NaN or ±Inf, such values cannot be the result of decoding.
The practical consequence is the second half of the reproducer: Marshal rejects ±Inf, so decoding, logging the error, and re-encoding the partially populated value used to succeed and now fails.
Worth noting before this is treated as a plain oversight: v2/arshal_test.go asserts the current behaviour on purpose (Floats/Float32/Overflow and Floats/Float64/Overflow expect math.Inf(-1)). So the code, its test, and the package documentation do not agree, and I am not sure which of the three is meant to give.
Found while comparing pre-1.27 and 1.27 encoding/json behaviour with a differential harness (https://github.com/IBlackVoid/jsonparity).
Go version
go1.27.0
What did you do
Unmarshal a number that overflows the destination float, then inspect the destination.
What did you see happen
go1.27.0:
go1.27.0 with
GOEXPERIMENT=nojsonv2:Both report the overflow. They differ in what they leave behind: the new implementation stores the clamped
±Infthatstrconv.ParseFloatreturns alongsideErrRange, the previous one left the destination alone.What did you expect to see
0, as before, and a value thatencoding/jsoncan still marshal.Unmarshaldocuments that it decodes as much as it can when it encounters an error, so the residue is observable rather than undefined, andencoding/json/v2documents this specific value as unreachable (arshal.go):The practical consequence is the second half of the reproducer:
Marshalrejects±Inf, so decoding, logging the error, and re-encoding the partially populated value used to succeed and now fails.Worth noting before this is treated as a plain oversight:
v2/arshal_test.goasserts the current behaviour on purpose (Floats/Float32/OverflowandFloats/Float64/Overflowexpectmath.Inf(-1)). So the code, its test, and the package documentation do not agree, and I am not sure which of the three is meant to give.Found while comparing pre-1.27 and 1.27
encoding/jsonbehaviour with a differential harness (https://github.com/IBlackVoid/jsonparity).