[tor-commits] [sbws/master] chg: json: Create custom JSON encoder/decoder

juga at torproject.org juga at torproject.org
Tue Apr 14 13:54:37 UTC 2020


commit f19738f1c24316399c0928f62130ec133cee5db0
Author: juga0 <juga at riseup.net>
Date:   Sat Mar 14 16:52:21 2020 +0000

    chg: json: Create custom JSON encoder/decoder
    
    to be able to serialize/deserailize datetime in the state file.
---
 sbws/util/json.py            | 44 ++++++++++++++++++++++++++++++++++++
 tests/unit/util/test_json.py | 54 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 98 insertions(+)

diff --git a/sbws/util/json.py b/sbws/util/json.py
new file mode 100644
index 0000000..f4f8130
--- /dev/null
+++ b/sbws/util/json.py
@@ -0,0 +1,44 @@
+"""JSON custom serializers and deserializers."""
+import datetime
+import json
+
+from .timestamps import DateTimeSeq, DateTimeIntSeq
+
+
+class CustomEncoder(json.JSONEncoder):
+    """JSONEncoder that serializes datetime to ISO 8601 string."""
+
+    def default(self, obj):
+        if isinstance(obj, DateTimeSeq) or isinstance(obj, DateTimeIntSeq):
+            return [self.default(i) for i in obj.list()]
+        if isinstance(obj, datetime.datetime):
+            return obj.replace(microsecond=0).isoformat()
+        else:
+            return super().default(obj)
+
+
+class CustomDecoder(json.JSONDecoder):
+    """JSONDecoder that deserializes ISO 8601 string to datetime."""
+
+    def decode(self, s, **kwargs):
+        decoded = super().decode(s, **kwargs)
+        return self.process(decoded)
+
+    def process(self, obj):
+        if isinstance(obj, list) and obj:
+            return [self.process(item) for item in obj]
+        if isinstance(obj, dict):
+            return {key: self.process(value) for key, value in obj.items()}
+        if isinstance(obj, str):
+            try:
+                return datetime.datetime.strptime(obj, "%Y-%m-%dT%H:%M:%S")
+            except ValueError:
+                try:
+                    datetime.datetime.strptime(
+                        obj, "%Y-%m-%dT%H:%M:%S.%f"
+                    ).replace(microsecond=0)
+                except ValueError:
+                    pass
+            except TypeError:
+                pass
+        return obj
diff --git a/tests/unit/util/test_json.py b/tests/unit/util/test_json.py
new file mode 100644
index 0000000..05e0bf3
--- /dev/null
+++ b/tests/unit/util/test_json.py
@@ -0,0 +1,54 @@
+"""json.py unit tests."""
+import json
+
+from sbws.util.json import CustomDecoder, CustomEncoder
+
+STATE = """{
+    "min_perc_reached": null,
+    "recent_consensus_count": [
+        "2020-03-04T10:00:00",
+        "2020-03-05T10:00:00",
+        "2020-03-06T10:00:00"
+    ],
+    "recent_measurement_attempt": [
+        [
+            "2020-03-04T10:00:00",
+            2
+        ],
+        [
+            "2020-03-05T10:00:00",
+            2
+        ],
+        [
+            "2020-03-06T10:00:00",
+            2
+        ]
+    ],
+    "recent_priority_list": [
+        "2020-03-04T10:00:00",
+        "2020-03-05T10:00:00",
+        "2020-03-06T10:00:00"
+    ],
+    "recent_priority_relay": [
+        [
+            "2020-03-04T10:00:00",
+            2
+        ],
+        [
+            "2020-03-05T10:00:00",
+            2
+        ],
+        [
+            "2020-03-06T10:00:00",
+            2
+        ]
+    ],
+    "scanner_started": "2020-03-14T16:15:22",
+    "uuid": "x"
+}"""
+
+
+def test_decode_encode_roundtrip():
+    d = json.loads(STATE, cls=CustomDecoder)
+    s = json.dumps(d, cls=CustomEncoder, indent=4, sort_keys=True)
+    assert s == STATE





More information about the tor-commits mailing list