dataclasses-json icon indicating copy to clipboard operation
dataclasses-json copied to clipboard

Inner dataclass is not converted from dict to dataclass if it is a union type

Open korommatyi opened this issue 3 years ago • 4 comments

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'})

korommatyi avatar Jul 12 '21 14:07 korommatyi

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={})})

cal-pratt avatar Aug 01 '21 20:08 cal-pratt

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

rynkk avatar Apr 13 '22 09:04 rynkk

@lidatong what would be needed to fix this?

gshpychka avatar Oct 04 '22 16:10 gshpychka

Should be fixed in latest, needs a re-test

george-zubrienko avatar Jul 20 '23 22:07 george-zubrienko