Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Override JSONEncoder.iterencode #203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plotly/tests/test_core/test_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
def setup_package():
import warnings
warnings.filterwarnings('ignore')
13 changes: 13 additions & 0 deletions plotly/tests/test_core/test_utils/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import json
from unittest import TestCase

from plotly.utils import PlotlyJSONEncoder


class TestJSONEncoder(TestCase):

def test_nan_to_null(self):
array = [1, float('NaN'), float('Inf'), float('-Inf'), 'platypus']
result = json.dumps(array, cls=PlotlyJSONEncoder)
expected_result = '[1, null, null, null, "platypus"]'
self.assertEqual(result, expected_result)
10 changes: 5 additions & 5 deletions plotly/tests/test_optional/test_utils/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def test_column_json_encoding():
'"2014-01-05 01:01:01", '
'"2014-01-05 01:01:01.000001"], '
'"name": "col 2"}, '
'{"data": [1, 2, 3, NaN, NaN, Infinity, '
'{"data": [1, 2, 3, null, null, null, '
'"2014-01-05"], "name": "col 3"}]' == json_columns)


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

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

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

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


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


def test_numpy_dates():
Expand Down
64 changes: 64 additions & 0 deletions plotly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,72 @@ class PlotlyJSONEncoder(json.JSONEncoder):

See PlotlyJSONEncoder.default for more implementation information.

Additionally, this encoder overrides nan functionality so that 'Inf',
'NaN' and '-Inf' encode to 'null'. Which is stricter JSON than the Python
version.

"""

# we want stricter JSON, so convert NaN, Inf, -Inf --> 'null'
nan_str = inf_str = neg_inf_str = 'null'

# uses code from official python json.encoder module. Same licence applies.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

woah gnarly

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may be naive, but can't we just extend our current encoder with a check for is NaN, Inf? like how we do with datetimes https://github.com/plotly/python-api/blob/master/plotly/utils.py#L224

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

situation kinda stinks, I actually documented it because this same thing was confusing to me as well:

https://github.com/plotly/python-api/blob/master/plotly/utils.py#L135-150

We only get a crack at stuff if the JSONEncoder can't encode it. What's more, as is mentioned in the SO post, when allow_nan=False, we also don't get a crack at it in our encoder. bummer!

Here's an example:

import json
import numpy

class Foo(object):
    pass

class NullEncoder(json.JSONEncoder):
    def encode_as_true(self, obj):
        return None

    def default(self, obj):
        return self.encode_as_true(obj)

json.dumps(Foo(), cls=NullEncoder)  # here, it works
# 'null'
json.dumps(numpy.ma.core.masked, cls=NullEncoder)  # and again here
# 'null'
json.dumps(float('NaN'), cls=NullEncoder)  # no shot! this doesn't work as expected!
# 'NaN'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, that's pretty wild. well, that looks good then, nice diggin!

def iterencode(self, o, _one_shot=False):
"""
Encode the given object and yield each string
representation as available.

For example::

for chunk in JSONEncoder().iterencode(bigobject):
mysocket.write(chunk)

"""
if self.check_circular:
markers = {}
else:
markers = None
if self.ensure_ascii:
_encoder = json.encoder.encode_basestring_ascii
else:
_encoder = json.encoder.encode_basestring
if self.encoding != 'utf-8':
def _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):
if isinstance(o, str):
o = o.decode(_encoding)
return _orig_encoder(o)

def floatstr(o, allow_nan=self.allow_nan,
_repr=json.encoder.FLOAT_REPR, _inf=json.encoder.INFINITY,
_neginf=-json.encoder.INFINITY):
# Check for specials. Note that this type of test is processor
# and/or platform-specific, so do tests which don't depend on the
# internals.

# *any* two NaNs are not equivalent (even to itself) try:
# float('NaN') == float('NaN')
if o != o:
text = self.nan_str
elif o == _inf:
text = self.inf_str
elif o == _neginf:
text = self.neg_inf_str
else:
return _repr(o)

if not allow_nan:
raise ValueError(
"Out of range float values are not JSON compliant: " +
repr(o))

return text

_iterencode = json.encoder._make_iterencode(
markers, self.default, _encoder, self.indent, floatstr,
self.key_separator, self.item_separator, self.sort_keys,
self.skipkeys, _one_shot)
return _iterencode(o, 0)

def default(self, obj):
"""
Accept an object (of unknown type) and try to encode with priority:
Expand Down
2 changes: 1 addition & 1 deletion plotly/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.6.10'
__version__ = '1.6.11'