Skip to content

Commit 458bcf7

Browse files
Maciej Katafiaszmathrick
authored andcommitted
Don't error out when logging non-JSON serialisable values
The serialiser now falls back on repr() to ensure some usable value can always be logged, even if no other serialiser exists for the value. Fixes #515
1 parent c311929 commit 458bcf7

File tree

4 files changed

+29
-4
lines changed

4 files changed

+29
-4
lines changed

eliot/json.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
from datetime import date, time
88
import platform
99

10+
from ._util import saferepr
11+
1012

1113
class EliotJSONEncoder(json.JSONEncoder):
1214
"""
@@ -88,7 +90,8 @@ def json_default(o: object) -> object:
8890
if isinstance(o, polars.Datetime):
8991
return o.isoformat()
9092

91-
raise TypeError("Unsupported type")
93+
# Fall back to repr() to get _some_ useful value to log
94+
return saferepr(o)
9295

9396

9497
if platform.python_implementation() == "PyPy":

eliot/tests/test_json.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ def test_numpy_not_imported(self):
7373
7474
This ensures NumPy isn't a hard dependency.
7575
"""
76-
with self.assertRaises(TypeError):
77-
dumps([object()], default=json_default)
76+
weird_val = object()
77+
self.assertEqual(dumps([weird_val], default=json_default), dumps([repr(weird_val)]))
7878
self.assertEqual(dumps(12, default=json_default), "12")
7979

8080
@skipUnless(np, "NumPy is not installed.")
@@ -206,3 +206,11 @@ class TestDataClass:
206206
obj = TestDataClass(name="test", value=42)
207207
serialized = loads(dumps(obj, default=json_default))
208208
self.assertEqual(serialized, {"name": "test", "value": 42})
209+
210+
def test_unserializable(self):
211+
"""Test that even values without dedicated JSON serialization
212+
support dump without errors."""
213+
def unserializable():
214+
pass
215+
216+
self.assertEqual(dumps(unserializable, default=json_default), dumps(repr(unserializable)))

eliot/tests/test_output.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ def test_JSON(self):
121121
logger.write(
122122
{"message_type": "type", "foo": "will become object()"}, serializer
123123
)
124-
self.assertRaises(TypeError, logger.validate)
124+
# No exception should be raised, even on values that are not
125+
# normally JSON-serialisable like object()
126+
logger.validate()
125127

126128
@skipUnless(np, "NumPy is not installed.")
127129
def test_EliotJSONEncoder(self):
@@ -144,6 +146,14 @@ def test_JSON_custom_encoder(self):
144146
)
145147
logger.validate()
146148

149+
# Unlike EliotJSONEncoder, CustomJSONEncoder doesn't have a
150+
# fallback for non-serialisable objects
151+
logger.write(
152+
{"message_type": "type", "unserialisable": object()},
153+
None,
154+
)
155+
self.assertRaises(TypeError, logger.validate)
156+
147157
def test_serialize(self):
148158
"""
149159
L{MemoryLogger.serialize} returns a list of serialized versions of the

eliot/tests/test_testing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,10 @@ def test_check_for_errors_validation(self):
10451045
# No errors:
10461046
check_for_errors(logger)
10471047

1048+
# Unlike EliotJSONEncoder, CustomJSONEncoder doesn't have a
1049+
# fallback for non-serialisable objects
1050+
logger = MemoryLogger(encoder=CustomJSONEncoder)
1051+
10481052
# Now long something unserializable to JSON:
10491053
logger.write({"message_type": object()})
10501054
with self.assertRaises(TypeError):

0 commit comments

Comments
 (0)