Skip to content

Commit 68757bf

Browse files
committed
parse input containing NaN and Infinity values
JSON standard does not allow NaN or Infinity, but some applications produce output that contains such values. One example of such application would be TensorFlow Serving[1]. An example of JSON parser that accepts NaN and Infinity is the one that comes with Python[2]. This commit introduces a new configuration parameter AllowNaN, that enables jsoniter to unmarshal input containing NaN and Infinity. 1. https://www.tensorflow.org/tfx/serving/api_rest#json_conformance 2. https://docs.python.org/3/library/json.html#infinite-and-nan-number-values
1 parent 49c900e commit 68757bf

File tree

4 files changed

+90
-3
lines changed

4 files changed

+90
-3
lines changed

config.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type Config struct {
2525
ValidateJsonRawMessage bool
2626
ObjectFieldMustBeSimpleString bool
2727
CaseSensitive bool
28+
// AllowNaN parses input that contains non-standard NaN and Infinity values
29+
AllowNaN bool
2830
}
2931

3032
// API the public interface of this package.
@@ -49,6 +51,7 @@ type API interface {
4951
// ConfigDefault the default API
5052
var ConfigDefault = Config{
5153
EscapeHTML: true,
54+
AllowNaN: true,
5255
}.Froze()
5356

5457
// ConfigCompatibleWithStandardLibrary tries to be 100% compatible with standard library behavior

iter.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ func init() {
5959
valueTypes['7'] = NumberValue
6060
valueTypes['8'] = NumberValue
6161
valueTypes['9'] = NumberValue
62+
valueTypes['N'] = NumberValue
63+
valueTypes['I'] = NumberValue
6264
valueTypes['t'] = BoolValue
6365
valueTypes['f'] = BoolValue
6466
valueTypes['n'] = NilValue

iter_float.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,46 @@ non_decimal_loop:
156156
return iter.readFloat32SlowPath()
157157
}
158158

159+
var nanBytes = []byte("NaN")
160+
161+
func (iter *Iterator) readNaN() (ret string) {
162+
for _, b := range nanBytes {
163+
if iter.readByte() != b {
164+
iter.ReportError("readNaN", "expect NaN")
165+
return
166+
}
167+
}
168+
if !iter.cfg.configBeforeFrozen.AllowNaN {
169+
iter.ReportError("readInfinity", "invalid number, AllowNaN is not set")
170+
return
171+
}
172+
return "NaN"
173+
}
174+
175+
var infinityBytes = []byte("Infinity")
176+
177+
func (iter *Iterator) readInfinity() (ret string) {
178+
var negative bool
179+
if iter.buf[iter.head] == '-' {
180+
negative = true
181+
iter.head++
182+
}
183+
for _, b := range infinityBytes {
184+
if iter.readByte() != b {
185+
iter.ReportError("readInfinity", "expect Infinity")
186+
return
187+
}
188+
}
189+
if !iter.cfg.configBeforeFrozen.AllowNaN {
190+
iter.ReportError("readInfinity", "invalid number, AllowNaN is not set")
191+
return
192+
}
193+
if negative {
194+
return "-Infinity"
195+
}
196+
return "Infinity"
197+
}
198+
159199
func (iter *Iterator) readNumberAsString() (ret string) {
160200
strBuf := [16]byte{}
161201
str := strBuf[0:0]
@@ -167,10 +207,19 @@ load_loop:
167207
case '+', '-', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
168208
str = append(str, c)
169209
continue
170-
default:
171-
iter.head = i
172-
break load_loop
210+
case 'N':
211+
if len(str) == 0 {
212+
return iter.readNaN()
213+
}
214+
case 'I':
215+
if len(str) == 0 {
216+
return iter.readInfinity()
217+
} else if len(str) == 1 && str[0] == '-' {
218+
return iter.readInfinity()
219+
}
173220
}
221+
iter.head = i
222+
break load_loop
174223
}
175224
if !iter.loadMore() {
176225
break

value_tests/float_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,43 @@ import (
66
"fmt"
77
"github.com/json-iterator/go"
88
"github.com/stretchr/testify/require"
9+
"math"
910
"strconv"
1011
"testing"
1112
)
1213

14+
func Test_NaN_Inf(t *testing.T) {
15+
cases := []struct {
16+
json string
17+
check func(float64) bool
18+
}{
19+
{
20+
json: "NaN",
21+
check: math.IsNaN,
22+
},
23+
{
24+
json: "-Infinity",
25+
check: func(f float64) bool { return math.IsInf(f, -1) },
26+
},
27+
{
28+
json: "Infinity",
29+
check: func(f float64) bool { return math.IsInf(f, 1) },
30+
},
31+
}
32+
33+
for _, tc := range cases {
34+
iter := jsoniter.ParseString(jsoniter.ConfigDefault, tc.json+",")
35+
if res := iter.ReadFloat64(); !tc.check(res) || iter.Error != nil {
36+
t.Errorf("couldn't parse %s, got %f (%v)", tc.json, res, iter.Error)
37+
}
38+
iterStd := jsoniter.ParseString(jsoniter.ConfigCompatibleWithStandardLibrary, tc.json+",")
39+
res := iterStd.Read()
40+
if iterStd.Error == nil {
41+
t.Errorf("standard compatible parser should have returned an error for %s, got %v", tc.json, res)
42+
}
43+
}
44+
}
45+
1346
func Test_read_float(t *testing.T) {
1447
inputs := []string{
1548
`1.1`, `1000`, `9223372036854775807`, `12.3`, `-12.3`, `720368.54775807`, `720368.547758075`,

0 commit comments

Comments
 (0)