mirror of
https://github.com/golang/go.git
synced 2026-06-27 19:30:52 +00:00
encoding/json/jsontext: report errors for numeric Token accessors
A JSON number may not cleanly correspond with a Go int64, uint64, or float64. For example: * 1e1000 overflows any of the Go number types. * -123 is a syntactically invalid uint64 * 123.456 or 1e13 is also syntactically invalid for a uint64 or int64 Previously, the Int, Uint, and Float accessors would report the closest representable value and swallow any conversion errors. This change makes it such that a sensible value is still returned, but that an error is also report in such edge cases. As a minor change, when a JSON number overflows a Go float, we use +Inf or -Inf instead of +MaxFloat or -MaxFloat. As another minor change, the v1 compatibility implementation of Decoder.Token now reports an error when the JSON number overflows a float64, just like how v1 behaves today. Fixes #77666 Change-Id: Ibd22d68b865319db302dc284170fef2e4597622b Reviewed-on: https://go-review.googlesource.com/c/go/+/772360 Reviewed-by: Michael Pratt <mpratt@google.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Daniel Martí <mvdan@mvdan.cc> LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
f1bc06b98d
commit
464dc3f344
11 changed files with 281 additions and 224 deletions
|
|
@ -604,26 +604,3 @@ func ParseUint(b []byte) (v uint64, ok bool) {
|
|||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
// ParseFloat parses a floating point number according to the Go float grammar.
|
||||
// Note that the JSON number grammar is a strict subset.
|
||||
//
|
||||
// If the number overflows the finite representation of a float,
|
||||
// then we return MaxFloat since any finite value will always be infinitely
|
||||
// more accurate at representing another finite value than an infinite value.
|
||||
func ParseFloat(b []byte, bits int) (v float64, ok bool) {
|
||||
fv, err := strconv.ParseFloat(string(b), bits)
|
||||
if math.IsInf(fv, 0) {
|
||||
switch {
|
||||
case bits == 32 && math.IsInf(fv, +1):
|
||||
fv = +math.MaxFloat32
|
||||
case bits == 64 && math.IsInf(fv, +1):
|
||||
fv = +math.MaxFloat64
|
||||
case bits == 32 && math.IsInf(fv, -1):
|
||||
fv = -math.MaxFloat32
|
||||
case bits == 64 && math.IsInf(fv, -1):
|
||||
fv = -math.MaxFloat64
|
||||
}
|
||||
}
|
||||
return fv, err == nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -397,47 +397,3 @@ func TestParseUint(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseFloat(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
want32 float64
|
||||
want64 float64
|
||||
wantOk bool
|
||||
}{
|
||||
{"0", 0, 0, true},
|
||||
{"-1", -1, -1, true},
|
||||
{"1", 1, 1, true},
|
||||
|
||||
{"-16777215", -16777215, -16777215, true}, // -(1<<24 - 1)
|
||||
{"16777215", 16777215, 16777215, true}, // +(1<<24 - 1)
|
||||
{"-16777216", -16777216, -16777216, true}, // -(1<<24)
|
||||
{"16777216", 16777216, 16777216, true}, // +(1<<24)
|
||||
{"-16777217", -16777216, -16777217, true}, // -(1<<24 + 1)
|
||||
{"16777217", 16777216, 16777217, true}, // +(1<<24 + 1)
|
||||
|
||||
{"-9007199254740991", -9007199254740992, -9007199254740991, true}, // -(1<<53 - 1)
|
||||
{"9007199254740991", 9007199254740992, 9007199254740991, true}, // +(1<<53 - 1)
|
||||
{"-9007199254740992", -9007199254740992, -9007199254740992, true}, // -(1<<53)
|
||||
{"9007199254740992", 9007199254740992, 9007199254740992, true}, // +(1<<53)
|
||||
{"-9007199254740993", -9007199254740992, -9007199254740992, true}, // -(1<<53 + 1)
|
||||
{"9007199254740993", 9007199254740992, 9007199254740992, true}, // +(1<<53 + 1)
|
||||
|
||||
{"-1e1000", -math.MaxFloat32, -math.MaxFloat64, false},
|
||||
{"1e1000", +math.MaxFloat32, +math.MaxFloat64, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
got32, gotOk32 := ParseFloat([]byte(tt.in), 32)
|
||||
if got32 != tt.want32 || gotOk32 != tt.wantOk {
|
||||
t.Errorf("ParseFloat(%q, 32) = (%v, %v), want (%v, %v)", tt.in, got32, gotOk32, tt.want32, tt.wantOk)
|
||||
}
|
||||
|
||||
got64, gotOk64 := ParseFloat([]byte(tt.in), 64)
|
||||
if got64 != tt.want64 || gotOk64 != tt.wantOk {
|
||||
t.Errorf("ParseFloat(%q, 64) = (%v, %v), want (%v, %v)", tt.in, got64, gotOk64, tt.want64, tt.wantOk)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error)
|
|||
// Float32 returns the floating-point value for a JSON number
|
||||
// parsed according to 32 bits of precision.
|
||||
//
|
||||
// If the JSON number is outside the representable range of a float32,
|
||||
// it returns +Inf or -Inf along with an error
|
||||
// that matches [strconv.ErrRange] according to [errors.Is].
|
||||
//
|
||||
// It returns a NaN, +Inf, or -Inf value for any JSON string
|
||||
// with the values "NaN", "Infinity", or "-Infinity".
|
||||
//
|
||||
// It panics if the token kind is not a JSON number
|
||||
// or a JSON string with the aforementioned values.
|
||||
//
|
||||
// Note that most JSON libraries and standards assume that JSON numbers
|
||||
// are 64-bit floating-point numbers.
|
||||
// This method should only be used if the caller knows
|
||||
|
|
@ -339,46 +349,57 @@ func (t Token) appendNumber(dst []byte, flags *jsonflags.Flags) ([]byte, error)
|
|||
// formatted only to 32 bits of precision (such as being encoded
|
||||
// using the [Float32] constructor). For all other situations,
|
||||
// prefer using the [Token.Float] accessor instead.
|
||||
//
|
||||
// It returns a NaN, +Inf, or -Inf value for any JSON string
|
||||
// with the values "NaN", "Infinity", or "-Infinity".
|
||||
// It panics for all other cases.
|
||||
func (t Token) Float32() float32 {
|
||||
return float32(t.float(32))
|
||||
func (t Token) Float32() (float32, error) {
|
||||
f, err := t.float(32)
|
||||
return float32(f), err
|
||||
}
|
||||
|
||||
// Float returns the floating-point value for a JSON number
|
||||
// parsed according to 64 bits of precision.
|
||||
//
|
||||
// If the JSON number is outside the representable range of a float64,
|
||||
// it returns +Inf or -Inf along with an error
|
||||
// that matches [strconv.ErrRange] according to [errors.Is].
|
||||
//
|
||||
// It returns a NaN, +Inf, or -Inf value for any JSON string
|
||||
// with the values "NaN", "Infinity", or "-Infinity".
|
||||
// It panics for all other cases.
|
||||
func (t Token) Float() float64 {
|
||||
return float64(t.float(64))
|
||||
//
|
||||
// It panics if the token kind is not a JSON number
|
||||
// or a JSON string with the aforementioned values.
|
||||
func (t Token) Float() (float64, error) {
|
||||
f, err := t.float(64)
|
||||
return float64(f), err
|
||||
}
|
||||
|
||||
func (t Token) float(bits int) float64 {
|
||||
func (t Token) float(bits int) (float64, error) {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw number value.
|
||||
// Handle raw JSON number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
buf := raw.previousBuffer()
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
fv, _ := jsonwire.ParseFloat(buf, bits)
|
||||
return fv
|
||||
fv, err := strconv.ParseFloat(string(buf), bits)
|
||||
if err != nil {
|
||||
err = &SyntacticError{Err: errors.Unwrap(err)} // only ever ErrRange
|
||||
}
|
||||
return fv, err
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact number value.
|
||||
// Handle typed Go number value.
|
||||
switch t.str[0] {
|
||||
case 'F':
|
||||
return float64(math.Float32frombits(uint32(t.num)))
|
||||
return float64(math.Float32frombits(uint32(t.num))), nil
|
||||
case 'f':
|
||||
return float64(math.Float64frombits(uint64(t.num)))
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if bits == 32 && !math.IsInf(f64, 0) && math.IsInf(float64(float32(f64)), 0) {
|
||||
return f64, &SyntacticError{Err: strconv.ErrRange}
|
||||
}
|
||||
return f64, nil
|
||||
case 'i':
|
||||
return float64(int64(t.num))
|
||||
return float64(int64(t.num)), nil // NOTE: This may lead to loss of precision.
|
||||
case 'u':
|
||||
return float64(uint64(t.num))
|
||||
return float64(uint64(t.num)), nil // NOTE: This may lead to loss of precision.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,128 +407,181 @@ func (t Token) float(bits int) float64 {
|
|||
if t.Kind() == '"' {
|
||||
switch t.String() {
|
||||
case "NaN":
|
||||
return math.NaN()
|
||||
return math.NaN(), nil
|
||||
case "Infinity":
|
||||
return math.Inf(+1)
|
||||
return math.Inf(+1), nil
|
||||
case "-Infinity":
|
||||
return math.Inf(-1)
|
||||
return math.Inf(-1), nil
|
||||
}
|
||||
// TODO: Should this be a error instead of a panic?
|
||||
// We can safely switch from a panic to an error in the future.
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
// Int returns the signed integer value for a JSON number.
|
||||
//
|
||||
// It reports an error that matches [strconv.ErrSyntax] according to [errors.Is]
|
||||
// if the JSON number does not match the restricted grammar of just a signed integer.
|
||||
// It reports an error that matches [strconv.ErrRange] according to [errors.Is]
|
||||
// if the JSON number is a signed integer, but outside the range of an int64.
|
||||
// Even if an error is reported, a reasonable value is still returned.
|
||||
// The fractional component of any number is ignored (truncation toward zero).
|
||||
// Any number beyond the representation of an int64 will be saturated
|
||||
// to the closest representable value.
|
||||
//
|
||||
// It panics if the token kind is not a JSON number.
|
||||
func (t Token) Int() int64 {
|
||||
func (t Token) Int() (int64, error) {
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw integer value.
|
||||
// Handle raw JSON number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
neg := false
|
||||
buf := raw.previousBuffer()
|
||||
if len(buf) > 0 && buf[0] == '-' {
|
||||
neg, buf = true, buf[1:]
|
||||
}
|
||||
if numAbs, ok := jsonwire.ParseUint(buf); ok {
|
||||
if neg {
|
||||
if numAbs > -minInt64 {
|
||||
return minInt64
|
||||
}
|
||||
return -1 * int64(numAbs)
|
||||
} else {
|
||||
if numAbs > +maxInt64 {
|
||||
return maxInt64
|
||||
}
|
||||
return +1 * int64(numAbs)
|
||||
// Prospectively parse a negative integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf[len("-"):]); {
|
||||
case abs > -minInt64:
|
||||
return minInt64, &SyntacticError{Err: strconv.ErrRange}
|
||||
case ok:
|
||||
return -1 * int64(abs), nil
|
||||
}
|
||||
} else {
|
||||
// Prospectively parse a non-negative integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf); {
|
||||
case abs > +maxInt64:
|
||||
return maxInt64, &SyntacticError{Err: strconv.ErrRange}
|
||||
case ok:
|
||||
return +1 * int64(abs), nil
|
||||
}
|
||||
}
|
||||
// This is not a signed integer, which implies ErrSyntax.
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
f64, _ := strconv.ParseFloat(string(buf), 64)
|
||||
return f64toi64(f64), &SyntacticError{Err: strconv.ErrSyntax}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact integer value.
|
||||
// Handle typed Go number value.
|
||||
switch t.str[0] {
|
||||
case 'i':
|
||||
return int64(t.num)
|
||||
return int64(t.num), nil
|
||||
case 'u':
|
||||
if t.num > maxInt64 {
|
||||
return maxInt64
|
||||
return maxInt64, &SyntacticError{Err: strconv.ErrRange}
|
||||
}
|
||||
return int64(t.num), nil
|
||||
case 'f', 'F':
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if t.str[0] == 'F' {
|
||||
f64 = float64(math.Float32frombits(uint32(t.num)))
|
||||
}
|
||||
switch i64 := f64toi64(f64); {
|
||||
case math.IsNaN(f64), math.Trunc(f64) != f64:
|
||||
return i64, &SyntacticError{Err: strconv.ErrSyntax}
|
||||
case (i64 == minInt64 && f64 < minInt64) || (i64 == maxInt64 && f64 > maxInt64):
|
||||
return i64, &SyntacticError{Err: strconv.ErrRange}
|
||||
default:
|
||||
return i64, nil
|
||||
}
|
||||
return int64(t.num)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON number that is a floating-point value.
|
||||
if t.Kind() == '0' {
|
||||
switch fv := t.Float(); {
|
||||
case fv >= maxInt64:
|
||||
return maxInt64
|
||||
case fv <= minInt64:
|
||||
return minInt64
|
||||
default:
|
||||
return int64(fv) // truncation toward zero
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
func f64toi64(f64 float64) int64 {
|
||||
switch {
|
||||
case math.IsNaN(f64):
|
||||
return 0
|
||||
case f64 >= maxInt64+1:
|
||||
return maxInt64
|
||||
case f64 < minInt64:
|
||||
return minInt64
|
||||
default:
|
||||
return int64(f64) // NOTE: This may lead to loss of precision.
|
||||
}
|
||||
}
|
||||
|
||||
// Uint returns the unsigned integer value for a JSON number.
|
||||
//
|
||||
// It reports an error that matches [strconv.ErrSyntax] if the JSON number
|
||||
// does not match the restricted grammar of just an unsigned integer.
|
||||
// It reports an error that matches [strconv.ErrRange] if the JSON number
|
||||
// is an unsigned integer, but outside the representable range of an uint64.
|
||||
// Even if an error is reported, a reasonable value is still returned.
|
||||
// The fractional component of any number is ignored (truncation toward zero).
|
||||
// Any number beyond the representation of an uint64 will be saturated
|
||||
// to the closest representable value.
|
||||
//
|
||||
// It panics if the token kind is not a JSON number.
|
||||
func (t Token) Uint() uint64 {
|
||||
func (t Token) Uint() (uint64, error) {
|
||||
// NOTE: This accessor returns 0 for any negative JSON number,
|
||||
// which might be surprising, but is at least consistent with the behavior
|
||||
// of saturating out-of-bounds numbers to the closest representable number.
|
||||
// We report ErrSyntax instead of ErrRange since the grammar for
|
||||
// an unsigned integer does not permit a negative sign.
|
||||
|
||||
if raw := t.raw; raw != nil {
|
||||
// Handle raw integer value.
|
||||
// Handle raw JSON number value.
|
||||
if uint64(raw.previousOffsetStart()) != t.num {
|
||||
panic(invalidTokenPanic)
|
||||
}
|
||||
neg := false
|
||||
buf := raw.previousBuffer()
|
||||
if len(buf) > 0 && buf[0] == '-' {
|
||||
neg, buf = true, buf[1:]
|
||||
// Prospectively parse an unsigned integer.
|
||||
switch abs, ok := jsonwire.ParseUint(buf); {
|
||||
case ok:
|
||||
return abs, nil
|
||||
case abs == maxUint64: // implies overflows
|
||||
return maxUint64, &SyntacticError{Err: strconv.ErrRange}
|
||||
}
|
||||
if num, ok := jsonwire.ParseUint(buf); ok {
|
||||
if neg {
|
||||
return minUint64
|
||||
}
|
||||
return num
|
||||
// This is not an unsigned integer, which implies ErrSyntax.
|
||||
if Kind(buf[0]).normalize() == '0' {
|
||||
f64, _ := strconv.ParseFloat(string(buf), 64)
|
||||
return f64tou64(f64), &SyntacticError{Err: strconv.ErrSyntax}
|
||||
}
|
||||
} else if t.num != 0 {
|
||||
// Handle exact integer value.
|
||||
// Handle typed Go number value.
|
||||
switch t.str[0] {
|
||||
case 'u':
|
||||
return t.num
|
||||
return t.num, nil
|
||||
case 'i':
|
||||
if int64(t.num) < minUint64 {
|
||||
return minUint64
|
||||
return minUint64, &SyntacticError{Err: strconv.ErrSyntax}
|
||||
}
|
||||
return uint64(int64(t.num)), nil
|
||||
case 'f', 'F':
|
||||
f64 := float64(math.Float64frombits(uint64(t.num)))
|
||||
if t.str[0] == 'F' {
|
||||
f64 = float64(math.Float32frombits(uint32(t.num)))
|
||||
}
|
||||
switch u64 := f64tou64(f64); {
|
||||
case math.IsNaN(f64), math.Trunc(f64) != f64, math.Signbit(f64):
|
||||
return u64, &SyntacticError{Err: strconv.ErrSyntax}
|
||||
case (u64 == minUint64 && f64 < minUint64) || (u64 == maxUint64 && f64 > maxUint64):
|
||||
return u64, &SyntacticError{Err: strconv.ErrRange}
|
||||
default:
|
||||
return u64, nil
|
||||
}
|
||||
return uint64(int64(t.num))
|
||||
}
|
||||
}
|
||||
|
||||
// Handle JSON number that is a floating-point value.
|
||||
if t.Kind() == '0' {
|
||||
switch fv := t.Float(); {
|
||||
case fv >= maxUint64:
|
||||
return maxUint64
|
||||
case fv <= minUint64:
|
||||
return minUint64
|
||||
default:
|
||||
return uint64(fv) // truncation toward zero
|
||||
}
|
||||
}
|
||||
|
||||
panic("invalid JSON token kind: " + t.Kind().String())
|
||||
}
|
||||
|
||||
func f64tou64(f64 float64) uint64 {
|
||||
switch {
|
||||
case math.IsNaN(f64):
|
||||
return 0
|
||||
case f64 >= maxUint64+1:
|
||||
return maxUint64
|
||||
case f64 < minUint64:
|
||||
return minUint64
|
||||
default:
|
||||
return uint64(f64) // NOTE: This may lead to loss of precision.
|
||||
}
|
||||
}
|
||||
|
||||
// Kind returns the token kind.
|
||||
func (t Token) Kind() Kind {
|
||||
switch {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package jsontext
|
|||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -32,15 +33,32 @@ func TestTokenStringAllocations(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTokenAccessors(t *testing.T) {
|
||||
type valueError[T any] struct {
|
||||
Value T
|
||||
Error error
|
||||
}
|
||||
type token struct {
|
||||
Bool bool
|
||||
String string
|
||||
Float32 float32
|
||||
Float float64
|
||||
Int int64
|
||||
Uint uint64
|
||||
Float32 valueError[float32]
|
||||
Float valueError[float64]
|
||||
Int valueError[int64]
|
||||
Uint valueError[uint64]
|
||||
Kind Kind
|
||||
}
|
||||
negZero := math.Copysign(0, -1)
|
||||
errRange := &SyntacticError{Err: strconv.ErrRange}
|
||||
errSyntax := &SyntacticError{Err: strconv.ErrSyntax}
|
||||
f32 := func(f32 float32) valueError[float32] { return valueError[float32]{Value: f32} }
|
||||
f32er := func(f32 float32) valueError[float32] { return valueError[float32]{Value: f32, Error: errRange} }
|
||||
f64 := func(f64 float64) valueError[float64] { return valueError[float64]{Value: f64} }
|
||||
f64er := func(f64 float64) valueError[float64] { return valueError[float64]{Value: f64, Error: errRange} }
|
||||
i64 := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64} }
|
||||
i64er := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64, Error: errRange} }
|
||||
i64es := func(i64 int64) valueError[int64] { return valueError[int64]{Value: i64, Error: errSyntax} }
|
||||
u64 := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64} }
|
||||
u64er := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64, Error: errRange} }
|
||||
u64es := func(u64 uint64) valueError[uint64] { return valueError[uint64]{Value: u64, Error: errSyntax} }
|
||||
|
||||
tests := []struct {
|
||||
in Token
|
||||
|
|
@ -59,81 +77,104 @@ func TestTokenAccessors(t *testing.T) {
|
|||
{String(""), token{String: "", Kind: '"'}},
|
||||
{String("hello, world!"), token{String: "hello, world!", Kind: '"'}},
|
||||
{rawToken(`"hello, world!"`), token{String: "hello, world!", Kind: '"'}},
|
||||
{Float32(float32(0)), token{String: "0", Float32: 0, Float: 0, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float32(float32(math.Copysign(0, -1))), token{String: "-0", Float32: float32(math.Copysign(0, -1)), Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float32(float32(math.NaN())), token{String: "NaN", Float32: float32(math.NaN()), Float: math.NaN(), Int: 0, Uint: 0, Kind: '"'}},
|
||||
{Float32(float32(math.Inf(+1))), token{String: "Infinity", Float32: float32(math.Inf(+1)), Float: math.Inf(+1), Kind: '"'}},
|
||||
{Float32(float32(math.Inf(-1))), token{String: "-Infinity", Float32: float32(math.Inf(-1)), Float: math.Inf(-1), Kind: '"'}},
|
||||
{Float32(float32(math.Pi)), token{String: "3.1415927", Float32: math.Pi, Float: float64(float32(math.Pi)), Int: 3, Uint: 3, Kind: '0'}},
|
||||
{Float(0), token{String: "0", Float32: 0, Float: 0, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float(math.Copysign(0, -1)), token{String: "-0", Float32: float32(math.Copysign(0, -1)), Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float(math.NaN()), token{String: "NaN", Float32: float32(math.NaN()), Float: math.NaN(), Int: 0, Uint: 0, Kind: '"'}},
|
||||
{Float(math.Inf(+1)), token{String: "Infinity", Float32: float32(math.Inf(+1)), Float: math.Inf(+1), Kind: '"'}},
|
||||
{Float(math.Inf(-1)), token{String: "-Infinity", Float32: float32(math.Inf(-1)), Float: math.Inf(-1), Kind: '"'}},
|
||||
{Float(math.Pi), token{String: "3.141592653589793", Float32: math.Pi, Float: math.Pi, Int: 3, Uint: 3, Kind: '0'}},
|
||||
{Int(minInt64), token{String: "-9223372036854775808", Float32: minInt64, Float: minInt64, Int: minInt64, Uint: minUint64, Kind: '0'}},
|
||||
{Int(minInt64 + 1), token{String: "-9223372036854775807", Float32: minInt64 + 1, Float: minInt64 + 1, Int: minInt64 + 1, Uint: minUint64, Kind: '0'}},
|
||||
{Int(-1), token{String: "-1", Float32: -1, Float: -1, Int: -1, Uint: minUint64, Kind: '0'}},
|
||||
{Int(0), token{String: "0", Float32: 0, Float: 0, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Int(+1), token{String: "1", Float32: +1, Float: +1, Int: +1, Uint: +1, Kind: '0'}},
|
||||
{Int(maxInt64 - 1), token{String: "9223372036854775806", Float32: maxInt64 - 1, Float: maxInt64 - 1, Int: maxInt64 - 1, Uint: maxInt64 - 1, Kind: '0'}},
|
||||
{Int(maxInt64), token{String: "9223372036854775807", Float32: maxInt64, Float: maxInt64, Int: maxInt64, Uint: maxInt64, Kind: '0'}},
|
||||
{Float32(float32(0)), token{String: "0", Float32: f32(0), Float: f64(0), Int: i64(0), Uint: u64(0), Kind: '0'}},
|
||||
{Float32(float32(math.Copysign(0, -1))), token{String: "-0", Float32: f32(float32(negZero)), Float: f64(negZero), Int: i64(0), Uint: u64es(0), Kind: '0'}},
|
||||
{Float32(float32(math.NaN())), token{String: "NaN", Float32: f32(float32(math.NaN())), Float: f64(math.NaN()), Kind: '"'}},
|
||||
{Float32(float32(math.Inf(+1))), token{String: "Infinity", Float32: f32(float32(math.Inf(+1))), Float: f64(math.Inf(+1)), Kind: '"'}},
|
||||
{Float32(float32(math.Inf(-1))), token{String: "-Infinity", Float32: f32(float32(math.Inf(-1))), Float: f64(math.Inf(-1)), Kind: '"'}},
|
||||
{Float32(float32(math.Pi)), token{String: "3.1415927", Float32: f32(math.Pi), Float: f64(float64(float32(math.Pi))), Int: i64es(3), Uint: u64es(3), Kind: '0'}},
|
||||
{Float32(float32(-1 * math.MaxFloat32)), token{String: "-3.4028235e+38", Float32: f32(float32(-1 * math.MaxFloat32)), Float: f64(-1 * math.MaxFloat32), Int: i64er(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{Float32(float32(+1 * math.MaxFloat32)), token{String: "3.4028235e+38", Float32: f32(float32(+1 * math.MaxFloat32)), Float: f64(+1 * math.MaxFloat32), Int: i64er(maxInt64), Uint: u64er(maxUint64), Kind: '0'}},
|
||||
{Float32(float32(123)), token{String: "123", Float32: f32(123), Float: f64(123), Int: i64(123), Uint: u64(123), Kind: '0'}},
|
||||
{Float(0), token{String: "0", Float32: f32(0), Float: f64(0), Int: i64(0), Uint: u64(0), Kind: '0'}},
|
||||
{Float(negZero), token{String: "-0", Float32: f32(float32(negZero)), Float: f64(negZero), Int: i64(0), Uint: u64es(0), Kind: '0'}},
|
||||
{Float(math.NaN()), token{String: "NaN", Float32: f32(float32(math.NaN())), Float: f64(math.NaN()), Int: i64(0), Uint: u64(0), Kind: '"'}},
|
||||
{Float(math.Inf(+1)), token{String: "Infinity", Float32: f32(float32(math.Inf(+1))), Float: f64(math.Inf(+1)), Kind: '"'}},
|
||||
{Float(math.Inf(-1)), token{String: "-Infinity", Float32: f32(float32(math.Inf(-1))), Float: f64(math.Inf(-1)), Kind: '"'}},
|
||||
{Float(math.Pi), token{String: "3.141592653589793", Float32: f32(math.Pi), Float: f64(math.Pi), Int: i64es(3), Uint: u64es(3), Kind: '0'}},
|
||||
{Float(-1 * math.MaxFloat64), token{String: "-1.7976931348623157e+308", Float32: f32er(float32(math.Inf(-1))), Float: f64(-1 * math.MaxFloat64), Int: i64er(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{Float(+1 * math.MaxFloat64), token{String: "1.7976931348623157e+308", Float32: f32er(float32(math.Inf(+1))), Float: f64(+1 * math.MaxFloat64), Int: i64er(maxInt64), Uint: u64er(maxUint64), Kind: '0'}},
|
||||
{Float(123), token{String: "123", Float32: f32(123), Float: f64(123), Int: i64(123), Uint: u64(123), Kind: '0'}},
|
||||
{Int(minInt64), token{String: "-9223372036854775808", Float32: f32(minInt64), Float: f64(minInt64), Int: i64(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{Int(minInt64 + 1), token{String: "-9223372036854775807", Float32: f32(minInt64 + 1), Float: f64(minInt64 + 1), Int: i64(minInt64 + 1), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{Int(-1), token{String: "-1", Float32: f32(-1), Float: f64(-1), Int: i64(-1), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{Int(0), token{String: "0", Float32: f32(0), Float: f64(0), Int: i64(0), Uint: u64(0), Kind: '0'}},
|
||||
{Int(+1), token{String: "1", Float32: f32(+1), Float: f64(+1), Int: i64(+1), Uint: u64(+1), Kind: '0'}},
|
||||
{Int(maxInt64 - 1), token{String: "9223372036854775806", Float32: f32(maxInt64 - 1), Float: f64(maxInt64 - 1), Int: i64(maxInt64 - 1), Uint: u64(maxInt64 - 1), Kind: '0'}},
|
||||
{Int(maxInt64), token{String: "9223372036854775807", Float32: f32(maxInt64), Float: f64(maxInt64), Int: i64(maxInt64), Uint: u64(maxInt64), Kind: '0'}},
|
||||
{Uint(minUint64), token{String: "0", Kind: '0'}},
|
||||
{Uint(minUint64 + 1), token{String: "1", Float32: minUint64 + 1, Float: minUint64 + 1, Int: minUint64 + 1, Uint: minUint64 + 1, Kind: '0'}},
|
||||
{Uint(maxUint64 - 1), token{String: "18446744073709551614", Float32: maxUint64 - 1, Float: maxUint64 - 1, Int: maxInt64, Uint: maxUint64 - 1, Kind: '0'}},
|
||||
{Uint(maxUint64), token{String: "18446744073709551615", Float32: maxUint64 - 1, Float: maxUint64 - 1, Int: maxInt64, Uint: maxUint64, Kind: '0'}},
|
||||
{rawToken(`-0`), token{String: "-0", Float32: float32(math.Copysign(0, -1)), Float: math.Copysign(0, -1), Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`1e1000`), token{String: "1e1000", Float32: math.MaxFloat32, Float: math.MaxFloat64, Int: maxInt64, Uint: maxUint64, Kind: '0'}},
|
||||
{rawToken(`-1e1000`), token{String: "-1e1000", Float32: -math.MaxFloat32, Float: -math.MaxFloat64, Int: minInt64, Uint: minUint64, Kind: '0'}},
|
||||
{rawToken(`0.1`), token{String: "0.1", Float32: 0.1, Float: 0.1, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`0.5`), token{String: "0.5", Float32: 0.5, Float: 0.5, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`0.9`), token{String: "0.9", Float32: 0.9, Float: 0.9, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`1.1`), token{String: "1.1", Float32: 1.1, Float: 1.1, Int: 1, Uint: 1, Kind: '0'}},
|
||||
{rawToken(`-0.1`), token{String: "-0.1", Float32: -0.1, Float: -0.1, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`-0.5`), token{String: "-0.5", Float32: -0.5, Float: -0.5, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`-0.9`), token{String: "-0.9", Float32: -0.9, Float: -0.9, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`-1.1`), token{String: "-1.1", Float32: -1.1, Float: -1.1, Int: -1, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`99999999999999999999`), token{String: "99999999999999999999", Float32: 1e20 - 1, Float: 1e20 - 1, Int: maxInt64, Uint: maxUint64, Kind: '0'}},
|
||||
{rawToken(`-99999999999999999999`), token{String: "-99999999999999999999", Float32: -1e20 - 1, Float: -1e20 - 1, Int: minInt64, Uint: minUint64, Kind: '0'}},
|
||||
{rawToken(`3.1415927`), token{String: "3.1415927", Float32: math.Pi, Float: 3.1415927, Int: 3, Uint: 3, Kind: '0'}},
|
||||
{rawToken(`3.141592653589793`), token{String: "3.141592653589793", Float32: math.Pi, Float: math.Pi, Int: 3, Uint: 3, Kind: '0'}},
|
||||
{Uint(minUint64 + 1), token{String: "1", Float32: f32(minUint64 + 1), Float: f64(minUint64 + 1), Int: i64(minUint64 + 1), Uint: u64(minUint64 + 1), Kind: '0'}},
|
||||
{Uint(maxUint64 - 1), token{String: "18446744073709551614", Float32: f32(maxUint64 - 1), Float: f64(maxUint64 - 1), Int: i64er(maxInt64), Uint: u64(maxUint64 - 1), Kind: '0'}},
|
||||
{Uint(maxUint64), token{String: "18446744073709551615", Float32: f32(maxUint64 - 1), Float: f64(maxUint64 - 1), Int: i64er(maxInt64), Uint: u64(maxUint64), Kind: '0'}},
|
||||
{rawToken(`-0`), token{String: "-0", Float32: f32(float32(negZero)), Float: f64(negZero), Int: i64(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`1e1000`), token{String: "1e1000", Float32: f32er(float32(math.Inf(+1))), Float: f64er(float64(math.Inf(+1))), Int: i64es(maxInt64), Uint: u64es(maxUint64), Kind: '0'}},
|
||||
{rawToken(`-1e1000`), token{String: "-1e1000", Float32: f32er(float32(math.Inf(-1))), Float: f64er(float64(math.Inf(-1))), Int: i64es(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{rawToken(`0.1`), token{String: "0.1", Float32: f32(0.1), Float: f64(0.1), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`0.5`), token{String: "0.5", Float32: f32(0.5), Float: f64(0.5), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`0.9`), token{String: "0.9", Float32: f32(0.9), Float: f64(0.9), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`1.0`), token{String: "1.0", Float32: f32(1.0), Float: f64(1.0), Int: i64es(1), Uint: u64es(1), Kind: '0'}},
|
||||
{rawToken(`1.1`), token{String: "1.1", Float32: f32(1.1), Float: f64(1.1), Int: i64es(1), Uint: u64es(1), Kind: '0'}},
|
||||
{rawToken(`123`), token{String: "123", Float32: f32(123), Float: f64(123), Int: i64(123), Uint: u64(123), Kind: '0'}},
|
||||
{rawToken(`-0.1`), token{String: "-0.1", Float32: f32(-0.1), Float: f64(-0.1), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`-0.5`), token{String: "-0.5", Float32: f32(-0.5), Float: f64(-0.5), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`-0.9`), token{String: "-0.9", Float32: f32(-0.9), Float: f64(-0.9), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`-1.0`), token{String: "-1.0", Float32: f32(-1.0), Float: f64(-1.0), Int: i64es(-1), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`-1.1`), token{String: "-1.1", Float32: f32(-1.1), Float: f64(-1.1), Int: i64es(-1), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`-123`), token{String: "-123", Float32: f32(-123), Float: f64(-123), Int: i64(-123), Uint: u64es(0), Kind: '0'}},
|
||||
{rawToken(`99999999999999999999`), token{String: "99999999999999999999", Float32: f32(1e20 - 1), Float: f64(1e20 - 1), Int: i64er(maxInt64), Uint: u64er(maxUint64), Kind: '0'}},
|
||||
{rawToken(`-99999999999999999999`), token{String: "-99999999999999999999", Float32: f32(-1e20 - 1), Float: f64(-1e20 - 1), Int: i64er(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{rawToken(`3.1415927`), token{String: "3.1415927", Float32: f32(math.Pi), Float: f64(3.1415927), Int: i64es(3), Uint: u64es(3), Kind: '0'}},
|
||||
{rawToken(`3.141592653589793`), token{String: "3.141592653589793", Float32: f32(math.Pi), Float: f64(math.Pi), Int: i64es(3), Uint: u64es(3), Kind: '0'}},
|
||||
{rawToken(`-9223372036854775807`), token{String: "-9223372036854775807", Float32: f32(-1 << 63), Float: f64(-1 << 63), Int: i64(minInt64 + 1), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{rawToken(`-9223372036854775808`), token{String: "-9223372036854775808", Float32: f32(-1 << 63), Float: f64(-1 << 63), Int: i64(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{rawToken(`-9223372036854775809`), token{String: "-9223372036854775809", Float32: f32(-1 << 63), Float: f64(-1 << 63), Int: i64er(minInt64), Uint: u64es(minUint64), Kind: '0'}},
|
||||
{rawToken(`9223372036854775806`), token{String: "9223372036854775806", Float32: f32(1 << 63), Float: f64(1 << 63), Int: i64(maxInt64 - 1), Uint: u64(maxInt64 - 1), Kind: '0'}},
|
||||
{rawToken(`9223372036854775807`), token{String: "9223372036854775807", Float32: f32(1 << 63), Float: f64(1 << 63), Int: i64(maxInt64), Uint: u64(maxInt64), Kind: '0'}},
|
||||
{rawToken(`9223372036854775808`), token{String: "9223372036854775808", Float32: f32(1 << 63), Float: f64(1 << 63), Int: i64er(maxInt64), Uint: u64(maxInt64 + 1), Kind: '0'}},
|
||||
{rawToken(`18446744073709551614`), token{String: "18446744073709551614", Float32: f32(1 << 64), Float: f64(1 << 64), Int: i64er(maxInt64), Uint: u64(maxUint64 - 1), Kind: '0'}},
|
||||
{rawToken(`18446744073709551615`), token{String: "18446744073709551615", Float32: f32(1 << 64), Float: f64(1 << 64), Int: i64er(maxInt64), Uint: u64(maxUint64), Kind: '0'}},
|
||||
{rawToken(`18446744073709551616`), token{String: "18446744073709551616", Float32: f32(1 << 64), Float: f64(1 << 64), Int: i64er(maxInt64), Uint: u64er(maxUint64), Kind: '0'}},
|
||||
|
||||
// NOTE: There exist many raw JSON numbers where:
|
||||
// float32(ParseFloat(s, 32)) != float32(ParseFloat(s, 64))
|
||||
// due to issues with double rounding in opposite directions.
|
||||
// This suggests the need for a Token.Float32 accessor.
|
||||
{rawToken(`9000000000.0000001`), token{String: "9000000000.0000001", Float32: 9000000000.0000001, Float: 9000000000.0000001, Int: 9e9, Uint: 9e9, Kind: '0'}},
|
||||
{rawToken(`9000000000.0000001`), token{String: "9000000000.0000001", Float32: f32(9000000000.0000001), Float: f64(9000000000.0000001), Int: i64es(9e9), Uint: u64es(9e9), Kind: '0'}},
|
||||
// NOTE: ±7.038531e-26 is the only 32-bit precision float where:
|
||||
// f != float32(ParseFloat(FormatFloat(f, 32), 64))
|
||||
// assuming FormatFloat uses ECMA-262, 10th edition, section 7.1.12.1.
|
||||
{rawToken(`7.038531e-26`), token{String: "7.038531e-26", Float32: 7.038531e-26, Float: 7.038531e-26, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float32(7.038531e-26), token{String: "7.038531e-26", Float32: 7.038531e-26, Float: 7.038530691851209e-26, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{Float(7.038531e-26), token{String: "7.038531e-26", Float32: 7.0385313e-26, Float: 7.038531e-26, Int: 0, Uint: 0, Kind: '0'}},
|
||||
{rawToken(`7.038531e-26`), token{String: "7.038531e-26", Float32: f32(7.038531e-26), Float: f64(7.038531e-26), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{Float32(7.038531e-26), token{String: "7.038531e-26", Float32: f32(7.038531e-26), Float: f64(7.038530691851209e-26), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
{Float(7.038531e-26), token{String: "7.038531e-26", Float32: f32(7.0385313e-26), Float: f64(7.038531e-26), Int: i64es(0), Uint: u64es(0), Kind: '0'}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Run(tt.in.String(), func(t *testing.T) {
|
||||
got := token{
|
||||
Bool: func() bool {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Bool()
|
||||
}(),
|
||||
String: tt.in.String(),
|
||||
Float32: func() float32 {
|
||||
Float32: func() valueError[float32] {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Float32()
|
||||
f32, err := tt.in.Float32()
|
||||
return valueError[float32]{f32, err}
|
||||
}(),
|
||||
Float: func() float64 {
|
||||
Float: func() valueError[float64] {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Float()
|
||||
f64, err := tt.in.Float()
|
||||
return valueError[float64]{f64, err}
|
||||
}(),
|
||||
Int: func() int64 {
|
||||
Int: func() valueError[int64] {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Int()
|
||||
i64, err := tt.in.Int()
|
||||
return valueError[int64]{i64, err}
|
||||
}(),
|
||||
Uint: func() uint64 {
|
||||
Uint: func() valueError[uint64] {
|
||||
defer func() { recover() }()
|
||||
return tt.in.Uint()
|
||||
u64, err := tt.in.Uint()
|
||||
return valueError[uint64]{u64, err}
|
||||
}(),
|
||||
Kind: tt.in.Kind(),
|
||||
}
|
||||
|
|
@ -144,17 +185,17 @@ func TestTokenAccessors(t *testing.T) {
|
|||
if got.String != tt.want.String {
|
||||
t.Errorf("Token(%s).String() = %v, want %v", tt.in, got.String, tt.want.String)
|
||||
}
|
||||
if math.Float32bits(got.Float32) != math.Float32bits(tt.want.Float32) {
|
||||
t.Errorf("Token(%s).Float32() = %v, want %v", tt.in, got.Float32, tt.want.Float32)
|
||||
if math.Float32bits(got.Float32.Value) != math.Float32bits(tt.want.Float32.Value) || !reflect.DeepEqual(got.Float32.Error, tt.want.Float32.Error) {
|
||||
t.Errorf("Token(%s).Float32() = (%v, %v), want (%v, %v)", tt.in, got.Float32.Value, got.Float32.Error, tt.want.Float32.Value, tt.want.Float32.Error)
|
||||
}
|
||||
if math.Float64bits(got.Float) != math.Float64bits(tt.want.Float) {
|
||||
t.Errorf("Token(%s).Float() = %v, want %v", tt.in, got.Float, tt.want.Float)
|
||||
if math.Float64bits(got.Float.Value) != math.Float64bits(tt.want.Float.Value) || !reflect.DeepEqual(got.Float.Error, tt.want.Float.Error) {
|
||||
t.Errorf("Token(%s).Float() = (%v, %v), want (%v, %v)", tt.in, got.Float.Value, got.Float.Error, tt.want.Float.Value, tt.want.Float.Error)
|
||||
}
|
||||
if got.Int != tt.want.Int {
|
||||
t.Errorf("Token(%s).Int() = %v, want %v", tt.in, got.Int, tt.want.Int)
|
||||
if got.Int.Value != tt.want.Int.Value || !reflect.DeepEqual(got.Int.Error, tt.want.Int.Error) {
|
||||
t.Errorf("Token(%s).Int() = (%v, %v), want (%v, %v)", tt.in, got.Int.Value, got.Int.Error, tt.want.Int.Value, tt.want.Int.Error)
|
||||
}
|
||||
if got.Uint != tt.want.Uint {
|
||||
t.Errorf("Token(%s).Uint() = %v, want %v", tt.in, got.Uint, tt.want.Uint)
|
||||
if got.Uint.Value != tt.want.Uint.Value || !reflect.DeepEqual(got.Uint.Error, tt.want.Uint.Error) {
|
||||
t.Errorf("Token(%s).Uint() = (%v, %v), want (%v, %v)", tt.in, got.Uint.Value, got.Uint.Error, tt.want.Uint.Value, tt.want.Uint.Error)
|
||||
}
|
||||
if got.Kind != tt.want.Kind {
|
||||
t.Errorf("Token(%s).Kind() = %v, want %v", tt.in, got.Kind, tt.want.Kind)
|
||||
|
|
@ -183,7 +224,7 @@ func TestTokenClone(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Run(tt.in.String(), func(t *testing.T) {
|
||||
got := tt.in.Clone()
|
||||
if !reflect.DeepEqual(got, tt.in) {
|
||||
t.Errorf("Token(%s) == Token(%s).Clone() = false, want true", tt.in, tt.in)
|
||||
|
|
|
|||
|
|
@ -535,7 +535,7 @@ func TestHTTPDecoding(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenTruncation(t *testing.T) {
|
||||
func TestTokenError(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
err error
|
||||
|
|
@ -556,13 +556,14 @@ func TestTokenTruncation(t *testing.T) {
|
|||
{in: `nul`, err: io.ErrUnexpectedEOF},
|
||||
{in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal `))}},
|
||||
{in: `false`, err: io.EOF},
|
||||
{in: ` 1e1000`, err: &UnmarshalTypeError{Value: "number 1e1000", Type: reflect.TypeFor[float64](), Offset: int64(len(` 1e100`))}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
d := NewDecoder(strings.NewReader(tt.in))
|
||||
for i := 0; true; i++ {
|
||||
if _, err := d.Token(); err != nil {
|
||||
if !reflect.DeepEqual(err, tt.err) {
|
||||
t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
|
||||
t.Errorf("`%s`: %d.Token error = %#v, want %#v", tt.in, i, err, tt.err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ package json
|
|||
|
||||
import (
|
||||
"cmp"
|
||||
"errors"
|
||||
"math"
|
||||
"reflect"
|
||||
"slices"
|
||||
|
|
@ -90,9 +91,9 @@ func unmarshalValueAny(dec *jsontext.Decoder, uo *jsonopts.Struct) (any, error)
|
|||
if uo.Flags.Get(jsonflags.UnmarshalAnyWithRawNumber) {
|
||||
return internal.RawNumberOf(val), nil
|
||||
}
|
||||
fv, ok := jsonwire.ParseFloat(val, 64)
|
||||
if !ok {
|
||||
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, strconv.ErrRange)
|
||||
fv, err := strconv.ParseFloat(string(val), 64)
|
||||
if err != nil {
|
||||
return fv, newUnmarshalErrorAfterWithValue(dec, float64Type, errors.Unwrap(err))
|
||||
}
|
||||
return fv, nil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -718,10 +718,10 @@ func makeFloatArshaler(t reflect.Type) *arshaler {
|
|||
if stringify && k == '0' {
|
||||
break
|
||||
}
|
||||
fv, ok := jsonwire.ParseFloat(val, bits)
|
||||
fv, err := strconv.ParseFloat(string(val), bits)
|
||||
va.SetFloat(fv)
|
||||
if !ok {
|
||||
return newUnmarshalErrorAfterWithValue(dec, t, strconv.ErrRange)
|
||||
if err != nil {
|
||||
return newUnmarshalErrorAfterWithValue(dec, t, errors.Unwrap(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5428,7 +5428,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
name: jsontest.Name("Floats/Float32/Overflow"),
|
||||
inBuf: `-1e1000`,
|
||||
inVal: addr(float32(32.32)),
|
||||
want: addr(float32(-math.MaxFloat32)),
|
||||
want: addr(float32(math.Inf(-1))),
|
||||
wantErr: EU(strconv.ErrRange).withVal(`-1e1000`).withType('0', T[float32]()),
|
||||
}, {
|
||||
name: jsontest.Name("Floats/Float64/Pi"),
|
||||
|
|
@ -5444,13 +5444,13 @@ func TestUnmarshal(t *testing.T) {
|
|||
name: jsontest.Name("Floats/Float64/Overflow"),
|
||||
inBuf: `-1e1000`,
|
||||
inVal: addr(float64(64.64)),
|
||||
want: addr(float64(-math.MaxFloat64)),
|
||||
want: addr(float64(math.Inf(-1))),
|
||||
wantErr: EU(strconv.ErrRange).withVal(`-1e1000`).withType('0', T[float64]()),
|
||||
}, {
|
||||
name: jsontest.Name("Floats/Any/Overflow"),
|
||||
inBuf: `1e1000`,
|
||||
inVal: new(any),
|
||||
want: addr(any(float64(math.MaxFloat64))),
|
||||
want: addr(any(float64(math.Inf(+1)))),
|
||||
wantErr: EU(strconv.ErrRange).withVal(`1e1000`).withType('0', T[float64]()),
|
||||
}, {
|
||||
name: jsontest.Name("Floats/Named"),
|
||||
|
|
|
|||
|
|
@ -471,7 +471,8 @@ func mustDecodeTokens(t testing.TB, data []byte) []jsontext.Token {
|
|||
case '"':
|
||||
tokens = append(tokens, jsontext.String(tok.String()))
|
||||
case '0':
|
||||
tokens = append(tokens, jsontext.Float(tok.Float()))
|
||||
f, _ := tok.Float()
|
||||
tokens = append(tokens, jsontext.Float(f))
|
||||
default:
|
||||
tokens = append(tokens, tok.Clone())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"encoding/json/jsontext"
|
||||
jsonv2 "encoding/json/v2"
|
||||
|
|
@ -231,7 +232,11 @@ func (dec *Decoder) Token() (Token, error) {
|
|||
if useNumber, _ := jsonv2.GetOption(dec.opts, unmarshalAnyWithRawNumber); useNumber {
|
||||
return Number(tok.String()), nil
|
||||
}
|
||||
return tok.Float(), nil
|
||||
v, err := tok.Float()
|
||||
if err != nil {
|
||||
return nil, &UnmarshalTypeError{Value: "number " + tok.String(), Type: reflect.TypeFor[float64](), Offset: dec.InputOffset() - int64(len(tok.String()))}
|
||||
}
|
||||
return v, nil
|
||||
case '{', '}', '[', ']':
|
||||
return Delim(k), nil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ func TestHTTPDecoding(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTokenTruncation(t *testing.T) {
|
||||
func TestTokenError(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
err error
|
||||
|
|
@ -537,13 +537,14 @@ func TestTokenTruncation(t *testing.T) {
|
|||
{in: `nul`, err: io.ErrUnexpectedEOF},
|
||||
{in: `fal `, err: &SyntaxError{"invalid character ' ' in literal false (expecting 's')", int64(len(`fal`))}},
|
||||
{in: `false`, err: io.EOF},
|
||||
{in: ` 1e1000`, err: &UnmarshalTypeError{Value: "number 1e1000", Type: reflect.TypeFor[float64](), Offset: int64(len(` `))}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
d := NewDecoder(strings.NewReader(tt.in))
|
||||
for i := 0; true; i++ {
|
||||
if _, err := d.Token(); err != nil {
|
||||
if !reflect.DeepEqual(err, tt.err) {
|
||||
t.Errorf("`%s`: %d.Token error = %#v, want %v", tt.in, i, err, tt.err)
|
||||
t.Errorf("`%s`: %d.Token error = %#v, want %#v", tt.in, i, err, tt.err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue