s-knibbs / dataclasses-jsonschema

JSON schema generation from dataclasses
MIT License
166 stars 38 forks source link

Unions of dataclasses always deserialise as the first dataclass #118

Open ghost opened 4 years ago

ghost commented 4 years ago

When a dataclass contains a union of dataclasses it will always deserialise the field as the first dataclass listed in the union.

>>> from dataclasses import dataclass
>>> from typing import Union
>>>
>>> from dataclasses_jsonschema import JsonSchemaMixin
>>>
>>>
>>> @dataclass
... class A(JsonSchemaMixin):
...     a: str
...
>>>
>>> @dataclass
... class B(JsonSchemaMixin):
...     b: str
...
>>>
>>> @dataclass
... class C(JsonSchemaMixin):
...     c: Union[A, B]
...
>>> a_variant = C(A("a"))
>>> b_variant = C(B("a"))
>>> print(C.from_json(a_variant.to_json()))
C(c=A(a='a'))
>>> print(C.from_json(b_variant.to_json()))
C(c=A(a=None))

This can be fixed using the following patch:

diff --git a/dataclasses_jsonschema/__init__.py b/dataclasses_jsonschema/__init__.py
index 58fe500..3d46fc2 100644
--- a/dataclasses_jsonschema/__init__.py
+++ b/dataclasses_jsonschema/__init__.py
@@ -398,7 +398,7 @@ class JsonSchemaMixin:
             # Replace any nested dictionaries with their targets
             field_type_name = cls._get_field_type_name(field_type)
             if cls._is_json_schema_subclass(field_type):
-                def decoder(_, ft, val): return ft.from_dict(val, validate=False)
+                def decoder(_, ft, val): return ft.from_dict(val)
             elif is_nullable(field_type):
                 def decoder(f, ft, val): return cls._decode_field(f, unwrap_nullable(ft), val)
             elif is_optional(field_type):
@@ -412,7 +412,7 @@ class JsonSchemaMixin:
                     try:
                         decoded = cls._decode_field(field, variant, value)
                         break
-                    except (AttributeError, TypeError, ValueError):
+                    except (AttributeError, TypeError, ValueError, ValidationError):
                         continue
                 if decoded is not None:
                     return decoded

Will submit a PR which fixes the issue and adds a test for this behaviour.