Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for #239: Union inside List or Dict is not deserialized as the correspond… #464

Merged
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions dataclasses_json/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,27 @@ def _decode_generic(type_, value, infer_missing):
res = _decode_generic(type_arg, value, infer_missing)
else:
res = _support_extended_types(type_arg, value)
else: # Union (already decoded or unsupported 'from_json' used)
res = value
else: # Union (already decoded or try to decode a dataclass)
type_options = _get_type_args(type_)
if type(value) is not dict or dict in type_options:
# already decoded
res = value
else:
# FIXME if all types in the union are dataclasses this
# will just pick the first option -
# maybe find the best fitting class in that case instead?
res = value
changed = False
for type_option in type_options:
if is_dataclass(type_option):
res = _decode_dataclass(type_option, value, infer_missing)
changed = True
break
if not changed:
warnings.warn(
f"Failed encoding {value} Union dataclasses."
f"Expected Union to include a dataclass and it didn't."
)
pawelwilczewski marked this conversation as resolved.
Show resolved Hide resolved
return res


Expand Down
172 changes: 172 additions & 0 deletions tests/test_collection_of_unions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from dataclasses import dataclass
from typing import Dict, Union, List
import json

from dataclasses_json import dataclass_json


@dataclass_json
@dataclass(frozen=True)
class TestChild:
some_field: int = None


@dataclass_json
@dataclass(frozen=True)
class TestOtherChild:
other_field: int = None


@dataclass_json
@dataclass(frozen=True)
class DictUnion:
d: Dict[str, Union[TestChild, TestOtherChild]]


@dataclass_json
@dataclass(frozen=True)
class ListUnion:
l: List[Union[TestChild, TestOtherChild]]


@dataclass_json
@dataclass(frozen=True)
class Player:
name: str


@dataclass_json
@dataclass(frozen=True)
class Team:
roster: List[Union[int, Player]]
roster_backup: Dict[int, Union[int, Player]]


class TestCollectionOfUnions:
def test_dict(self):
data = {
'd': {
'child' : {
'some_field' : 1
},
'other_child' : {
'other_field' : 2
}
}
}
json_str = json.dumps(data)
obj = DictUnion.from_json(json_str)

assert type(obj.d['child']) in (TestChild, TestOtherChild)

def test_list(self):
data = {
'l': [
{
'some_field' : 1
},
{
'other_field' : 1
}
]
}
json_str = json.dumps(data)
obj = ListUnion.from_json(json_str)

assert type(obj.l[0]) in (TestChild, TestOtherChild)

def test_int(self):
data = {
'roster': [
1,
2,
3
],
'roster_backup': {
1: 5,
2: 3,
3: 2
}
}
json_str = json.dumps(data)
obj: Team = Team.from_json(json_str)

assert type(obj.roster[0]) is int and type(obj.roster_backup[1]) is int

def test_dataclass(self):
data = {
'roster': [
{
'name': 'player1'
},
{
'name': 'player2'
},
{
'name': 'player3'
}
],
'roster_backup': {
1: {
'name': 'player1'
},
2: {
'name': 'player2'
},
3: {
'name': 'player3'
}
}
}
json_str = json.dumps(data)
obj: Team = Team.from_json(json_str)

assert type(obj.roster[0]) is Player and type(obj.roster_backup[1]) is Player

def test_mixed(self):
data = {
'roster': [
{
'name': 'player1'
},
{
'name': 'player2'
},
{
'name': 'player3'
}
],
'roster_backup': {
1: 5,
2: 3,
3: 2
}
}
json_str = json.dumps(data)
obj: Team = Team.from_json(json_str)

assert type(obj.roster[0]) is Player and type(obj.roster_backup[1]) is int

def test_mixed_inverse(self):
data = {
'roster': [
1,
2,
3
],
'roster_backup': {
1: {
'name': 'player1'
},
2: {
'name': 'player2'
},
3: {
'name': 'player3'
}
}
}
json_str = json.dumps(data)
obj: Team = Team.from_json(json_str)

assert type(obj.roster[0]) is int and type(obj.roster_backup[1]) is Player