From 5093eadee51328b13e215cf0e0387ded5586b26c Mon Sep 17 00:00:00 2001 From: vera-rubin <322284620+vera-rubin@users.noreply.github.com> Date: Wed, 14 Oct 2026 22:24:01 +0000 Subject: [PATCH] Fix JSON encoding of large integers --- src/celpy/adapter.py | 9 ++++++--- tests/test_package.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/celpy/adapter.py b/src/celpy/adapter.py index 43a2403..03c39f7 100644 --- a/src/celpy/adapter.py +++ b/src/celpy/adapter.py @@ -43,23 +43,26 @@ class CELJSONEncoder(json.JSONEncoder): This is **not** a reversible transformation. Some things are coerced to strings without any more detailed type marker. - Specifically timestamps, durations, and bytes. + Specifically timestamps, durations, bytes, and integers outside the interoperable range. """ @staticmethod def to_python( cel_object: celtypes.Value, - ) -> Union[celtypes.Value, List[Any], Dict[Any, Any], bool]: + ) -> Union[celtypes.Value, List[Any], Dict[Any, Any], bool, str]: """Recursive walk through the CEL object, replacing :py:class:`celpy.celtypes.BoolType` with native ``bool`` instances. This lets the :py:mod:`json` module correctly represent the obects with JSON ``true`` and ``false``. This will also replace :py:class:`celpy.celtypes.ListType` and :py:class:`celpy.celtypes.MapType` with native ``list`` and ``dict``. + CEL integers outside ``-(2**53 - 1)`` to ``2**53 - 1`` become decimal strings. All other CEL objects will be left intact. This creates an intermediate hybrid beast that's not quite a :py:class:`celpy.celtypes.Value` because a few things have been replaced. """ if isinstance(cel_object, celtypes.BoolType): return True if cel_object else False + elif isinstance(cel_object, (celtypes.IntType, celtypes.UintType)): + return str(cel_object) if abs(cel_object) > 2**53 - 1 else cel_object elif isinstance(cel_object, celtypes.ListType): return [CELJSONEncoder.to_python(item) for item in cel_object] elif isinstance(cel_object, celtypes.MapType): @@ -72,7 +75,7 @@ def to_python( def encode(self, cel_object: celtypes.Value) -> str: """ - Override built-in encode to create proper Python ``bool`` objects. + Override built-in encode to convert booleans and large integers. """ return super().encode(CELJSONEncoder.to_python(cel_object)) diff --git a/tests/test_package.py b/tests/test_package.py index 3b30d91..051051f 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -96,6 +96,31 @@ def test_encoder(): ) +@pytest.mark.parametrize( + "cel_value, expected", + [ + (celpy.celtypes.IntType(-2**63), '"-9223372036854775808"'), + (celpy.celtypes.IntType(-(2**53 + 1)), '"-9007199254740993"'), + (celpy.celtypes.IntType(-2**53), '"-9007199254740992"'), + (celpy.celtypes.IntType(-(2**53 - 1)), "-9007199254740991"), + (celpy.celtypes.IntType(2**53 - 1), "9007199254740991"), + (celpy.celtypes.IntType(2**53), '"9007199254740992"'), + (celpy.celtypes.IntType(2**53 + 1), '"9007199254740993"'), + (celpy.celtypes.IntType(2**63 - 1), '"9223372036854775807"'), + (celpy.celtypes.UintType(2**53 - 1), "9007199254740991"), + (celpy.celtypes.UintType(2**53), '"9007199254740992"'), + (celpy.celtypes.UintType(2**53 + 1), '"9007199254740993"'), + (celpy.celtypes.UintType(2**64 - 1), '"18446744073709551615"'), + ], +) +def test_encoder_integer_range(cel_value, expected): + assert json.dumps(cel_value, cls=celpy.CELJSONEncoder) == expected + cel_obj = celpy.celtypes.MapType( + {celpy.celtypes.StringType("numbers"): celpy.celtypes.ListType([cel_value])} + ) + assert json.dumps(cel_obj, cls=celpy.CELJSONEncoder) == f'{{"numbers": [{expected}]}}' + + def test_encoder_unknown(): cel_obj = sentinel.no_json with pytest.raises(TypeError):