From d93b2954fe76dd481767e8f31269a5a0bd6c9b51 Mon Sep 17 00:00:00 2001 From: "Erlend E. Aasland" Date: Sat, 29 Feb 2020 22:00:47 +0100 Subject: [PATCH] bpo-24518: json.dumps should accept key function for sort_keys Co-authored-by: Catherine Devlin --- Lib/json/__init__.py | 3 ++- Lib/json/encoder.py | 10 ++++++++-- Lib/test/test_json/test_sort.py | 17 +++++++++++++++++ .../2020-02-29-21-54-13.bpo-24518.Tiq8PU.rst | 1 + 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 Lib/test/test_json/test_sort.py create mode 100644 Misc/NEWS.d/next/Library/2020-02-29-21-54-13.bpo-24518.Tiq8PU.rst diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index 2c52bdeba6..d58481fcb8 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -154,7 +154,8 @@ def dump(obj, fp, *, skipkeys=False, ensure_ascii=True, check_circular=True, of obj or raise TypeError. The default simply raises TypeError. If *sort_keys* is true (default: ``False``), then the output of - dictionaries will be sorted by key. + dictionaries will be sorted by key. If *sort_keys* is a callable, + it will be used as the sort key function. To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the ``.default()`` method to serialize additional types), specify it with diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py index c8c78b9c23..ddbbfb017e 100644 --- a/Lib/json/encoder.py +++ b/Lib/json/encoder.py @@ -127,6 +127,7 @@ class JSONEncoder(object): If sort_keys is true, then the output of dictionaries will be sorted by key; this is useful for regression tests to ensure that JSON serializations can be compared on a day-to-day basis. + If sort_keys is a callable, it will be used as the sort key function. If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that @@ -244,7 +245,8 @@ class JSONEncoder(object): if (_one_shot and c_make_encoder is not None - and self.indent is None): + and self.indent is None + and not callable(self.sort_keys)): _iterencode = c_make_encoder( markers, self.default, _encoder, self.indent, self.key_separator, self.item_separator, self.sort_keys, @@ -350,7 +352,11 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr, item_separator = _item_separator first = True if _sort_keys: - items = sorted(dct.items()) + if callable(_sort_keys): + sorter = lambda kv: _sort_keys(kv[0]) + else: + sorter = lambda kv: kv[0] + items = sorted(dct.items(), key=sorter) else: items = dct.items() for key, value in items: diff --git a/Lib/test/test_json/test_sort.py b/Lib/test/test_json/test_sort.py new file mode 100644 index 0000000000..29b469a289 --- /dev/null +++ b/Lib/test/test_json/test_sort.py @@ -0,0 +1,17 @@ +from io import StringIO +from test.test_json import PyTest, CTest + + +class TestSort: + # Issue 24518 + def test_callable_sort_keys(self): + data = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5} + is_odd = lambda n: n % 2 + sio = StringIO() + self.json.dump(data, sio, sort_keys=is_odd) + result = sio.getvalue() + self.assertTrue(result.find("2") < result.find("1")) + self.assertTrue(result.find("4") < result.find("3")) + +class TestPyIndent(TestSort, PyTest): pass +class TestCIndent(TestSort, CTest): pass diff --git a/Misc/NEWS.d/next/Library/2020-02-29-21-54-13.bpo-24518.Tiq8PU.rst b/Misc/NEWS.d/next/Library/2020-02-29-21-54-13.bpo-24518.Tiq8PU.rst new file mode 100644 index 0000000000..eeeee9d99f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-02-29-21-54-13.bpo-24518.Tiq8PU.rst @@ -0,0 +1 @@ +``json.dumps()`` now accepts key function for ``sort_keys``. Patch by Catherine Devlin. -- 2.22.0