Skip to content

Commit

Permalink
fix: serializer omit None values as expected (#136)
Browse files Browse the repository at this point in the history

Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck authored Sep 16, 2024
1 parent d26c9ec commit 1a0e14b
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 4 deletions.
10 changes: 7 additions & 3 deletions serializable/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,11 @@ def as_xml(self: Any, view_: Optional[Type[ViewType]] = None,
elif prop_info.is_enum:
v = v.value

if v is None:
v = prop_info.get_none_value_for_view(view_=view_)
if v is None:
continue

this_e_attributes[_namespace_element_name(new_key, xmlns)] = \
_xs_string_mod_apply(str(v), prop_info.xml_string_config)

Expand All @@ -453,9 +458,6 @@ def as_xml(self: Any, view_: Optional[Type[ViewType]] = None,
# Skip as rendering for a view and this Property is not registered form this View
continue

if v is None:
v = prop_info.get_none_value_for_view(view_=view_)

new_key = BaseNameFormatter.decode_handle_python_builtins_and_keywords(name=k)

if not prop_info:
Expand All @@ -464,6 +466,8 @@ def as_xml(self: Any, view_: Optional[Type[ViewType]] = None,
if not prop_info.is_xml_attribute:
new_key = prop_info.custom_names.get(SerializationType.XML, new_key)

if v is None:
v = prop_info.get_none_value_for_view(view_=view_)
if v is None:
SubElement(this_e, _namespace_element_name(tag_name=new_key, xmlns=xmlns))
continue
Expand Down
19 changes: 19 additions & 0 deletions tests/fixtures/the-phoenix-project-bookedition-none.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"George Spafford",
"Gene Kim",
"Kevin Behr"
],
"edition": {
"name": "Preview Edition"
},
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"isbnNumber": "978-1942788294",
"publishDate": "2018-04-16",
"publisher": {
"name": "IT Revolution Press LLC"
},
"rating": 9.8,
"title": "{J} The Phoenix Project",
"type": "fiction"
}
14 changes: 14 additions & 0 deletions tests/fixtures/the-phoenix-project-bookedition-none.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<book isbnNumber="978-1942788294">
<id>f3758bf0-0ff7-4366-a5e5-c209d4352b2d</id>
<title>{X} The Phoenix Project</title>
<edition>Preview Edition</edition>
<publishDate>2018-04-16</publishDate>
<author>Kevin Behr</author>
<author>George Spafford</author>
<author>Gene Kim</author>
<type>fiction</type>
<publisher>
<name>IT Revolution Press LLC</name>
</publisher>
<rating>9.8</rating>
</book>
33 changes: 33 additions & 0 deletions tests/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ def xml_deserialize(cls, o: str) -> str:
return re.sub(r'^\{X} ', '', o)


class BookEditionHelper(BaseHelper):

@classmethod
def serialize(cls, o: Any) -> Optional[int]:
return o \
if isinstance(o, int) and o > 0 \
else None

@classmethod
def deserialize(cls, o: Any) -> int:
try:
return int(o)
except Exception:
return 1


@serializable.serializable_class
class Chapter:

Expand Down Expand Up @@ -170,6 +186,7 @@ def __init__(self, *, number: int, name: str) -> None:

@property
@serializable.xml_attribute()
@serializable.type_mapping(BookEditionHelper)
def number(self) -> int:
return self._number

Expand Down Expand Up @@ -469,6 +486,22 @@ def stock_ids(self) -> Set[StockId]:

# endregion ThePhoenixProject_unnormalized

# region ThePhoenixProject_attr_serialized_none

# a case where an attribute is serialized to `None` and deserialized from it
ThePhoenixProject_attr_serialized_none = Book(
title='The Phoenix Project',
isbn='978-1942788294',
publish_date=date(year=2018, month=4, day=16),
authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
publisher=Publisher(name='IT Revolution Press LLC'),
edition=BookEdition(number=0, name='Preview Edition'),
id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
rating=Decimal('9.8')
)

# endregion ThePhoenixProject_attr_serialized_none

if __name__ == '__main__':
tpp_as_xml = ThePhoenixProject.as_xml() # type:ignore[attr-defined]
tpp_as_json = ThePhoenixProject.as_json() # type:ignore[attr-defined]
Expand Down
17 changes: 16 additions & 1 deletion tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,21 @@
SnakeCasePropertyNameFormatter,
)
from tests.base import FIXTURES_DIRECTORY, BaseTestCase
from tests.model import Book, SchemaVersion2, SchemaVersion3, SchemaVersion4, ThePhoenixProject, ThePhoenixProject_v1
from tests.model import (
Book,
SchemaVersion2,
SchemaVersion3,
SchemaVersion4,
ThePhoenixProject,
ThePhoenixProject_attr_serialized_none,
ThePhoenixProject_v1,
)


class TestJson(BaseTestCase):

# region test_serialize

def test_serialize_tfp_cc(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-camel-case.json')) as expected_json:
Expand Down Expand Up @@ -130,3 +140,8 @@ def test_deserialize_tfp_sc(self) -> None:
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_serialize_attr_none(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-bookedition-none.json')) as expected_json:
self.assertEqualJson(expected_json.read(), ThePhoenixProject_attr_serialized_none.as_json())
6 changes: 6 additions & 0 deletions tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
SchemaVersion3,
SchemaVersion4,
ThePhoenixProject,
ThePhoenixProject_attr_serialized_none,
ThePhoenixProject_unnormalized,
ThePhoenixProject_v1,
)
Expand Down Expand Up @@ -129,6 +130,11 @@ def test_serialize_unnormalized(self) -> None:
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-camel-case-1-v4.xml')) as expected_xml:
self.assertEqualXml(expected_xml.read(), ThePhoenixProject_unnormalized.as_xml(SchemaVersion4))

def test_serialize_attr_none(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-bookedition-none.xml')) as expected_xml:
self.assertEqualXml(expected_xml.read(), ThePhoenixProject_attr_serialized_none.as_xml(SchemaVersion4))

# endregion test_serialize

# region test_deserialize
Expand Down

0 comments on commit 1a0e14b

Please sign in to comment.