lidatong / dataclasses-json

Easily serialize Data Classes to and from JSON
MIT License
1.34k stars 150 forks source link

[BUG] Dataclass.from_json with Union type fields infers missing fields and fails in the wrong spot #488

Open pingretispec opened 9 months ago

pingretispec commented 9 months ago

Description

I have a Message json dataclass that has a field namely "record" that can be dataclass Record1, Record2, or Record3. The code fails trying to infer record with dataclass Record1.

Code snippet that reproduces the issue

@dataclass_json @dataclass class Message: type: MessageType op: Operation record: Union[Record1, Record2]

@dataclass_json @dataclass class Record1: id: uuid.UUID date: datetime.date count: int

@dataclass_json @dataclass class Record2: id: uuid.UUID date: datetime.date qty: int

record = Record2(id='001', date=datetime.date(2022,9,22), qty=10) message = Message(type=MessageType.COUNT, op=Operation.CREATE, record=record) msg_json = json.dump(message.to_dict(), json_path) with open(json_path) as json_f: msg = Message.from_json(json_f.read())

Describe the results you expected

msg = Message(type="COUNT", op="CREATE", record=Record2(id="001", date=datetime.date(2022,9,22), qty=10))

Python version you are using

python --version 3.10.10

Environment description

dataclasses-json==0.6.0

idbentley commented 9 months ago

I was able to reproduce this bug with a few simplifications to the code snippet:

from dataclasses import dataclass
from dataclasses_json import dataclass_json
from typing import Union
import uuid
import json

@dataclass_json
@dataclass
class Record1:
    id: uuid.UUID
    count: int

@dataclass_json
@dataclass
class Record2:
    id: uuid.UUID
    qty: int

@dataclass_json
@dataclass
class Message:
    type: str
    op: str
    record: Union[Record1, Record2]

record = Record2(id=uuid.UUID(int=1), qty=10)
message = Message(type='count', op='create', record=record)
msg_json = json.dumps(message.to_dict())
msg = Message.from_json(msg_json)

Which results in:

dataclasses_json/core.py:325: UserWarning: Failed to decode {'id': '001', 'qty': 10} Union dataclasses.Expected Union to include a matching dataclass and it didn't.
  warnings.warn(
>>> msg
Message(type='count', op='create', record={'id': '001', 'qty': 10})

For @pingretispec I would recommend using the schema as a workaround:

from dataclasses import dataclass
from dataclasses_json import dataclass_json
from typing import Union
import uuid
import json

@dataclass_json
@dataclass
class Record1:
    id: uuid.UUID
    count: int

@dataclass_json
@dataclass
class Record2:
    id: uuid.UUID
    qty: int

@dataclass_json
@dataclass
class Message:
    type: str
    op: str
    record: Union[Record1, Record2]

record = Record2(id=uuid.UUID(int=1), qty=10)
message = Message(type='count', op='create', record=record)
schema = message.schema()
schema_json = schema.dump(message)
msg = schema.load(schema_json)

Which does the encoding and decoding successfully:

>>> msg
Message(type='count', op='create', record=Record2(id=UUID('00000000-0000-0000-0000-000000000001'), qty=10))