Accept NaN/Inf

This commit allows for NaN and Inf numbers.

see #242
This commit is contained in:
tidwall 2021-10-22 04:00:22 -07:00
parent 35fa0d71c8
commit 0cbc0f402f
2 changed files with 99 additions and 35 deletions

102
gjson.go
View File

@ -1103,6 +1103,7 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
} }
hit = pmatch && !rp.more hit = pmatch && !rp.more
for ; i < len(c.json); i++ { for ; i < len(c.json); i++ {
var num bool
switch c.json[i] { switch c.json[i] {
default: default:
continue continue
@ -1150,15 +1151,13 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
return i, true return i, true
} }
} }
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case 'n':
i, val = parseNumber(c.json, i) if i+1 < len(c.json) && c.json[i+1] != 'u' {
if hit { num = true
c.value.Raw = val break
c.value.Type = Number
c.value.Num, _ = strconv.ParseFloat(val, 64)
return i, true
} }
case 't', 'f', 'n': fallthrough
case 't', 'f':
vc := c.json[i] vc := c.json[i]
i, val = parseLiteral(c.json, i) i, val = parseLiteral(c.json, i)
if hit { if hit {
@ -1171,6 +1170,18 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
} }
return i, true return i, true
} }
case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'i', 'I', 'N':
num = true
}
if num {
i, val = parseNumber(c.json, i)
if hit {
c.value.Raw = val
c.value.Type = Number
c.value.Num, _ = strconv.ParseFloat(val, 64)
return i, true
}
} }
break break
} }
@ -1358,6 +1369,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
} else { } else {
ch = c.json[i] ch = c.json[i]
} }
var num bool
switch ch { switch ch {
default: default:
continue continue
@ -1440,26 +1452,13 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
return i, true return i, true
} }
} }
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case 'n':
i, val = parseNumber(c.json, i) if i+1 < len(c.json) && c.json[i+1] != 'u' {
if rp.query.on { num = true
var qval Result
qval.Raw = val
qval.Type = Number
qval.Num, _ = strconv.ParseFloat(val, 64)
if procQuery(qval) {
return i, true
}
} else if hit {
if rp.alogok {
break break
} }
c.value.Raw = val fallthrough
c.value.Type = Number case 't', 'f':
c.value.Num, _ = strconv.ParseFloat(val, 64)
return i, true
}
case 't', 'f', 'n':
vc := c.json[i] vc := c.json[i]
i, val = parseLiteral(c.json, i) i, val = parseLiteral(c.json, i)
if rp.query.on { if rp.query.on {
@ -1487,6 +1486,9 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
} }
return i, true return i, true
} }
case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'i', 'I', 'N':
num = true
case ']': case ']':
if rp.arrch && rp.part == "#" { if rp.arrch && rp.part == "#" {
if rp.alogok { if rp.alogok {
@ -1562,6 +1564,26 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
} }
return i + 1, false return i + 1, false
} }
if num {
i, val = parseNumber(c.json, i)
if rp.query.on {
var qval Result
qval.Raw = val
qval.Type = Number
qval.Num, _ = strconv.ParseFloat(val, 64)
if procQuery(qval) {
return i, true
}
} else if hit {
if rp.alogok {
break
}
c.value.Raw = val
c.value.Type = Number
c.value.Num, _ = strconv.ParseFloat(val, 64)
return i, true
}
}
break break
} }
} }
@ -2081,6 +2103,7 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
if json[i] <= ' ' { if json[i] <= ' ' {
continue continue
} }
var num bool
switch json[i] { switch json[i] {
case '"': case '"':
i++ i++
@ -2100,15 +2123,13 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
} }
} }
return i, res, true return i, res, true
case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': case 'n':
i, val = parseNumber(json, i) if i+1 < len(json) && json[i+1] != 'u' {
if hit { num = true
res.Raw = val break
res.Type = Number
res.Num, _ = strconv.ParseFloat(val, 64)
} }
return i, res, true fallthrough
case 't', 'f', 'n': case 't', 'f':
vc := json[i] vc := json[i]
i, val = parseLiteral(json, i) i, val = parseLiteral(json, i)
if hit { if hit {
@ -2121,7 +2142,20 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
} }
return i, res, true return i, res, true
} }
case '+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'i', 'I', 'N':
num = true
} }
if num {
i, val = parseNumber(json, i)
if hit {
res.Raw = val
res.Type = Number
res.Num, _ = strconv.ParseFloat(val, 64)
}
return i, res, true
}
} }
return i, res, false return i, res, false
} }

View File

@ -5,6 +5,7 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"math/rand" "math/rand"
"strconv" "strconv"
"strings" "strings"
@ -2235,3 +2236,32 @@ func TestKeysValuesModifier(t *testing.T) {
assert(t, Get(`[]`, `@values`).String() == `[]`) assert(t, Get(`[]`, `@values`).String() == `[]`)
assert(t, Get(`[1,2,3]`, `@values`).String() == `[1,2,3]`) assert(t, Get(`[1,2,3]`, `@values`).String() == `[1,2,3]`)
} }
func TestNaNInf(t *testing.T) {
json := `[+Inf,-Inf,Inf,iNF,-iNF,+iNF,NaN,nan,nAn,-0,+0]`
raws := []string{"+Inf", "-Inf", "Inf", "iNF", "-iNF", "+iNF", "NaN", "nan",
"nAn", "-0", "+0"}
nums := []float64{math.Inf(+1), math.Inf(-1), math.Inf(0), math.Inf(0),
math.Inf(-1), math.Inf(+1), math.NaN(), math.NaN(), math.NaN(),
math.Copysign(0, -1), 0}
// assert(t, int(Get(json, `#`).Int()) == len(raws))
for i := 0; i < len(raws); i++ {
r := Get(json, fmt.Sprintf("%d", i))
// fmt.Printf("%s %s\n", r.Raw, raws[i])
assert(t, r.Raw == raws[i])
// fmt.Printf("%f %f\n", r.Num, nums[i])
assert(t, r.Num == nums[i] || (math.IsNaN(r.Num) && math.IsNaN(nums[i])))
}
// println("------------")
var i int
Parse(json).ForEach(func(_, r Result) bool {
// fmt.Printf("%s %s\n", r.Raw, raws[i])
assert(t, r.Raw == raws[i])
// fmt.Printf("%f %f\n", r.Num, nums[i])
assert(t, r.Num == nums[i] || (math.IsNaN(r.Num) && math.IsNaN(nums[i])))
i++
return true
})
}