Merge pull request #33 from alexwlchan/sort-keys
- ID
aa4dce1- date
2025-05-05 07:37:07+00:00- author
Alex Chan <alex@alexwlchan.net>- parents
d991f3a,059afac- message
Merge pull request #33 from alexwlchan/sort-keys Add a `sort_keys` parameter to `write_js`- changed files
5 files, 60 additions, 12 deletions
Changed files
CHANGELOG.md (3923) → CHANGELOG.md (4074)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a9a80c0..c64ee04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# CHANGELOG
+## v1.3.0 - 2025-05-05
+
+Add a `sort_keys` parameter to `write_js`.
+If `True`, dictionaries with be serialised to JSON sorted by key.
+Default `False`.
+
## v1.2.3 - 2025-05-04
Tweak the error message introduced in v1.2.2 -- JSON objects are **name**/value pairs, not key/value pairs.
src/javascript_data_files/__init__.py (6783) → src/javascript_data_files/__init__.py (6833)
diff --git a/src/javascript_data_files/__init__.py b/src/javascript_data_files/__init__.py
index 2f72ea2..bbdecc4 100644
--- a/src/javascript_data_files/__init__.py
+++ b/src/javascript_data_files/__init__.py
@@ -74,6 +74,7 @@ def write_js(
*,
value: typing.Any,
varname: str,
+ sort_keys: bool = False,
) -> None:
"""
Write a JavaScript "data file".
@@ -88,7 +89,7 @@ def write_js(
'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
"""
- js_string = encode_as_js(value, varname)
+ js_string = encode_as_js(value, varname, sort_keys=sort_keys)
if isinstance(p, io.TextIOBase):
p.write(js_string)
src/javascript_data_files/encoder.py (1155) → src/javascript_data_files/encoder.py (1253)
diff --git a/src/javascript_data_files/encoder.py b/src/javascript_data_files/encoder.py
index 6720d82..3f5087d 100644
--- a/src/javascript_data_files/encoder.py
+++ b/src/javascript_data_files/encoder.py
@@ -26,18 +26,18 @@ class HumanReadableEncoder(json.JSONEncoder):
return super().encode(o)
-def encode_as_json(value: typing.Any) -> str:
+def encode_as_json(value: typing.Any, *, sort_keys: bool = False) -> str:
"""
Convert a Python value to a JSON-encoded string.
"""
- return json.dumps(value, indent=2, cls=HumanReadableEncoder)
+ return json.dumps(value, indent=2, sort_keys=sort_keys, cls=HumanReadableEncoder)
-def encode_as_js(value: typing.Any, varname: str) -> str:
+def encode_as_js(value: typing.Any, varname: str, *, sort_keys: bool = False) -> str:
"""
Convert a Python value to a JSON-encoded JavaScript value.
"""
- json_string = encode_as_json(value)
+ json_string = encode_as_json(value, sort_keys=sort_keys)
js_string = f"const {varname} = {json_string};\n"
return js_string
tests/test_encoder.py (2541) → tests/test_encoder.py (2946)
diff --git a/tests/test_encoder.py b/tests/test_encoder.py
index 16163ca..e139915 100644
--- a/tests/test_encoder.py
+++ b/tests/test_encoder.py
@@ -17,6 +17,21 @@ def test_it_pretty_prints_json() -> None:
)
+def test_it_sorts_keys() -> None:
+ """
+ If you pass `sort_keys=True`, it sorts the keys in JSON objects.
+ """
+ assert (
+ encode_as_json({"sides": 5, "colour": "red"}, sort_keys=False)
+ == '{\n "sides": 5,\n "colour": "red"\n}'
+ )
+
+ assert (
+ encode_as_json({"sides": 5, "colour": "red"}, sort_keys=True)
+ == '{\n "colour": "red",\n "sides": 5\n}'
+ )
+
+
def test_a_list_of_ints_is_not_split_over_multiple_lines() -> None:
"""
If there's a list of small integers, they're printed on one line
tests/test_javascript_data_files.py (17712) → tests/test_javascript_data_files.py (18531)
diff --git a/tests/test_javascript_data_files.py b/tests/test_javascript_data_files.py
index 3446a02..79553a0 100644
--- a/tests/test_javascript_data_files.py
+++ b/tests/test_javascript_data_files.py
@@ -153,7 +153,7 @@ class TestWriteJs:
Tests for the ``write_js()`` function.
"""
- def test_can_write_file(self, js_path: pathlib.Path) -> None:
+ def test_write_file(self, js_path: pathlib.Path) -> None:
"""
Writing to a file stores the correct JavaScript string.
"""
@@ -166,7 +166,7 @@ class TestWriteJs:
== 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
- def test_can_write_to_str(self, tmp_path: pathlib.Path) -> None:
+ def test_write_to_str(self, tmp_path: pathlib.Path) -> None:
"""
It can write to a path passed as a ``str``.
"""
@@ -181,7 +181,7 @@ class TestWriteJs:
== 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
- def test_can_write_to_path(self, tmp_path: pathlib.Path) -> None:
+ def test_write_to_path(self, tmp_path: pathlib.Path) -> None:
"""
It can write to a path passed as a ``pathlib.Path``.
"""
@@ -196,7 +196,7 @@ class TestWriteJs:
== 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
- def test_can_write_to_file(self, tmp_path: pathlib.Path) -> None:
+ def test_write_to_file(self, tmp_path: pathlib.Path) -> None:
"""
It can write to a file.
"""
@@ -212,7 +212,7 @@ class TestWriteJs:
== 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
- def test_can_write_to_binary_file(self, tmp_path: pathlib.Path) -> None:
+ def test_write_to_binary_file(self, tmp_path: pathlib.Path) -> None:
"""
It can write to a file opened in binary mode.
"""
@@ -228,7 +228,7 @@ class TestWriteJs:
== 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
- def test_can_write_to_string_buffer(self) -> None:
+ def test_write_to_string_buffer(self) -> None:
"""
It can write to a string buffer.
"""
@@ -243,7 +243,7 @@ class TestWriteJs:
== 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
- def test_can_write_to_bytes_buffer(self) -> None:
+ def test_write_to_bytes_buffer(self) -> None:
"""
It can write to a binary buffer.
"""
@@ -258,6 +258,32 @@ class TestWriteJs:
== b'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
)
+ def test_write_with_sort_keys(self, tmp_path: pathlib.Path) -> None:
+ """
+ If you pass `sort_keys=True`, it sorts the keys in JSON objects.
+ """
+ red_pentagon = {"sides": 5, "colour": "red"}
+
+ unsorted_path = tmp_path / "unsorted.js"
+ sorted_path = tmp_path / "sorted.js"
+
+ write_js(
+ unsorted_path,
+ value=red_pentagon,
+ varname="redPentagon",
+ sort_keys=False,
+ )
+ assert (
+ unsorted_path.read_text()
+ == 'const redPentagon = {\n "sides": 5,\n "colour": "red"\n};\n'
+ )
+
+ write_js(sorted_path, value=red_pentagon, varname="redPentagon", sort_keys=True)
+ assert (
+ sorted_path.read_text()
+ == 'const redPentagon = {\n "colour": "red",\n "sides": 5\n};\n'
+ )
+
def test_fails_if_file_is_read_only(self, tmp_path: pathlib.Path) -> None:
"""
It cannot write to a file open in read-only mode.