Cyclic or “Recursive” Dataclasses

Note

Important: The current functionality for cyclic or “recursive” dataclasses is being re-imagined. Please refer to the new docs for V1 Opt-in features, which introduces enhanced support for these use cases. For more details, see the Field Guide to V1 Opt‐in and the Recursive Types and Dataclasses with Cyclic References in V1 documentation.

This change is part of the ongoing improvements in version v0.34.0+, and the old functionality will no longer be maintained in future releases.

Prior to version v0.27.0, dataclasses with cyclic references or self-referential structures were not supported. This limitation is shown in the following toy example:

from dataclasses import dataclass

from dataclass_wizard import JSONWizard


@dataclass
class A(JSONWizard):
    a: 'A | None' = None


a = A.from_dict({'a': {'a': {'a': None}}})
assert a == A(a=A(a=A(a=None)))

This has been a longstanding issue.

New in v0.27.0: The Dataclass Wizard now extends its support to cyclic and self-referential dataclass models.

The example below demonstrates recursive dataclasses with cyclic dependencies, following the pattern A -> B -> A -> B.

With Class Inheritance

Here’s a basic example demonstrating the use of recursive dataclasses with cyclic dependencies, using a class inheritance model and the JSONWizard mixin:

from __future__ import annotations  # This can be removed in Python 3.10+

from dataclasses import dataclass

from dataclass_wizard import JSONWizard


@dataclass
class A(JSONWizard):
    class _(JSONWizard.Meta):
        # enable support for self-referential / recursive dataclasses
        recursive_classes = True

    b: 'B | None' = None


@dataclass
class B:
    a: A | None = None


# confirm that `from_dict` with a recursive, self-referential
# input `dict` works as expected.
a = A.from_dict({'b': {'a': {'b': {'a': None}}}})
assert a == A(b=B(a=A(b=B())))

Without Class Inheritance

Here is the same example as above, but with relying solely on dataclasses, without using any special class inheritance model:

from __future__ import annotations  # This can be removed in Python 3.10+

from dataclasses import dataclass

from dataclass_wizard import fromdict, LoadMeta


@dataclass
class A:
    b: 'B | None' = None


@dataclass
class B:
    a: A | None = None


# enable support for self-referential / recursive dataclasses
LoadMeta(recursive_classes=True).bind_to(A)

# confirm that `from_dict` with a recursive, self-referential
# input `dict` works as expected.
a = fromdict(A, {'b': {'a': {'b': {'a': None}}}})
assert a == A(b=B(a=A(b=B())))