Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ If you change the keyword arguments to a class which derives from `Serializable`
The format records Python classes and modules, which must remain importable when
loading data. Keys starting with two underscores are reserved. Nested Python
values require the serialization helpers rather than ordinary JSON encoding.
`to_json` raises `ValueError` for NaN or infinite floats, which standard JSON
can't represent; `from_json` still reads the `NaN` / `Infinity` literals which
older versions wrote.

## Documentation

Expand Down
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ and objects implementing the serialization interface. Dictionary keys beginning
with two underscores are reserved by the format. `to_dict()` is the object's
field mapping; it does not by itself guarantee an ordinary JSON-compatible
mapping when those fields contain nested Python objects.

Floats must be finite: `to_json()` raises `ValueError` for NaN or infinity,
which standard JSON can't represent. `from_json()` still reads the `NaN` and
`Infinity` literals which older versions wrote.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ classifiers = [
requires-python = ">=3.9"
dependencies = [
"typechecks>=0.0.2",
"simplejson>=3.0.0",
]

[project.optional-dependencies]
Expand Down
11 changes: 8 additions & 3 deletions serializable/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@

from __future__ import annotations

import json
from functools import lru_cache
from types import BuiltinFunctionType, FunctionType
from typing import Any, Union

import simplejson as json # type: ignore[import-untyped]

from .primitive_types import PRIMITIVE_TYPES


Expand Down Expand Up @@ -310,13 +309,19 @@ def to_json(x: Any) -> str:
"""
Returns JSON representation of a given Serializable instance or
other primitive object.

Raises ValueError for NaN or infinite floats, which have no standard
JSON representation.
"""
return json.dumps(to_serializable_repr(x))
return json.dumps(to_serializable_repr(x), allow_nan=False)


def from_json(json_string: Union[str, bytes]) -> Any:
"""
Inverse of to_json. Objects are reconstructed bottom-up while parsing,
equivalent to (but faster than) from_serializable_repr(json.loads(...)).

Accepts the NaN and Infinity literals which older versions of
serializable wrote.
"""
return json.loads(json_string, object_hook=_from_reconstructed_dict)
2 changes: 1 addition & 1 deletion serializable/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "1.2.1"
__version__ = "1.3.0"


def print_version():
Expand Down
20 changes: 19 additions & 1 deletion tests/test_primitive_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from serializable import from_serializable_repr, to_serializable_repr
import math

import pytest

from serializable import from_json, from_serializable_repr, to_json, to_serializable_repr

from .common import eq_

Expand All @@ -29,3 +33,17 @@ def test_bool():

def test_str():
eq_("waffles", from_serializable_repr(to_serializable_repr("waffles")))


@pytest.mark.parametrize("value", [math.nan, math.inf, -math.inf])
def test_to_json_rejects_non_finite_floats(value):
# NaN and infinities have no standard JSON representation
with pytest.raises(ValueError, match="Out of range float values"):
to_json([value])


def test_from_json_accepts_non_finite_literals():
# serializable used to write these (with simplejson < 3.19)
values = from_json("[NaN, Infinity, -Infinity]")
assert math.isnan(values[0])
eq_(values[1:], [math.inf, -math.inf])
Loading