Skip to content

Commit c4a38a4

Browse files
committed
Merge pull request #203 from plotly/encode-nan-inf
Override `JSONEncoder.iterencode`
2 parents ce0adcc + 9af744b commit c4a38a4

File tree

5 files changed

+86
-6
lines changed

5 files changed

+86
-6
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
def setup_package():
2+
import warnings
3+
warnings.filterwarnings('ignore')
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import json
2+
from unittest import TestCase
3+
4+
from plotly.utils import PlotlyJSONEncoder
5+
6+
7+
class TestJSONEncoder(TestCase):
8+
9+
def test_nan_to_null(self):
10+
array = [1, float('NaN'), float('Inf'), float('-Inf'), 'platypus']
11+
result = json.dumps(array, cls=PlotlyJSONEncoder)
12+
expected_result = '[1, null, null, null, "platypus"]'
13+
self.assertEqual(result, expected_result)

plotly/tests/test_optional/test_utils/test_utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def test_column_json_encoding():
173173
'"2014-01-05 01:01:01", '
174174
'"2014-01-05 01:01:01.000001"], '
175175
'"name": "col 2"}, '
176-
'{"data": [1, 2, 3, NaN, NaN, Infinity, '
176+
'{"data": [1, 2, 3, null, null, null, '
177177
'"2014-01-05"], "name": "col 3"}]' == json_columns)
178178

179179

@@ -188,7 +188,7 @@ def test_figure_json_encoding():
188188
js2 = json.dumps(s2, cls=utils.PlotlyJSONEncoder, sort_keys=True)
189189

190190
assert(js1 == '{"type": "scatter3d", "x": [1, 2, 3], '
191-
'"y": [1, 2, 3, NaN, NaN, Infinity, "2014-01-05"], '
191+
'"y": [1, 2, 3, null, null, null, "2014-01-05"], '
192192
'"z": [1, "A", "2014-01-05", '
193193
'"2014-01-05 01:01:01", "2014-01-05 01:01:01.000001"]}')
194194
assert(js2 == '{"type": "scatter", "x": [1, 2, 3]}')
@@ -219,7 +219,7 @@ def test_datetime_json_encoding():
219219

220220
def test_pandas_json_encoding():
221221
j1 = json.dumps(df['col 1'], cls=utils.PlotlyJSONEncoder)
222-
assert(j1 == '[1, 2, 3, "2014-01-05", null, NaN, Infinity]')
222+
assert(j1 == '[1, 2, 3, "2014-01-05", null, null, null]')
223223

224224
# Test that data wasn't mutated
225225
assert_series_equal(df['col 1'],
@@ -249,7 +249,7 @@ def test_numpy_masked_json_encoding():
249249
l = [1, 2, np.ma.core.masked]
250250
j1 = json.dumps(l, cls=utils.PlotlyJSONEncoder)
251251
print j1
252-
assert(j1 == '[1, 2, NaN]')
252+
assert(j1 == '[1, 2, null]')
253253
assert(set(l) == set([1, 2, np.ma.core.masked]))
254254

255255

@@ -276,7 +276,7 @@ def test_masked_constants_example():
276276
jy = json.dumps(renderer.plotly_fig['data'][1]['y'],
277277
cls=utils.PlotlyJSONEncoder)
278278
assert(jy == '[-398.11793026999999, -398.11792966000002, '
279-
'-398.11786308000001, NaN]')
279+
'-398.11786308000001, null]')
280280

281281

282282
def test_numpy_dates():

plotly/utils.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,72 @@ class PlotlyJSONEncoder(json.JSONEncoder):
119119
120120
See PlotlyJSONEncoder.default for more implementation information.
121121
122+
Additionally, this encoder overrides nan functionality so that 'Inf',
123+
'NaN' and '-Inf' encode to 'null'. Which is stricter JSON than the Python
124+
version.
125+
122126
"""
123127

128+
# we want stricter JSON, so convert NaN, Inf, -Inf --> 'null'
129+
nan_str = inf_str = neg_inf_str = 'null'
130+
131+
# uses code from official python json.encoder module. Same licence applies.
132+
def iterencode(self, o, _one_shot=False):
133+
"""
134+
Encode the given object and yield each string
135+
representation as available.
136+
137+
For example::
138+
139+
for chunk in JSONEncoder().iterencode(bigobject):
140+
mysocket.write(chunk)
141+
142+
"""
143+
if self.check_circular:
144+
markers = {}
145+
else:
146+
markers = None
147+
if self.ensure_ascii:
148+
_encoder = json.encoder.encode_basestring_ascii
149+
else:
150+
_encoder = json.encoder.encode_basestring
151+
if self.encoding != 'utf-8':
152+
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
153+
if isinstance(o, str):
154+
o = o.decode(_encoding)
155+
return _orig_encoder(o)
156+
157+
def floatstr(o, allow_nan=self.allow_nan,
158+
_repr=json.encoder.FLOAT_REPR, _inf=json.encoder.INFINITY,
159+
_neginf=-json.encoder.INFINITY):
160+
# Check for specials. Note that this type of test is processor
161+
# and/or platform-specific, so do tests which don't depend on the
162+
# internals.
163+
164+
# *any* two NaNs are not equivalent (even to itself) try:
165+
# float('NaN') == float('NaN')
166+
if o != o:
167+
text = self.nan_str
168+
elif o == _inf:
169+
text = self.inf_str
170+
elif o == _neginf:
171+
text = self.neg_inf_str
172+
else:
173+
return _repr(o)
174+
175+
if not allow_nan:
176+
raise ValueError(
177+
"Out of range float values are not JSON compliant: " +
178+
repr(o))
179+
180+
return text
181+
182+
_iterencode = json.encoder._make_iterencode(
183+
markers, self.default, _encoder, self.indent, floatstr,
184+
self.key_separator, self.item_separator, self.sort_keys,
185+
self.skipkeys, _one_shot)
186+
return _iterencode(o, 0)
187+
124188
def default(self, obj):
125189
"""
126190
Accept an object (of unknown type) and try to encode with priority:

plotly/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.6.10'
1+
__version__ = '1.6.11'

0 commit comments

Comments
 (0)