Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DeserializationError when loading into a class with an Optional bson.ObjectID #176

Open
yurirocha15 opened this issue Jun 20, 2022 · 2 comments

Comments

@yurirocha15
Copy link

yurirocha15 commented Jun 20, 2022

Hello, I encountered an issue when loading data into a class with an Optional ObjectID field.

Environment

Python 3.8.5
jsons 1.6.3

Issue

  • If a class has a non-optional ObjectID field, jsons works fine.
  • If a class has an optional ObjectID field, but the field is "None" in the dictionary, jsons also works fine.
  • However, when loading an ObjectID into an Optional[ObjectID] field, the following error happens:
Traceback (most recent call last):
  File "/home/yuri/ex.py", line 25, in <module>
    print(jsons.load(data2, TestData))
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/_load_impl.py", line 99, in load
    return _do_load(json_obj, deserializer, cls, initial, **kwargs_)
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/_load_impl.py", line 111, in _do_load
    result = deserializer(json_obj, cls, **kwargs)
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/deserializers/default_object.py", line 40, in default_object_deserializer
    constructor_args = _get_constructor_args(obj, cls, **kwargs)
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/deserializers/default_object.py", line 64, in _get_constructor_args
    key, value = _get_value_for_attr(obj=obj,
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/deserializers/default_object.py", line 94, in _get_value_for_attr
    result = sig_key, _get_value_from_obj(obj, cls, sig, sig_key,
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/deserializers/default_object.py", line 140, in _get_value_from_obj
    value = load(obj[sig_key], cls_, meta_hints=new_hints, **kwargs)
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/_load_impl.py", line 82, in load
    cls, meta_hints = _check_and_get_cls_and_meta_hints(
  File "/home/yuri/.pyenv/versions/3.8.5/lib/python3.8/site-packages/jsons/_load_impl.py", line 198, in _check_and_get_cls_and_meta_hints
    raise DeserializationError(msg, json_obj, cls)
jsons.exceptions.DeserializationError: Invalid type: "bson.objectid.ObjectId", only arguments of the following types are allowed: str, int, float, bool, list, tuple, set, dict, NoneType

Reproducing the Error

Here is an example script where this error is happening:

from dataclasses import dataclass
from typing import Optional

import jsons
from bson.objectid import ObjectId

@dataclass
class TestData:
    """Test Data"""

    non_optional_id: ObjectId
    optional_id: Optional[ObjectId] = None

data1 = {
    "non_optional_id": ObjectId(),
}

data2 = {
    "non_optional_id": ObjectId(),
    "optional_id": ObjectId(),
}

# prints: TestData(non_optional_id=ObjectId('62b1411ddde5c1f9fc9842a1'), optional_id=None)
print(jsons.load(data1, TestData)) 
# jsons.exceptions.DeserializationError: Invalid type: "bson.objectid.ObjectId", only arguments of the following types are allowed: str, int, float, bool, list, tuple, set, dict, NoneType
print(jsons.load(data2, TestData)) 
@yurirocha15 yurirocha15 changed the title Error when loading into a class with an Optional bson.ObjectID DeserializationError when loading into a class with an Optional bson.ObjectID Jun 20, 2022
@ramonhagenaars
Copy link
Owner

Hi @yurirocha15 ,

You are trying to load a "half json-serialized" object; a dict that contains objects that are not json-compatible (bson.objectid.ObjectId instances).

I traced down what happened. In non-strict mode, when an object is loaded into a class of which it already is an instance (e.g. load("this is a string", str)), the loading is skipped and that instance is returned instantly. This is what happens with your data1. With data2, it compares the type ObjectId with Optional which are inequal, therefore it is not skipped. Then, before actually trying to deserialize an ObjectId, it stops as ObjectId is not a json value (one of str, int, float, bool, list, tuple, set, dict, NoneType).

Do you really need to load a "half json-serialized" object, or would it be possible in your case to just do the following?

TestData(**data2)

@yurirocha15
Copy link
Author

yurirocha15 commented Jul 29, 2022

This would be possible in the example I posted but not in my real use case. I am parsing a nested dictionary into nested dataclasses; this optional parameter is in the deeper layers of the dictionary. Therefore, using the root dataclass constructor directly is not an option for me.

Is there any way to solve this using custom serializers/deserializers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants