From 0a2289a53b03bf36f55149eee51fd6890af13659 Mon Sep 17 00:00:00 2001 From: Noah Negin-Ulster Date: Fri, 30 Dec 2022 14:21:21 -0500 Subject: [PATCH] feat(serializer): preserve key ordering of OrderedDict BREAKING CHANGE: Key order is now preserved if using OrderedDict in both the Amber serializer and JSON serializer. --- src/syrupy/extensions/amber/serializer.py | 7 ++++++- src/syrupy/extensions/json/__init__.py | 15 +++++++++++---- .../__snapshots__/test_amber_serializer.ambr | 6 ++++++ .../extensions/amber/test_amber_serializer.py | 12 +++++++++++- .../test_json_serializer/test_ordered_dict.json | 7 +++++++ .../extensions/json/test_json_serializer.py | 12 +++++++++++- 6 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 tests/syrupy/extensions/json/__snapshots__/test_json_serializer/test_ordered_dict.json diff --git a/src/syrupy/extensions/amber/serializer.py b/src/syrupy/extensions/amber/serializer.py index fd996ed4..3409429b 100644 --- a/src/syrupy/extensions/amber/serializer.py +++ b/src/syrupy/extensions/amber/serializer.py @@ -1,4 +1,5 @@ import os +from collections import OrderedDict from types import ( GeneratorType, MappingProxyType, @@ -246,9 +247,13 @@ def serialize_namedtuple(cls, data: NamedTuple, **kwargs: Any) -> str: def serialize_dict( cls, data: Dict["PropertyName", "SerializableData"], **kwargs: Any ) -> str: + keys = ( + data.keys() if isinstance(data, (OrderedDict,)) else cls.sort(data.keys()) + ) + return cls.__serialize_iterable( data=data, - resolve_entries=(cls.sort(data.keys()), item_getter, None), + resolve_entries=(keys, item_getter, None), open_paren="{", close_paren="}", separator=": ", diff --git a/src/syrupy/extensions/json/__init__.py b/src/syrupy/extensions/json/__init__.py index d35a1efa..e9f0113c 100644 --- a/src/syrupy/extensions/json/__init__.py +++ b/src/syrupy/extensions/json/__init__.py @@ -1,5 +1,6 @@ import datetime import json +from collections import OrderedDict from types import GeneratorType from typing import ( TYPE_CHECKING, @@ -69,8 +70,14 @@ def _filter( filtered_dct: Dict[Any, Any] if isinstance(data, (dict,)): - filtered_dct = {} - for key, value in data.items(): + filtered_dct = OrderedDict() + keys = ( + cls.sort(data.keys()) + if not isinstance(data, (OrderedDict,)) + else data.keys() + ) + for key in keys: + value = data[key] if exclude and exclude(prop=key, path=path): continue if not isinstance(key, (str,)): @@ -86,7 +93,7 @@ def _filter( return filtered_dct if cls.__is_namedtuple(data): - filtered_dct = {} + filtered_dct = OrderedDict() for key in cls.sort(data._fields): value = getattr(data, key) filtered_dct[key] = cls._filter( @@ -135,4 +142,4 @@ def serialize( data = self._filter( data=data, depth=0, path=(), exclude=exclude, matcher=matcher ) - return json.dumps(data, indent=2, ensure_ascii=False, sort_keys=True) + "\n" + return json.dumps(data, indent=2, ensure_ascii=False, sort_keys=False) + "\n" diff --git a/tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr b/tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr index 9d2d5417..9ce2c540 100644 --- a/tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr +++ b/tests/syrupy/extensions/amber/__snapshots__/test_amber_serializer.ambr @@ -310,6 +310,12 @@ # name: test_numbers.2 0.3333333333333333 # --- +# name: test_ordered_dict + OrderedDict({ + 'b': 0, + 'a': 1, + }) +# --- # name: test_parameter_with_dot[value.with.dot] 'value.with.dot' # --- diff --git a/tests/syrupy/extensions/amber/test_amber_serializer.py b/tests/syrupy/extensions/amber/test_amber_serializer.py index 2385b13b..58b13f8c 100644 --- a/tests/syrupy/extensions/amber/test_amber_serializer.py +++ b/tests/syrupy/extensions/amber/test_amber_serializer.py @@ -1,4 +1,7 @@ -from collections import namedtuple +from collections import ( + OrderedDict, + namedtuple, +) import pytest @@ -218,3 +221,10 @@ def test_parameter_with_dot(parameter_with_dot, snapshot): def test_doubly_parametrized(parameter_1, parameter_2, snapshot): assert parameter_1 == snapshot assert parameter_2 == snapshot + + +def test_ordered_dict(snapshot): + d = OrderedDict() + d["b"] = 0 + d["a"] = 1 + assert snapshot == d diff --git a/tests/syrupy/extensions/json/__snapshots__/test_json_serializer/test_ordered_dict.json b/tests/syrupy/extensions/json/__snapshots__/test_json_serializer/test_ordered_dict.json new file mode 100644 index 00000000..358bbd8b --- /dev/null +++ b/tests/syrupy/extensions/json/__snapshots__/test_json_serializer/test_ordered_dict.json @@ -0,0 +1,7 @@ +{ + "b": 0, + "a": { + "b": true, + "a": false + } +} diff --git a/tests/syrupy/extensions/json/test_json_serializer.py b/tests/syrupy/extensions/json/test_json_serializer.py index 9c1e359c..dde6d49b 100644 --- a/tests/syrupy/extensions/json/test_json_serializer.py +++ b/tests/syrupy/extensions/json/test_json_serializer.py @@ -1,4 +1,7 @@ -from collections import namedtuple +from collections import ( + OrderedDict, + namedtuple, +) import pytest @@ -222,3 +225,10 @@ def test_parameter_with_dot(parameter_with_dot, snapshot_json): def test_doubly_parametrized(parameter_1, parameter_2, snapshot_json): assert parameter_1 == snapshot_json assert parameter_2 == snapshot_json + + +def test_ordered_dict(snapshot_json): + d = OrderedDict() + d["b"] = 0 + d["a"] = OrderedDict(b=True, a=False) + assert snapshot_json == d