python-marshmallow-union
python-marshmallow-union copied to clipboard
Ambiguous serialization
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 ?
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.
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)