Predicate Hooks¶
In contrast to Class Hooks, predicate hooks enable registering hooks more broadly based on class or object properties, rather than just the class and its inheritance tree itself. This is done through registering boolean predicate functions together with (de)serialization hooks. They receive the object, inspect it and return True if the corresponding hook should be used for the object. A prime usecase example for this mechanism are DeterministicJsonSerializer’s default hooks registered for dataclasses and attrs classes, which immediately catches all such class objects by inspecting whether they are dataclasses or attrs classes. Otherwise, an individual class hook would be needed for every single dataclass or attrs class, while a predicate hook can catch all such classes in one go.
# Example 8:
from typing import Any, Protocol, Self
from parityos_serialization.serializer import DeterministicJsonSerializer, Serializer
from parityos_serialization.utils.class_tagger import NameClassTagger
from parityos_serialization.utils.typedefs import TypedDataDict
from typing_extensions import override
class PointLike2D(Protocol):
x: int
y: int
@classmethod
def from_2d_coordinates(cls, x: int, y: int) -> Self: ...
class Point2D:
x: int
y: int
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y
@classmethod
def from_2d_coordinates(cls, x: int, y: int):
return cls(x, y)
@override
def __eq__(self, other: object) -> bool:
return vars(self) == vars(other)
# serialization predicate
def is_pointlike_obj(obj: object) -> bool:
return hasattr(obj, "x") and hasattr(obj, "y")
# serialization hook
def serialize_pointlike(
obj: PointLike2D, _serializer: Serializer[TypedDataDict, NameClassTagger]
) -> dict[str, int]:
return {"x": obj.x, "y": obj.y}
# deserialization predicate
def is_pointlike_data(data: Any, _: type):
return isinstance(data, dict) and data.keys() == {"x", "y"}
# deserialization hook
def deserialize_pointlike(
data: dict[str, int],
cls: type[PointLike2D],
_serializer: Serializer[TypedDataDict, NameClassTagger],
) -> PointLike2D:
return cls.from_2d_coordinates(data["x"], data["y"])
serializer = DeterministicJsonSerializer()
serializer.register_predicate_serialization_hook(is_pointlike_obj, serialize_pointlike)
serializer.register_predicate_deserialization_hook(is_pointlike_data, deserialize_pointlike)
serializer.class_tagger.register_classes(Point2D)
obj = Point2D(4, 2)
dct = serializer.serialize(obj)
obj2 = serializer.deserialize(dct)
assert obj == obj2
print(dct)
# {'_data': {'x': 4, 'y': 2}, '_cls': 'Point2D'}
You can imagine e.g. implementing a Point3D (whose from_2d_coordinates classmethod would just set the z coordinate to zero) which would also be supported by these hooks.