diff --git a/CHANGES.md b/CHANGES.md index 8dc2017..a87b6b0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,19 @@ To be released. [#227]: https://github.com/spoqa/nirum/pull/227 +Version 0.3.1 +------------- + +Released on March 1, 2018. + +### 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 ------------- @@ -180,7 +193,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 2bec351..687263a 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 0a97689..b7c382c 100644 --- a/src/Nirum/Targets/Python.hs +++ b/src/Nirum/Targets/Python.hs @@ -948,7 +948,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: @@ -1097,7 +1101,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 65aeda5..103f823 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_default_tag():