Add a method for appending a key/value pair to a JSON object
- ID
4e143d8- date
2024-08-17 20:21:46+00:00- author
Alex Chan <alex@alexwlchan.net>- parent
1ffbd24- message
Add a method for appending a key/value pair to a JSON object- changed files
3 files, 85 additions, 3 deletions
Changed files
README.md (664) → README.md (765)
diff --git a/README.md b/README.md
index c16bab5..194cd13 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,8 @@ Think of this module as the JSON module, but for JavaScript files.
* You can read a JavaScript file with `read_js(path, varname)`
* You can write a JavaScript file with `write_js(path, value, varname)`
-* You can append an item to a JavaScript array with `append_js_array_value(path, value)`
+* You can append an item to a JavaScript array with `append_to_js_array(path, value)`
+* You can append a key-value pair to a JavaScript object with `append_to_js_object(path, key, value)`
## Installation
src/javascript/__init__.py (2984) → src/javascript/__init__.py (4638)
diff --git a/src/javascript/__init__.py b/src/javascript/__init__.py
index b8ebdc4..b0bdd3c 100644
--- a/src/javascript/__init__.py
+++ b/src/javascript/__init__.py
@@ -104,3 +104,54 @@ def append_to_js_array(p: pathlib.Path | str, *, value: typing.Any) -> None:
return
raise ValueError(f"End of file {p!r} does not look like an array")
+
+
+def append_to_js_object(p: pathlib.Path | str, *, key: str, value: typing.Any) -> None:
+ """
+ Append a single key/value pair to a JSON object in a JavaScript "data file".
+
+ Example:
+
+ >>> write_js('shape.js', value={'colour': 'red', 'sides': 5}, varname='redPentagon')
+ >>> append_to_js_object('shape.js', key='sideLengths', value=[5, 5, 6, 6, 6])
+ >>> read_js('shape.js', varname='redPentagon')
+ {'colour': 'red', 'sides': 5, 'sideLengths': [5, 5, 6, 6, 6]}
+
+ If you have a large file, this is usually faster than reading,
+ appending, and re-writing the entire file.
+
+ """
+ file_size = os.stat(p).st_size
+
+ enc_key = json.dumps(key)
+ enc_value = json.dumps(value)
+
+ json_to_append = f",\n {enc_key}: {enc_value}\n}};\n".encode("utf8")
+
+ with open(p, "rb+") as out_file:
+ out_file.seek(file_size - 4)
+
+ if out_file.read(4) == b"\n};\n":
+ out_file.seek(file_size - 4)
+ out_file.write(json_to_append)
+ return
+
+ out_file.seek(file_size - 3)
+ if out_file.read(3) in {b"\n};", b"};\n"}:
+ out_file.seek(file_size - 3)
+ out_file.write(json_to_append)
+ return
+
+ out_file.seek(file_size - 2)
+ if out_file.read(2) in {b"};", b"}\n"}:
+ out_file.seek(file_size - 2)
+ out_file.write(json_to_append)
+ return
+
+ out_file.seek(file_size - 1)
+ if out_file.read(1) == b"}":
+ out_file.seek(file_size - 1)
+ out_file.write(json_to_append)
+ return
+
+ raise ValueError(f"End of file {p!r} does not look like an object")
tests/test_javascript.py (5019) → tests/test_javascript.py (6224)
diff --git a/tests/test_javascript.py b/tests/test_javascript.py
index 6ba72e9..a83f910 100644
--- a/tests/test_javascript.py
+++ b/tests/test_javascript.py
@@ -3,7 +3,7 @@ import typing
import pytest
-from javascript import append_to_js_array, read_js, write_js
+from javascript import append_to_js_array, append_to_js_object, read_js, write_js
@pytest.fixture
@@ -85,7 +85,7 @@ class TestAppendToArray:
'const fruit = ["apple", "banana", "coconut"];\n',
'const fruit = ["apple","banana", "coconut"];',
'const fruit = [\n "apple",\n "banana",\n "coconut"\n];\n',
- 'const fruit = [\n "apple",\n "banana",\n "coconut"\n]',
+ 'const fruit = [\n "apple",\n "banana",\n "coconut"\n];',
'const fruit = [\n "apple",\n "banana",\n "coconut"\n]',
],
)
@@ -119,6 +119,36 @@ class TestAppendToArray:
append_to_js_array(js_path, value=["yellow"])
+class TestAppendToObject:
+ @pytest.mark.parametrize(
+ "text",
+ [
+ 'const redPentagon = {"colour": "red", "sides": 5};\n',
+ 'const redPentagon = {"colour": "red", "sides": 5};',
+ 'const redPentagon = {\n "colour": "red",\n "sides": 5\n};\n',
+ 'const redPentagon = {\n "colour": "red",\n "sides": 5\n};',
+ 'const redPentagon = {\n "colour": "red",\n "sides": 5\n}',
+ ],
+ )
+ def test_can_append_array_value(self, js_path: pathlib.Path, text: str) -> None:
+ js_path.write_text(text)
+
+ append_to_js_object(js_path, key="sideLengths", value=[5, 5, 6, 6, 6])
+ assert read_js(js_path, varname="redPentagon") == {
+ "colour": "red",
+ "sides": 5,
+ "sideLengths": [5, 5, 6, 6, 6],
+ }
+
+ def test_error_if_file_doesnt_look_like_object(self, js_path: pathlib.Path) -> None:
+ shapes = ["apple", "banana", "cherry"]
+
+ write_js(js_path, value=shapes, varname="fruit")
+
+ with pytest.raises(ValueError, match="does not look like an object"):
+ append_to_js_object(js_path, key="sideLengths", value=[5, 5, 6, 6, 6])
+
+
class TestRoundTrip:
@pytest.mark.parametrize(
"value",