python-marshmallow-union icon indicating copy to clipboard operation
python-marshmallow-union copied to clipboard

Ambiguous serialization

Open asmodehn opened this issue 5 years ago • 2 comments

It seems the union is ambiguous, ie it is not tagged... As a result :

Python 3.7.5 (default, Nov 20 2019, 09:21:52) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.10.1 -- An enhanced Interactive Python. Type '?' for help.
PyDev console: using IPython 7.10.1

Python 3.7.5 (default, Nov 20 2019, 09:21:52) 
[GCC 9.2.1 20191008] on linux
from marshmallow_union import Union
from marshmallow import fields
u = Union(fields=[fields.Integer(), fields.String()])
u.serialize('v', {'v': 0})
# 0
u.serialize('v', {'v': '0'})
# 0
u.deserialize('0')
# 0
u.deserialize(0)
# 0
u.deserialize(u.serialize('v', {'v': '0'})) == u.deserialize(u.serialize('v', {'v': 0}))
# True
'0' == 0
# False

For reference, with pure marshmallow, serialization can be abused, but deserialization is always correct.

f = fields.Integer()
f.serialize('v', {'v': 0})
# 0
f.serialize('v', {'v': '0'})
# 0
f.deserialize(f.serialize('v', {'v': '0'})) == 0
# True
f.deserialize(f.serialize('v', {'v': 0})) == 0
# True
f.deserialize(f.serialize('v', {'v': '0'})) == '0'
# False
f.deserialize(f.serialize('v', {'v': 0})) == '0'
# False

f = fields.String()
f.deserialize(f.serialize('v', {'v': 0})) == '0'
# True
f.deserialize(f.serialize('v', {'v': 0})) == 0
# False

Thoughts ?

asmodehn avatar Dec 12 '19 10:12 asmodehn

Yeah, some information gets lost in translation during the unioning process. For a more explicit take on the multiple-type serialization, marshmallow-polyfield looks like a good option.

adamboche avatar Jan 30 '20 21:01 adamboche

You can avoid the ambiguity by defining strict fields, that only accept elements of the expected type. Here is an example:


def checkType(value, typ, many=False, err: Type[Exception] = TypeError):
    if many:
        for v in value:
            checkType(v, typ)
    if not isinstance(value, typ):
        raise err(f"Expected a value of type {typ.__name__}, got {repr(value)}")


class StrictStr(marshmallow.fields.String):
    def _serialize(self, value, attr, obj, **kwargs):
        checkType(value, str, many=kwargs.get("many", False))
        return super(StrictStr, self)._serialize(value, attr, obj, **kwargs)

    def _deserialize(self, value, attr, obj, **kwargs):
        checkType(value, str, many=kwargs.get("many", False), err=marshmallow.ValidationError)
        return super(StrictStr, self)._deserialize(value, attr, obj, **kwargs)


class StrictInt(marshmallow.fields.Integer):
    def _serialize(self, value, attr, obj, **kwargs):
        checkType(value, int, many=kwargs.get("many", False))
        return super(StrictInt, self)._serialize(value, attr, obj, **kwargs)

    def _deserialize(self, value, attr, obj, **kwargs):
        checkType(value, int, many=kwargs.get("many", False), err=marshmallow.ValidationError)
        return super(StrictInt, self)._deserialize(value, attr, obj, **kwargs)

lovasoa avatar May 23 '20 14:05 lovasoa