Skip to content

Commit

Permalink
Merge pull request plotly#3386 from plotly/json_remove_sort_keys
Browse files Browse the repository at this point in the history
Do not sort keys during json serialization
  • Loading branch information
nicolaskruchten authored Sep 16, 2021
2 parents 0a83329 + c25e710 commit ce0ed07
Show file tree
Hide file tree
Showing 7 changed files with 26 additions and 14 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [next] - ???

### Fixed
- Fixed error when serializing dict with mix of string and non-string keys [#3380](https://github.com/plotly/plotly.py/issues/3380)

### Updated
- The JSON serialization engines no longer sort their keys [#3380](https://github.com/plotly/plotly.py/issues/3380)

## [5.3.1] - 2021-08-31

### Updated
Expand Down
6 changes: 3 additions & 3 deletions packages/python/plotly/plotly/io/_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def to_json_plotly(plotly_object, pretty=False, engine=None):
# Dump to a JSON string and return
# --------------------------------
if engine == "json":
opts = {"sort_keys": True}
opts = {}
if pretty:
opts["indent"] = 2
else:
Expand All @@ -124,7 +124,7 @@ def to_json_plotly(plotly_object, pretty=False, engine=None):
return json.dumps(plotly_object, cls=PlotlyJSONEncoder, **opts)
elif engine == "orjson":
JsonConfig.validate_orjson()
opts = orjson.OPT_SORT_KEYS | orjson.OPT_SERIALIZE_NUMPY
opts = orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY

if pretty:
opts |= orjson.OPT_INDENT_2
Expand Down Expand Up @@ -462,7 +462,7 @@ def clean_to_json_compatible(obj, **kwargs):
return obj

if isinstance(obj, dict):
return {str(k): clean_to_json_compatible(v, **kwargs) for k, v in obj.items()}
return {k: clean_to_json_compatible(v, **kwargs) for k, v in obj.items()}
elif isinstance(obj, (list, tuple)):
if obj:
# Must process list recursively even though it may be slow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def test_move_nested_trace_properties(self):
},
)

self.assertEqual(pio.to_json(templated_fig), pio.to_json(expected_fig))
self.assertEqual(templated_fig.to_dict(), expected_fig.to_dict())

def test_move_nested_trace_properties_existing_traces(self):
fig = go.Figure(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_deepcopy_figure(fig1):
fig_copied = copy.deepcopy(fig1)

# Contents should be equal
assert pio.to_json(fig_copied) == pio.to_json(fig1)
assert fig_copied.to_dict() == fig1.to_dict()

# Identities should be distinct
assert fig_copied is not fig1
Expand All @@ -50,7 +50,7 @@ def test_deepcopy_figure_subplots(fig_subplots):
fig_copied = copy.deepcopy(fig_subplots)

# Contents should be equal
assert pio.to_json(fig_copied) == pio.to_json(fig_subplots)
assert fig_copied.to_dict() == fig_subplots.to_dict()

# Subplot metadata should be equal
assert fig_subplots._grid_ref == fig_copied._grid_ref
Expand All @@ -66,7 +66,7 @@ def test_deepcopy_figure_subplots(fig_subplots):
fig_copied.add_bar(y=[0, 0, 1], row=1, col=2)

# And contents should be still equal
assert pio.to_json(fig_copied) == pio.to_json(fig_subplots)
assert fig_copied.to_dict() == fig_subplots.to_dict()


def test_deepcopy_layout(fig1):
Expand All @@ -91,21 +91,21 @@ def test_pickle_figure_round_trip(fig1):
fig_copied = pickle.loads(pickle.dumps(fig1))

# Contents should be equal
assert pio.to_json(fig_copied) == pio.to_json(fig1)
assert fig_copied.to_dict() == fig1.to_dict()


def test_pickle_figure_subplots_round_trip(fig_subplots):
fig_copied = pickle.loads(pickle.dumps(fig_subplots))

# Contents should be equal
assert pio.to_json(fig_copied) == pio.to_json(fig_subplots)
assert fig_copied.to_dict() == fig_subplots.to_dict()

# Should be possible to add new trace to subplot location
fig_subplots.add_bar(y=[0, 0, 1], row=1, col=2)
fig_copied.add_bar(y=[0, 0, 1], row=1, col=2)

# And contents should be still equal
assert pio.to_json(fig_copied) == pio.to_json(fig_subplots)
assert fig_copied.to_dict() == fig_subplots.to_dict()


def test_pickle_layout(fig1):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ def fig1(request):
opts = {
"separators": (",", ":"),
"cls": plotly.utils.PlotlyJSONEncoder,
"sort_keys": True,
}
pretty_opts = {"indent": 2, "cls": plotly.utils.PlotlyJSONEncoder, "sort_keys": True}
pretty_opts = {"indent": 2, "cls": plotly.utils.PlotlyJSONEncoder}


# to_json
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def datetime_array(request, datetime_value):
def test_graph_object_input(engine, pretty):
scatter = go.Scatter(x=[1, 2, 3], y=np.array([4, 5, 6]))
result = pio.to_json_plotly(scatter, engine=engine)
expected = """{"type":"scatter","x":[1,2,3],"y":[4,5,6]}"""
expected = """{"x":[1,2,3],"y":[4,5,6],"type":"scatter"}"""
assert result == expected
check_roundtrip(result, engine=engine, pretty=pretty)

Expand Down Expand Up @@ -215,3 +215,9 @@ def test_nonstring_key(engine, pretty):
value = build_test_dict({0: 1})
result = pio.to_json_plotly(value, engine=engine)
check_roundtrip(result, engine=engine, pretty=pretty)


def test_mixed_string_nonstring_key(engine, pretty):
value = build_test_dict({0: 1, "a": 2})
result = pio.to_json_plotly(value, engine=engine)
check_roundtrip(result, engine=engine, pretty=pretty)
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ def test_default_mpl_plot_generates_expected_html(self):
# just make sure a few of the parts are in here
# like PlotlyOfflineTestCase(TestCase) in test_core
self.assertTrue(data_json in html) # data is in there
self.assertTrue(layout_json in html) # layout is in there too
self.assertTrue(PLOTLYJS in html) # and the source code
# and it's an <html> doc
self.assertTrue(html.startswith("<html>") and html.endswith("</html>"))

0 comments on commit ce0ed07

Please sign in to comment.