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: if field has custom decoder, schema takes it into account #462

Merged
merged 15 commits into from
Aug 7, 2023
14 changes: 12 additions & 2 deletions dataclasses_json/mm.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ def inner(type_, options):
origin = getattr(type_, '__origin__', type_)
args = [inner(a, {}) for a in getattr(type_, '__args__', []) if
a is not type(None)]

if type_ == Ellipsis:
return type_

if _is_optional(type_):
options["allow_none"] = True
if origin is tuple:
Expand Down Expand Up @@ -318,6 +318,16 @@ def schema(cls, mixin, infer_missing):
options['data_key'] = metadata.letter_case(field.name)

t = build_type(type_, options, mixin, field, cls)
if field.metadata.get('dataclasses_json', {}).get('decoder'):
# If the field defines a custom decoder, it should completely replace the Marshmallow field's conversion
# logic.
# From Marshmallow's documentation for the _deserialize method:
# "Deserialize value. Concrete :class:`Field` classes should implement this method. "
# This is the method that Field implementations override to perform the actual deserialization logic.
# In this case we specifically override this method instead of `deserialize` to minimize potential
# side effects, and only cancel the actual value deserialization.
t._deserialize = lambda v, *_a, **_kw: v

# if type(t) is not fields.Field: # If we use `isinstance` we would return nothing.
if field.type != typing.Optional[CatchAllVar]:
schema[field.name] = t
Expand Down
10 changes: 10 additions & 0 deletions tests/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,16 @@ class DataClassWithErroneousDecode:
id: float = field(metadata=config(decoder=lambda: None))


def split_str(data: str, *_args, **_kwargs):
return data.split(',')


@dataclass_json
@dataclass
class DataClassDifferentTypeDecode:
lst: List[str] = field(default=None, metadata=config(decoder=split_str))


@dataclass_json
@dataclass
class DataClassMappingBadDecode:
Expand Down
7 changes: 6 additions & 1 deletion tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import pytest

from .entities import (DataClassDefaultListStr, DataClassDefaultOptionalList, DataClassList, DataClassOptional,
DataClassWithNestedOptional, DataClassWithNestedOptionalAny, DataClassWithNestedAny)
DataClassWithNestedOptional, DataClassWithNestedOptionalAny, DataClassWithNestedAny,
DataClassDifferentTypeDecode)
from .test_letter_case import CamelCasePerson, KebabCasePerson, SnakeCasePerson, FieldNamePerson

test_do_list = """[{}, {"children": [{"name": "a"}, {"name": "b"}]}]"""
Expand Down Expand Up @@ -47,3 +48,7 @@ def test_nested_optional_any(self):
def test_nested_any_accepts_optional(self):
DataClassWithNestedAny.schema().loads(nested_optional_data)
assert True

def test_accounts_for_decode(self):
assert DataClassDifferentTypeDecode.schema().load({'lst': '1,2,3'}) == \
DataClassDifferentTypeDecode(lst=['1', '2', '3'])