lovasoa / marshmallow_dataclass

Automatic generation of marshmallow schemas from dataclasses.
https://lovasoa.github.io/marshmallow_dataclass/html/marshmallow_dataclass.html
MIT License
456 stars 78 forks source link

Unions of classes and sub classes with default values not serializing and deserializing correctly #268

Open pnetherwood opened 3 months ago

pnetherwood commented 3 months ago

I'm having trouble with Unions of classes. If you have subclasses of data classes the serialisation and deserialization behaviour depends on the order of the Union. However, it is not symmetrical if you also have default values in your data classes ie if the union is one way around serialisation succeeds but then deserialisation fails and vice versa.

For example:

@dataclass
class Base:
    name: str
    value: int = None

@dataclass
class SubBase(Base):
    value2: int = None

@dataclass
class Container:
    base: Union[Base, SubBase]

c = Container(SubBase("Test", 1, 2))

schema: Schema = marshmallow_dataclass.class_schema(Container)()

print(schema.dumps(c))

which incorrectly prints the following:

{"base": {"name": "Test", "value": 1}}

It recognises that SubBase is an instance of Base so uses the the Base serialiser and only serializes the fields in Base.

For deserialization:

print(schema.loads('{"base": {"name": "Test", "value": 1, "value2": 2}}'))
print(schema.loads('{"base": {"name": "Test", "value": 1}}'))

you get the right values:

Container(base=SubBase(name='Test', value=1, value2=2))
Container(base=Base(name='Test', value=1))

However, if you switch the Union around you get the correct serialisation:

@dataclass
class Container:
    base: Union[SubBase, Base]
{"base": {"name": "Test", "value": 1, "value2": 2}}

but now the deserialization is wrong:

Container(base=SubBase(name='Test', value=1, value2=2))
Container(base=SubBase(name='Test', value=1, value2=None))

This is because it's using the default value for value2 in SubBase to create a valid SubBase instance using only the arguments for Base.

I'm not sure if there is any way around this.