dataclasses-json
dataclasses-json copied to clipboard
Inner dataclass is not converted from dict to dataclass if it is a union type
In [1]: from dataclasses_json import DataClassJsonMixin
...: from dataclasses import dataclass
...: from typing import Union
In [2]: @dataclass
...: class Inner(DataClassJsonMixin):
...: a: str
...:
In [3]: @dataclass
...: class Outer1(DataClassJsonMixin):
...: inner: Inner
...:
In [4]: @dataclass
...: class Outer2(DataClassJsonMixin):
...: inner: Union[str, Inner]
...:
In [5]: Outer1.from_dict({'inner': {'a': 'a'}}) # works as expected
Out[5]: Outer1(inner=Inner(a='a'))
In [6]: Outer2.from_dict({'inner': 'a'}) # works as expected
Out[6]: Outer2(inner='a')
In [7]: Outer2.from_dict({'inner': {'a': 'a'}}) # type of `inner` should be Inner
Out[7]: Outer2(inner={'a': 'a'})
Running into the same issue. I've been able to hack this to work with the following workaround:
@dataclass
class Inner(DataClassJsonMixin):
a: str
@dataclass
class Inner(DataClassJsonMixin):
a: str
@dataclass
class Outer(DataClassJsonMixin):
inner: Union[str, Inner] = field(
metadata=config(
decoder=lambda d: d if isinstance(d, str) else Inner.from_dict(d)
)
)
print(Outer.from_dict({'inner': {'a': 'a'}}))
print(Outer.from_dict({'inner': 'a'}))
Outer2(inner=Inner(a='a'))
Outer2(inner='a')
I also notice there is a similar issue with recursive types inside containers:
@dataclass
class Outer(DataClassJsonMixin):
inner: dict[str, "Outer"]
print(Outer.from_dict({'inner': {'a': {'inner': {}}}}))
Outer(inner={'a': {'inner': {}}})
This is also avoided by the same workaround:
@dataclass
class Outer(DataClassJsonMixin):
inner: dict[str, "Outer"] = field(
metadata=config(
decoder=lambda d: {k: Outer.from_dict(d[k]) for k in d}
)
)
print(Outer.from_dict({'inner': {'a': {'inner': {}}}}))
Outer(inner={'a': Outer(inner={})})
Running into the same problem.
Judging from a quick glance this might be the culprit:
https://github.com/lidatong/dataclasses-json/blob/master/dataclasses_json/core.py#L278
Maybe it would be feasible to match the types of the Union one by one (l2r) and use the first matching one? AFAIK this is how Pydantic does it for their Unions
@lidatong what would be needed to fix this?
Should be fixed in latest, needs a re-test