Skip to content

serializable_dataclass breaks with from __future__ import annotations when nested vals #66

@mivanit

Description

@mivanit

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:

  1. Auto-serialization of nested SerializableDataclass fields fails. The generated serialize() doesn't recognize that e.g. list[Inner] contains a SerializableDataclass, so nested objects are left as-is and json.dumps() raises TypeError: Object of type ... is not JSON serializable.

  2. Plain defaults after serializable_field cause TypeError: non-default argument follows default argument. When annotations are strings, it appears that the decorator's pre-processing of fields mishandles plain = value defaults, causing dataclasses.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 serializable

Workaround

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):
    ...

Metadata

Metadata

Assignees

No one assigned

    Labels

    sweepAssigns Sweep to an issue or pull request.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions