From 274975f2854b425c1fee8182f073f36c18bba2b8 Mon Sep 17 00:00:00 2001 From: esoteric-ephemera Date: Fri, 6 Dec 2024 10:48:41 -0800 Subject: [PATCH] add optional test for serializing extended JSON --- src/monty/json.py | 4 +++- tests/test_json.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/monty/json.py b/src/monty/json.py index 4e5fbbad..4dfe6024 100644 --- a/src/monty/json.py +++ b/src/monty/json.py @@ -865,7 +865,9 @@ def decode(self, s): :return: Object. """ if bson is not None: - d = json_util.loads(s) + # need to pass `json_options` to ensure that datetimes are not + # converted by BSON + d = json_util.loads(s, json_options=json_util.JSONOptions(tz_aware=True)) elif orjson is not None: try: d = orjson.loads(s) diff --git a/tests/test_json.py b/tests/test_json.py index e59e89d5..bd7bba57 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -1068,3 +1068,31 @@ def test_enum(self): assert d_ == {"v": "value_a"} na2 = EnumAsDict.from_dict(d_) assert na2 == na1 + + @pytest.mark.skipif(ObjectId is None, reason="bson not present") + def test_extended_json(self): + from bson import json_util + + ext_json_dict = { + "datetime": datetime.datetime.now(datetime.timezone.utc), + "NaN": float("NaN"), + "infinity": float("inf"), + "-infinity": -float("inf"), + } + ext_json_str = json_util.dumps(ext_json_dict) + + not_serialized = json.loads(ext_json_str) + assert all(isinstance(v, dict) for v in not_serialized.values()) + + reserialized = MontyDecoder().decode(ext_json_str) + for k, v in ext_json_dict.items(): + if k == "datetime": + # BSON's json_util only saves datetimes up to microseconds + assert reserialized[k].timestamp() == pytest.approx( + v.timestamp(), abs=1e-3 + ) + elif k == "NaN": + assert np.isnan(reserialized[k]) + else: + assert v == reserialized[k] + assert not isinstance(reserialized[k], dict)