Skip to content

fix: preserve out-of-float64-range numbers when decoding into json.Number (#555) - #599

Open
hdimer wants to merge 1 commit into
goccy:masterfrom
hdimer:fix/json-number-out-of-float64-range
Open

fix: preserve out-of-float64-range numbers when decoding into json.Number (#555)#599
hdimer wants to merge 1 commit into
goccy:masterfrom
hdimer:fix/json-number-out-of-float64-range

Conversation

@hdimer

@hdimer hdimer commented Aug 1, 2026

Copy link
Copy Markdown

Problem

numberDecoder (the decoder used for json.Number fields and Decoder.UseNumber()) validated every literal with strconv.ParseFloat and rejected any error it returned. Because json.Number keeps the literal verbatim, a number whose magnitude overflows float64 is still a perfectly valid json.Number, and encoding/json accepts it. go-json instead rejected it with strconv.ParseFloat: ... value out of range.

var n json.Number
err := json.Unmarshal([]byte("1e999"), &n)
// encoding/json: err == nil, n == "1e999"
// go-json:       err = "value out of range"   <-- bug

Same for a 400-digit integer, 1.7976931348623157e400, etc. This is a silent compatibility break: the caller asked for the raw literal and got an error instead. Reported in #555 (originating from an OpenTelemetry payload with an oversized number).

Fix

Ignore ParseFloat's ErrRange (the literal is syntactically valid, just out of float64 range) and keep rejecting all other errors. Both call sites (Decode and DecodeStream) go through a shared validateNumber helper. numberDecoder only ever writes a json.Number, so float64/int decoding (which should still reject overflow) is untouched.

Tests

Test_Decoder_Number_OutOfFloat64Range covers the Unmarshal, streaming Decoder, and json.Number struct-field paths. It asserts overflow/underflow literals are preserved verbatim and that malformed numbers (1e2e3, 1e+-2, 1.2.3) are still rejected. Verified byte-for-byte against encoding/json; full suite green.

One note

The check still leans on ParseFloat, so two oddly-formatted overflow literals (1.e999, 01e999) are now accepted where stdlib rejects them. That is consistent with the leading-zero / trailing-dot leniency already tolerated for their in-range twins (1.e1, 01, commented out in invalidTests in number_test.go). Making those strict would mean replacing ParseFloat with a full JSON-number grammar check and dropping that intentional leniency, so I kept this change scoped to the reported bug.


Used AI assistance on this; I reviewed and tested it.

…mber

numberDecoder validated every literal with strconv.ParseFloat and rejected
any error, so a number whose magnitude overflows float64 (e.g. 1e999, a
400-digit integer) was rejected with "value out of range" when decoding into
a json.Number field or via Decoder.UseNumber(). json.Number keeps the literal
verbatim and encoding/json accepts these, so this was a silent divergence.

Ignore ParseFloat's ErrRange (the literal is still a syntactically valid
number) while still rejecting genuine syntax errors. Scoped to json.Number;
float64/int overflow handling is unchanged.

Fixes goccy#555
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant