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

Issue1414: wrong behavior of json() method with nested models containing custom root #1429

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/1414-silversurfer34.md
@@ -0,0 +1 @@
Change the behavior of json() method to remove the extra `__root__` keys in the result when using nested models with custom root types
24 changes: 22 additions & 2 deletions pydantic/main.py
Expand Up @@ -428,8 +428,28 @@ def json(
exclude_defaults=exclude_defaults,
exclude_none=exclude_none,
)
if self.__custom_root_type__:
data = data[ROOT_KEY]

def reparent_root(d: 'DictStrAny') -> 'DictStrAny':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be done more easily with a change to _get_value, though I haven't tried myself.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to look at _get_value but it would spread the change also to dict() method and make some other tests failing (and might introduce new issues?).

"""
Parse the input dictionary, search for the keys "__root__" and take the associated values and reparent them
to "__root__" parent
"""
result = dict()
for k, v in d.items():
if isinstance(v, dict):
if k is not ROOT_KEY:
result[k] = reparent_root(v)
else:
return reparent_root(v)
else:
if k is not ROOT_KEY:
result[k] = v
else:
return v
return result

res = reparent_root(data)
data = res
return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs)

@classmethod
Expand Down
31 changes: 31 additions & 0 deletions tests/test_json.py
Expand Up @@ -204,6 +204,37 @@ class Model(BaseModel):
assert Model(__root__=['a', 'b']).json() == '["a", "b"]'


def test_encode_nested_root():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has nothing to do with .json() in particular, it should be true of .dict() too, so tests shouldn't be in this file.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure to understand your comment. Those tests build models with nested root as child elements, then calls the json method and the expected result is a json without the __root__ intermediate key.
(The behavior change concerns only the json method, doing it in dict method is breaking other tests)

class Pets(BaseModel):
__root__: List[str]

class PetHouse(BaseModel):
Animals: Pets
Location: str

assert (
PetHouse(**{'Animals': ['dog', 'cat'], 'Location': 'Montpellier'}).json()
== '{"Animals": ["dog", "cat"], "Location": "Montpellier"}'
)


def test_encode_several_nested_root():
class PetsAlias(BaseModel):
__root__: List[str]

class Pets(BaseModel):
__root__: PetsAlias

class PetHouse(BaseModel):
Animals: Pets
Location: str

assert (
PetHouse(**{'Animals': ['dog', 'cat'], 'Location': 'Montpellier'}).json()
== '{"Animals": ["dog", "cat"], "Location": "Montpellier"}'
)


def test_custom_decode_encode():
load_calls, dump_calls = 0, 0

Expand Down
17 changes: 17 additions & 0 deletions tests/test_schema.py
Expand Up @@ -1432,6 +1432,23 @@ class Model(BaseModel):
}


def test_nested_root_model():
class Pets(BaseModel):
__root__: List[str]

class PetHouse(BaseModel):
Animals: Pets
Location: str

assert PetHouse.schema() == {
'title': 'PetHouse',
'type': 'object',
'properties': {'Animals': {'$ref': '#/definitions/Pets'}, 'Location': {'title': 'Location', 'type': 'string'}},
'required': ['Animals', 'Location'],
'definitions': {'Pets': {'title': 'Pets', 'type': 'array', 'items': {'type': 'string'}}},
}


def test_new_type_schema():
a_type = NewType('a_type', int)
b_type = NewType('b_type', a_type)
Expand Down