From 9abbf3a3bd32435ac74bc98c3554ad3c71086036 Mon Sep 17 00:00:00 2001 From: Ramon Hagenaars <37958579+ramonhagenaars@users.noreply.github.com> Date: Thu, 9 Jun 2022 21:50:52 +0200 Subject: [PATCH] Fixed the bug that a string was unintentionally parsed as a datetime. (#171) Co-authored-by: Ramon --- README.md | 4 ++++ jsons/_load_impl.py | 6 +++-- jsons/_package_info.py | 2 +- jsons/deserializers/default_string.py | 3 +++ tests/test_str.py | 33 +++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/test_str.py diff --git a/README.md b/README.md index bea7f72..6321d77 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ list_of_tuples = jsons.load(some_dict, List[Tuple[AClass, AnotherClass]]) ## Recent updates +### 1.6.3 + +- Bugfix: a string was sometimes unintentionally parsed into a datetime. + ### 1.6.2 - Bugfix: `fork_inst`s were not propagated in `default_list_deserializer` (thanks to patrickguenther). diff --git a/jsons/_load_impl.py b/jsons/_load_impl.py index 2d2d322..724cdf2 100644 --- a/jsons/_load_impl.py +++ b/jsons/_load_impl.py @@ -79,6 +79,7 @@ def load( return json_obj if isinstance(cls, str): cls = get_cls_from_str(cls, json_obj, fork_inst) + original_cls = cls cls, meta_hints = _check_and_get_cls_and_meta_hints( json_obj, cls, fork_inst, kwargs.get('_inferred_cls', False)) @@ -88,12 +89,13 @@ def load( initial = kwargs.get('_initial', True) kwargs_ = { + 'meta_hints': meta_hints, # Overridable by kwargs. + **kwargs, 'strict': strict, 'fork_inst': fork_inst, 'attr_getters': attr_getters, - 'meta_hints': meta_hints, '_initial': False, - **kwargs + '_inferred_cls': cls is not original_cls, } return _do_load(json_obj, deserializer, cls, initial, **kwargs_) diff --git a/jsons/_package_info.py b/jsons/_package_info.py index bc37be5..f4781c8 100644 --- a/jsons/_package_info.py +++ b/jsons/_package_info.py @@ -1,5 +1,5 @@ __title__ = 'jsons' -__version__ = '1.6.2' +__version__ = '1.6.3' __author__ = 'Ramon Hagenaars' __author_email__ = 'ramon.hagenaars@gmail.com' __description__ = 'For serializing Python objects to JSON (dicts) and back' diff --git a/jsons/deserializers/default_string.py b/jsons/deserializers/default_string.py index 5024198..b20733e 100644 --- a/jsons/deserializers/default_string.py +++ b/jsons/deserializers/default_string.py @@ -17,6 +17,9 @@ def default_string_deserializer(obj: str, :param kwargs: any keyword arguments. :return: the deserialized obj. """ + target_is_str = cls is str and not kwargs.get('_inferred_cls') + if target_is_str: + return str(obj) try: result = load(obj, datetime, **kwargs) except DeserializationError: diff --git a/tests/test_str.py b/tests/test_str.py new file mode 100644 index 0000000..2ec5a35 --- /dev/null +++ b/tests/test_str.py @@ -0,0 +1,33 @@ +from datetime import datetime +from unittest import TestCase + +import jsons + + +class TestStr(TestCase): + def test_string_is_loaded_as_string(self): + + class C: + def __init__(self, x: str): + self.x = x + + fork = jsons.fork() + jsons.set_deserializer(lambda obj, _, **kwargs: datetime.strptime(obj, '%Y'), datetime, fork_inst=fork) + loaded = jsons.load({'x': '1025'}, C, strict=True, fork_inst=fork) + + self.assertIsInstance(loaded.x, str) + + def test_string_is_loaded_as_datetime(self): + + class C: + def __init__(self, x): + # x has no hint, so the type will be inferred. Since x is not + # explicitly targeted as str, it may get parsed as a datetime. + # And in this test, it should. + self.x = x + + fork = jsons.fork() + jsons.set_deserializer(lambda obj, _, **kwargs: datetime.strptime(obj, '%Y'), datetime, fork_inst=fork) + loaded = jsons.load({'x': '1025'}, C, strict=True, fork_inst=fork) + + self.assertIsInstance(loaded.x, datetime)