lidatong / dataclasses-json

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

[FEATURE] Find 'best' match for deserialising Unions #536

Closed alexm-plutoflume closed 2 weeks ago

alexm-plutoflume commented 2 weeks ago

Description

When working with classes that have fields that are Unions of other dataclasses, and the dataclasses have default values, rather than find the first match, hopefully we can find the best match?

Here's an example:

@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class A(DataClassJsonMixin):
    a: str = "test"

@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class B(DataClassJsonMixin):
    b: int

@dataclass_json(undefined=Undefined.EXCLUDE)
@dataclass
class C(DataClassJsonMixin):
    c: Union[A, B]

myDict = {
    "c": {
        "b": 6
    }
}

myClass = C.from_dict(myDict)
>> C(c=A(a='test'))

In this case, although my dictionary of values actually matches class B, it picks class A (because technically, class A also matches). If I switch the Union order to [B, A], it'll pick class B. I'm sure this is intended behavior, but is not super intuitive to me. I think I would want it to look for matching keys before including default values.

Possible solution

I would want the deserialized dataclass to be the one that matches 'best' in the Union. In my mind 'best' would be the most number of matching keys first before default values, and if equivalent, then then most matching with default keys, and if all else fails the first matching. But I know there probably isn't a great way to define 'best'.

Context

Some additional context: This seems to be the behavior from 0.6.0 onward. Before this, the above example would result in

>>C(c={'b': 6})