diff --git a/CHANGES.md b/CHANGES.md index c4d78b8..f26fe20 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,13 @@ Version 0.3.1 To be released. +### Python target + + - Fixed record/union deserializers to ignore unknown fields in data payload. + Deserializers had raised `KeyError` before. [#232] + +[#232]: https://github.com/spoqa/nirum/issues/232 + Version 0.3.0 ------------- @@ -172,7 +179,6 @@ Released on February 18, 2018. [#203]: https://github.com/spoqa/nirum/pull/203 [#204]: https://github.com/spoqa/nirum/pull/204 [#216]: https://github.com/spoqa/nirum/issues/216 -[#222]: https://github.com/spoqa/nirum/pull/222 [#218]: https://github.com/spoqa/nirum/issues/218 [#222]: https://github.com/spoqa/nirum/pull/222 [#223]: https://github.com/spoqa/nirum/pull/223 diff --git a/docs/refactoring.md b/docs/refactoring.md index 7ab6e23..2f167b6 100644 --- a/docs/refactoring.md +++ b/docs/refactoring.md @@ -129,6 +129,15 @@ returns, but in program codes we become able to deal with distance using `meter` type rather than primitive `bigint` type. +Removing a field +---------------- + +Any fields in payload that are unlisted in an interface definition are ignored +by a deserializer. If you are going to remove an existing field you should +deploy the newer version to a payload consumer first, and then a payload +provider last. + + Interchangeability of enum type and `text` ------------------------------------------ diff --git a/docs/serialization.md b/docs/serialization.md index 9bf5f17..3652f6c 100644 --- a/docs/serialization.md +++ b/docs/serialization.md @@ -201,6 +201,10 @@ It's represented in JSON to: "uri": null } +When a payload is deserialized, undefined fields are just ignored. +It can be used to drop an existing field without breaking +backward compatibility. + Union type ---------- @@ -243,6 +247,9 @@ It's represented in JSON to: "uri": null } +In a similar way to a recrod type, undefined fields in a payload are ignored +by deserializer. + Option type ----------- diff --git a/src/Nirum/Targets/Python.hs b/src/Nirum/Targets/Python.hs index c982e53..0694ab7 100644 --- a/src/Nirum/Targets/Python.hs +++ b/src/Nirum/Targets/Python.hs @@ -953,7 +953,7 @@ class $className(object): ) args = dict() behind_names = cls.__nirum_field_names__.behind_names - field_types = cls.__nirum_field_types__ + field_types = cls.__nirum_field_types__() if callable(field_types): field_types = field_types() # old compiler could generate non-callable dictionary @@ -966,7 +966,11 @@ class $className(object): else: name = attribute_name try: - args[name] = deserialize_meta(field_types[name], item) + field_type = field_types[name] + except KeyError: + continue + try: + args[name] = deserialize_meta(field_type, item) except ValueError as e: errors.add('%s: %s' % (attribute_name, str(e))) if errors: @@ -1107,7 +1111,11 @@ class $className({T.intercalate "," $ compileExtendClasses annotations}): name = attribute_name tag_types = dict(cls.__nirum_tag_types__()) try: - args[name] = deserialize_meta(tag_types[name], item) + field_type = tag_types[name] + except KeyError: + continue + try: + args[name] = deserialize_meta(field_type, item) except ValueError as e: errors.add('%s: %s' % (attribute_name, str(e))) if errors: diff --git a/test/python/primitive_test.py b/test/python/primitive_test.py index ac1cb8e..ba9a064 100644 --- a/test/python/primitive_test.py +++ b/test/python/primitive_test.py @@ -138,10 +138,18 @@ def test_record(): point2_serialize = {'_type': 'point2', 'left': 3, 'top': 14} assert point2.__nirum_serialize__() == point2_serialize assert Point2.__nirum_deserialize__(point2_serialize) == point2 - with raises(ValueError): + with raises(ValueError): # no "_type" -- FIXME: "_type" is unnecessary Point2.__nirum_deserialize__({'left': 3, 'top': 14}) - with raises(ValueError): + with raises(ValueError): # no "left" and "top" fields Point2.__nirum_deserialize__({'_type': 'foo'}) + p = Point2.__nirum_deserialize__({ + # extra field does not matter; just ignored + '_type': 'point2', + 'left': 3, + 'top': 14, + 'extra': 'it does not matter', + }) + assert p == Point2(left=IntUnbox(3), top=IntUnbox(14)) with raises(TypeError): Point2(left=IntUnbox(1), top='a') with raises(TypeError): @@ -266,16 +274,24 @@ def test_union(): }) assert isinstance(name, MixedName.EastAsianName) with raises(ValueError) as e: - MixedName.__nirum_deserialize__({ + MixedName.__nirum_deserialize__({ # invalid field types '_type': 'mixed_name', '_tag': 'east_asian_name', - 'family_name': 404, - 'given_name': 503, + 'family_name': 404, # not a text + 'given_name': 503, # not a text }) assert str(e.value) == '''\ family_name: '404' is not a string. given_name: '503' is not a string.\ -''' +''' # message can contain multiple errors + n = MixedName.__nirum_deserialize__({ # invalid field types + '_type': 'mixed_name', + '_tag': 'east_asian_name', + 'family_name': u'John', + 'given_name': u'Doe', + 'extra': u'it does not matter', + }) + assert n == EastAsianName(family_name=u'John', given_name=u'Doe') def test_union_with_special_case():