-
Notifications
You must be signed in to change notification settings - Fork 4
serializable_dataclass breaks with from __future__ import annotations when nested vals #66
Description
Details
Description
serializable_dataclass doesn't handle from __future__ import annotations (PEP 563). When the import is present, all annotations become strings at runtime, which causes two problems:
-
Auto-serialization of nested
SerializableDataclassfields fails. The generatedserialize()doesn't recognize that e.g.list[Inner]contains aSerializableDataclass, so nested objects are left as-is andjson.dumps()raisesTypeError: Object of type ... is not JSON serializable. -
Plain defaults after
serializable_fieldcauseTypeError: non-default argument follows default argument. When annotations are strings, it appears that the decorator's pre-processing of fields mishandles plain= valuedefaults, causingdataclasses.dataclass()to reject the field ordering.
Reproducer
from __future__ import annotations
from muutils.json_serialize import (
SerializableDataclass,
serializable_dataclass,
serializable_field,
)
import json
@serializable_dataclass
class Inner(SerializableDataclass):
value: int = 0
@serializable_dataclass
class Outer(SerializableDataclass):
items: list[Inner] = serializable_field(default_factory=list)
outer = Outer(items=[Inner(value=42)])
data = outer.serialize()
# data["items"] is [Inner(value=42)] instead of [{"value": 42, ...}]
json.dumps(data) # TypeError: Object of type Inner is not JSON serializableWorkaround
Add explicit serialization_fn/deserialize_fn for any field containing nested SerializableDataclass objects:
items: list[Inner] = serializable_field(
default_factory=list,
serialization_fn=lambda items: [i.serialize() for i in items],
deserialize_fn=lambda items: [Inner.load(i) for i in items],
)For the field-ordering issue, use kw_only=True:
@serializable_dataclass(kw_only=True)
class MyConfig(SerializableDataclass):
...