From 56ab57fe3d445350a22b606bfa19f3f95d99813c Mon Sep 17 00:00:00 2001 From: waiho-gumloop Date: Fri, 27 Feb 2026 16:15:05 -0800 Subject: [PATCH] feat: add default parameter to JsonObject.serialize() Add an optional `default` parameter to `JsonObject.serialize()` that is passed through to `json.dumps()`. This allows callers to handle custom types (e.g., `datetime`) that are not natively JSON-serializable, using the standard `json.dumps` extension mechanism. Backward-compatible: `default=None` preserves existing behavior. Made-with: Cursor --- google/cloud/spanner_v1/data_types.py | 29 +++++++++++++--- tests/unit/test_datatypes.py | 48 +++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/google/cloud/spanner_v1/data_types.py b/google/cloud/spanner_v1/data_types.py index 6703f359e9..3f1250e81f 100644 --- a/google/cloud/spanner_v1/data_types.py +++ b/google/cloud/spanner_v1/data_types.py @@ -80,22 +80,41 @@ def from_str(cls, str_repr): return cls(json.loads(str_repr)) - def serialize(self): + def serialize(self, default=None): """Return the object text representation. + Args: + default (callable, optional): A function that is called for + objects that are not JSON serializable. It should return a + JSON-encodable version of the object or raise a + :class:`TypeError`. This is passed directly to + :func:`json.dumps` as the ``default`` parameter. + For example, to support ``datetime`` objects:: + + obj.serialize(default=lambda o: o.isoformat() + if hasattr(o, 'isoformat') else str(o)) + Returns: - str: JSON object text representation. + str: JSON object text representation, or None if the object + is null. """ if self._is_null: return None if self._is_scalar_value: - return json.dumps(self._simple_value) + return json.dumps(self._simple_value, default=default) if self._is_array: - return json.dumps(self._array_value, sort_keys=True, separators=(",", ":")) + return json.dumps( + self._array_value, + sort_keys=True, + separators=(",", ":"), + default=default, + ) - return json.dumps(self, sort_keys=True, separators=(",", ":")) + return json.dumps( + self, sort_keys=True, separators=(",", ":"), default=default + ) @dataclass diff --git a/tests/unit/test_datatypes.py b/tests/unit/test_datatypes.py index 65ccacb4ff..d465318c77 100644 --- a/tests/unit/test_datatypes.py +++ b/tests/unit/test_datatypes.py @@ -96,3 +96,51 @@ def test_w_JsonObject_of_list_of_simple_JsonData(self): expected = json.dumps(data, sort_keys=True, separators=(",", ":")) data_jsonobject = JsonObject(JsonObject(data)) self.assertEqual(data_jsonobject.serialize(), expected) + + +class Test_JsonObject_serialize_default(unittest.TestCase): + """Tests for the ``default`` parameter of ``JsonObject.serialize()``.""" + + def test_dict_with_custom_type_and_default(self): + from datetime import datetime + + dt = datetime(2023, 6, 15, 9, 30, 0) + data = {"ts": dt, "name": "test"} + obj = JsonObject(data) + result = obj.serialize(default=lambda o: o.isoformat() if isinstance(o, datetime) else str(o)) + parsed = json.loads(result) + self.assertEqual(parsed["ts"], "2023-06-15T09:30:00") + self.assertEqual(parsed["name"], "test") + + def test_array_with_custom_type_and_default(self): + from datetime import datetime + + dt = datetime(2023, 1, 1) + data = [dt, "hello"] + obj = JsonObject(data) + result = obj.serialize(default=lambda o: o.isoformat() if isinstance(o, datetime) else str(o)) + parsed = json.loads(result) + self.assertEqual(parsed[0], "2023-01-01T00:00:00") + self.assertEqual(parsed[1], "hello") + + def test_without_default_raises_on_custom_type(self): + from datetime import datetime + + data = {"ts": datetime(2023, 1, 1)} + obj = JsonObject(data) + with self.assertRaises(TypeError): + obj.serialize() + + def test_default_none_preserves_existing_behavior(self): + data = {"foo": "bar"} + expected = json.dumps(data, sort_keys=True, separators=(",", ":")) + obj = JsonObject(data) + self.assertEqual(obj.serialize(default=None), expected) + + def test_scalar_with_default(self): + from datetime import datetime + + dt = datetime(2023, 6, 15) + obj = JsonObject(dt) + result = obj.serialize(default=lambda o: o.isoformat() if isinstance(o, datetime) else str(o)) + self.assertEqual(json.loads(result), "2023-06-15T00:00:00")