Skip to content

Commit

Permalink
fix: JSON deserialize Decimal (#31)
Browse files Browse the repository at this point in the history

Signed-off-by: Jan Kowalleck <jan.kowalleck@gmail.com>
  • Loading branch information
jkowalleck committed Oct 8, 2023
1 parent 7bb0d1b commit b6dc66a
Show file tree
Hide file tree
Showing 27 changed files with 63 additions and 10 deletions.
2 changes: 2 additions & 0 deletions serializable/__init__.py
Expand Up @@ -319,6 +319,8 @@ def from_json(cls: Type[_T], data: Dict[str, Any]) -> Optional[_T]:
if global_klass_name in ObjectMetadataLibrary.klass_mappings:
_data[k] = prop_info.concrete_type.from_json(data=v)
else:
if prop_info.concrete_type is Decimal:
v = str(v)
_data[k] = prop_info.concrete_type(v)
except AttributeError as e:
logging.error(f'There was an AttributeError deserializing JSON to {cls}.{os.linesep}'
Expand Down
13 changes: 9 additions & 4 deletions tests/base.py
Expand Up @@ -75,12 +75,17 @@ def assertDeepEqual(self: Union[TestCase, 'DeepCompareMixin'],
self.maxDiff = _omd

def __deepDict(self, o: Any) -> Any:
if isinstance(o, (list, tuple)):
if isinstance(o, tuple):
return tuple(self.__deepDict(i) for i in o)
if isinstance(o, list):
return list(self.__deepDict(i) for i in o)
if isinstance(o, dict):
return {k: self.__deepDict(v) for k, v in o}
return {k: self.__deepDict(v) for k, v in o.items()}
if isinstance(o, set):
return tuple(sorted((self.__deepDict(i) for i in o), key=repr))
# this method returns dict. `dict` is not hashable, so use `tuple` instead.
return tuple(self.__deepDict(i) for i in sorted(o, key=hash)) + ('%conv:%set',)
if hasattr(o, '__dict__'):
return {k: self.__deepDict(v) for k, v in vars(o).items() if not (k.startswith('__') and k.endswith('__'))}
d = {a: self.__deepDict(v) for a, v in o.__dict__.items() if '__' not in a}
d['%conv'] = str(type(o))
return d
return o
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1-v1.xml
Expand Up @@ -30,4 +30,5 @@
</chapter>
</chapters>
<references />
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1-v2.xml
Expand Up @@ -31,4 +31,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1-v3.xml
Expand Up @@ -30,4 +30,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1-v4.xml
Expand Up @@ -40,4 +40,5 @@
</reference>
<reference ref="my-ref-1" />
</references>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-1.xml
Expand Up @@ -29,4 +29,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
Expand Up @@ -2,6 +2,7 @@
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"isbnNumber": "978-1942788294",
"rating": 9.8,
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-v2.json
Expand Up @@ -2,6 +2,7 @@
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"isbnNumber": "978-1942788294",
"rating": 9.8,
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-v3.json
Expand Up @@ -2,6 +2,7 @@
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"isbnNumber": "978-1942788294",
"rating": 9.8,
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case-v4.json
Expand Up @@ -2,6 +2,7 @@
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"isbnNumber": "978-1942788294",
"rating": 9.8,
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
Expand Down
@@ -1,6 +1,7 @@
{
"something_to_be_ignored": "some_value",
"title": "{J} The Phoenix Project",
"rating": 9.8,
"isbnNumber": "978-1942788294",
"edition": {
"number": 5,
Expand Down
Expand Up @@ -29,4 +29,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-camel-case.json
@@ -1,6 +1,7 @@
{
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"rating": 9.8,
"isbnNumber": "978-1942788294",
"edition": {
"number": 5,
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-defaultNS-isset-v4.xml
Expand Up @@ -40,4 +40,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
@@ -1 +1 @@
<book xmlns="http://the.phoenix.project/testing/defaultNS" isbn_number="978-1942788294"><id>f3758bf0-0ff7-4366-a5e5-c209d4352b2d</id><title>{X} The Phoenix Project</title><edition number="5">5th Anniversary Limited Edition</edition><publish_date>2018-04-16</publish_date><author>Karl Ranseier</author><type>fiction</type><publisher><name>IT Revolution Press LLC</name></publisher><chapters><chapter><number>1</number><title>Tuesday, September 2</title></chapter><chapter><number>2</number><title>Tuesday, September 2</title></chapter><chapter><number>3</number><title>Tuesday, September 2</title></chapter><chapter><number>4</number><title>Wednesday, September 3</title></chapter></chapters></book>
<book xmlns="http://the.phoenix.project/testing/defaultNS" isbn_number="978-1942788294"><id>f3758bf0-0ff7-4366-a5e5-c209d4352b2d</id><title>{X} The Phoenix Project</title><edition number="5">5th Anniversary Limited Edition</edition><publish_date>2018-04-16</publish_date><author>Karl Ranseier</author><type>fiction</type><publisher><name>IT Revolution Press LLC</name></publisher><chapters><chapter><number>1</number><title>Tuesday, September 2</title></chapter><chapter><number>2</number><title>Tuesday, September 2</title></chapter><chapter><number>3</number><title>Tuesday, September 2</title></chapter><chapter><number>4</number><title>Wednesday, September 3</title></chapter></chapters><rating>9.8</rating></book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-defaultNS-mixed-v4.xml
Expand Up @@ -41,4 +41,5 @@
<my:title>Wednesday, September 3</my:title>
</my:chapter>
</my:chapters>
<my:rating>9.8</my:rating>
</my:book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-defaultNS-unset-v4.xml
Expand Up @@ -40,4 +40,5 @@
<my:title>Wednesday, September 3</my:title>
</my:chapter>
</my:chapters>
<my:rating>9.8</my:rating>
</my:book>
@@ -1 +1 @@
<ns0:book xmlns:ns0="http://the.phoenix.project/testing/defaultNS" ns0:isbn_number="978-1942788294"><ns0:id>f3758bf0-0ff7-4366-a5e5-c209d4352b2d</ns0:id><ns0:title>{X} The Phoenix Project</ns0:title><ns0:edition ns0:number="5">5th Anniversary Limited Edition</ns0:edition><ns0:publish_date>2018-04-16</ns0:publish_date><ns0:author>Karl Ranseier</ns0:author><ns0:type>fiction</ns0:type><ns0:publisher><ns0:name>IT Revolution Press LLC</ns0:name></ns0:publisher><ns0:chapters><ns0:chapter><ns0:number>1</ns0:number><ns0:title>Tuesday, September 2</ns0:title></ns0:chapter><ns0:chapter><ns0:number>2</ns0:number><ns0:title>Tuesday, September 2</ns0:title></ns0:chapter><ns0:chapter><ns0:number>3</ns0:number><ns0:title>Tuesday, September 2</ns0:title></ns0:chapter><ns0:chapter><ns0:number>4</ns0:number><ns0:title>Wednesday, September 3</ns0:title></ns0:chapter></ns0:chapters></ns0:book>
<ns0:book xmlns:ns0="http://the.phoenix.project/testing/defaultNS" ns0:isbn_number="978-1942788294"><ns0:id>f3758bf0-0ff7-4366-a5e5-c209d4352b2d</ns0:id><ns0:title>{X} The Phoenix Project</ns0:title><ns0:edition ns0:number="5">5th Anniversary Limited Edition</ns0:edition><ns0:publish_date>2018-04-16</ns0:publish_date><ns0:author>Karl Ranseier</ns0:author><ns0:type>fiction</ns0:type><ns0:publisher><ns0:name>IT Revolution Press LLC</ns0:name></ns0:publisher><ns0:chapters><ns0:chapter><ns0:number>1</ns0:number><ns0:title>Tuesday, September 2</ns0:title></ns0:chapter><ns0:chapter><ns0:number>2</ns0:number><ns0:title>Tuesday, September 2</ns0:title></ns0:chapter><ns0:chapter><ns0:number>3</ns0:number><ns0:title>Tuesday, September 2</ns0:title></ns0:chapter><ns0:chapter><ns0:number>4</ns0:number><ns0:title>Wednesday, September 3</ns0:title></ns0:chapter></ns0:chapters><ns0:rating>9.8</ns0:rating></ns0:book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-kebab-case-1-v2.xml
Expand Up @@ -31,4 +31,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-kebab-case-1.xml
Expand Up @@ -29,4 +29,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-kebab-case.json
Expand Up @@ -2,6 +2,7 @@
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"isbn-number": "978-1942788294",
"rating": 9.8,
"edition": {
"number": 5,
"name": "5th Anniversary Limited Edition"
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-snake-case-1.xml
Expand Up @@ -29,4 +29,5 @@
<title>Wednesday, September 3</title>
</chapter>
</chapters>
<rating>9.8</rating>
</book>
1 change: 1 addition & 0 deletions tests/fixtures/the-phoenix-project-snake-case.json
@@ -1,6 +1,7 @@
{
"id": "f3758bf0-0ff7-4366-a5e5-c209d4352b2d",
"title": "{J} The Phoenix Project",
"rating": 9.8,
"isbn_number": "978-1942788294",
"edition": {
"number": 5,
Expand Down
20 changes: 17 additions & 3 deletions tests/model.py
Expand Up @@ -19,6 +19,7 @@

import re
from datetime import date
from decimal import Decimal
from enum import Enum, unique
from typing import Any, Dict, Iterable, List, Optional, Set, Type
from uuid import UUID, uuid4
Expand Down Expand Up @@ -232,7 +233,8 @@ class Book:
def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[str],
publisher: Optional[Publisher] = None, chapters: Optional[Iterable[Chapter]] = None,
edition: Optional[BookEdition] = None, type: BookType = BookType.FICTION,
id: Optional[UUID] = None, references: Optional[Iterable[BookReference]] = None) -> None:
id: Optional[UUID] = None, references: Optional[Iterable[BookReference]] = None,
rating: Optional[Decimal] = None) -> None:
self._id = id or uuid4()
self._title = title
self._isbn = isbn
Expand All @@ -243,6 +245,7 @@ def __init__(self, title: str, isbn: str, publish_date: date, authors: Iterable[
self.chapters = list(chapters or [])
self._type = type
self.references = set(references or [])
self.rating = Decimal('NaN') if rating is None else rating

@property
@serializable.xml_sequence(1)
Expand Down Expand Up @@ -310,13 +313,23 @@ def references(self) -> Set[BookReference]:
def references(self, references: Iterable[BookReference]) -> None:
self._references = set(references)

@property
@serializable.xml_sequence(20)
def rating(self) -> Decimal:
return self._rating

@rating.setter
def rating(self, rating: Decimal) -> None:
self._rating = rating


ThePhoenixProject_v1 = 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=5, name='5th Anniversary Limited Edition'),
id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d')
id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
rating=Decimal('9.8')
)

ThePhoenixProject_v1.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
Expand All @@ -329,7 +342,8 @@ def references(self, references: Iterable[BookReference]) -> None:
authors=['Gene Kim', 'Kevin Behr', 'George Spafford'],
publisher=Publisher(name='IT Revolution Press LLC', address='10 Downing Street'),
edition=BookEdition(number=5, name='5th Anniversary Limited Edition'),
id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d')
id=UUID('f3758bf0-0ff7-4366-a5e5-c209d4352b2d'),
rating=Decimal('9.8')
)

ThePhoenixProject_v2.chapters.append(Chapter(number=1, title='Tuesday, September 2'))
Expand Down
5 changes: 5 additions & 0 deletions tests/test_json.py
Expand Up @@ -64,6 +64,7 @@ def test_deserialize_tfp_cc(self) -> None:
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.references, book.references)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_deserialize_tfp_cc_with_references(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand All @@ -79,6 +80,7 @@ def test_deserialize_tfp_cc_with_references(self) -> None:
self.assertEqual(ThePhoenixProject.chapters, book.chapters)
self.assertEqual(3, len(book.references))
self.assertEqual(ThePhoenixProject.references, book.references)
self.assertEqual(ThePhoenixProject.rating, book.rating)

def test_deserialize_tfp_cc_with_ignored(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand All @@ -91,6 +93,7 @@ def test_deserialize_tfp_cc_with_ignored(self) -> None:
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_serialize_tfp_kc(self) -> None:
CurrentFormatter.formatter = KebabCasePropertyNameFormatter
Expand All @@ -108,6 +111,7 @@ def test_deserialize_tfp_kc(self) -> None:
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_serialize_tfp_sc(self) -> None:
CurrentFormatter.formatter = SnakeCasePropertyNameFormatter
Expand All @@ -125,3 +129,4 @@ def test_deserialize_tfp_sc(self) -> None:
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)
9 changes: 8 additions & 1 deletion tests/test_xml.py
Expand Up @@ -75,7 +75,7 @@ def test_serialize_tfp_kc1_v2(self) -> None:

def test_serialize_tfp_sc1(self) -> None:
CurrentFormatter.formatter = SnakeCasePropertyNameFormatter
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-snake-case-1.xml')) as expected_xml:
with open(os.path.join(FIXTURES_DIRECTORY, 'the-phoenix-project-snake-case-1.xml'), 'r') as expected_xml:
self.assertEqualXml(expected_xml.read(), ThePhoenixProject.as_xml())

def test_serializable_no_defaultNS(self) -> None:
Expand Down Expand Up @@ -130,6 +130,7 @@ def test_deserialize_tfp_cc1(self) -> None:
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_deserialize_tfp_cc1_v2(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand All @@ -144,6 +145,7 @@ def test_deserialize_tfp_cc1_v2(self) -> None:
self.assertEqual(ThePhoenixProject.authors, book.authors)
self.assertEqual(ThePhoenixProject.chapters, book.chapters)
self.assertSetEqual(set(), book.references)
self.assertEqual(ThePhoenixProject.rating, book.rating)

def test_deserialize_tfp_cc1_v3(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand All @@ -158,6 +160,7 @@ def test_deserialize_tfp_cc1_v3(self) -> None:
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.references, book.references)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_deserialize_tfp_cc1_v4(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand All @@ -172,6 +175,7 @@ def test_deserialize_tfp_cc1_v4(self) -> None:
self.assertEqual(ThePhoenixProject.authors, book.authors)
self.assertEqual(ThePhoenixProject.chapters, book.chapters)
self.assertEqual(ThePhoenixProject.references, book.references)
self.assertEqual(ThePhoenixProject.rating, book.rating)

def test_deserialize_tfp_cc1_with_ignored(self) -> None:
CurrentFormatter.formatter = CamelCasePropertyNameFormatter
Expand All @@ -184,6 +188,7 @@ def test_deserialize_tfp_cc1_with_ignored(self) -> None:
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_deserialize_tfp_kc1(self) -> None:
CurrentFormatter.formatter = KebabCasePropertyNameFormatter
Expand All @@ -196,6 +201,7 @@ def test_deserialize_tfp_kc1(self) -> None:
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_deserialize_tfp_sc1(self) -> None:
CurrentFormatter.formatter = SnakeCasePropertyNameFormatter
Expand All @@ -208,6 +214,7 @@ def test_deserialize_tfp_sc1(self) -> None:
self.assertEqual(ThePhoenixProject_v1.publisher, book.publisher)
self.assertEqual(ThePhoenixProject_v1.authors, book.authors)
self.assertEqual(ThePhoenixProject_v1.chapters, book.chapters)
self.assertEqual(ThePhoenixProject_v1.rating, book.rating)

def test_deserializable_with_defaultNS(self) -> None:
"""regression test for https://github.com/madpah/serializable/issues/11"""
Expand Down

0 comments on commit b6dc66a

Please sign in to comment.