From 7f576a9374f4d22481c2bbd1bc38e436a7101e8e Mon Sep 17 00:00:00 2001 From: silversurfer34 Date: Fri, 24 Apr 2020 15:54:49 +0200 Subject: [PATCH 1/4] Fix json() behavior for nested models with __root__ --- pydantic/main.py | 25 +++++++++++++++++++++++-- tests/test_json.py | 14 ++++++++++++++ tests/test_schema.py | 17 +++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/pydantic/main.py b/pydantic/main.py index e1161d7fcd..e62d99bb96 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -428,8 +428,29 @@ 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': + """ + 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: + if k is not None: + 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 + + data = reparent_root(data) + return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs) @classmethod diff --git a/tests/test_json.py b/tests/test_json.py index cf916f6ae9..b0f0346e9d 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -204,6 +204,20 @@ class Model(BaseModel): assert Model(__root__=['a', 'b']).json() == '["a", "b"]' +def test_encode_nested_root(): + 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_custom_decode_encode(): load_calls, dump_calls = 0, 0 diff --git a/tests/test_schema.py b/tests/test_schema.py index e78ddb69e0..10496f95d2 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -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) From 8d30c7189375fd17d722ff0f20383eed6747e298 Mon Sep 17 00:00:00 2001 From: silversurfer34 Date: Fri, 24 Apr 2020 16:52:01 +0200 Subject: [PATCH 2/4] adding missing test --- pydantic/main.py | 11 +++++------ tests/test_json.py | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pydantic/main.py b/pydantic/main.py index e62d99bb96..de671c74b5 100644 --- a/pydantic/main.py +++ b/pydantic/main.py @@ -438,10 +438,9 @@ def reparent_root(d: 'DictStrAny') -> 'DictStrAny': for k, v in d.items(): if isinstance(v, dict): if k is not ROOT_KEY: - if k is not None: - result[k] = reparent_root(v) - else: - return reparent_root(v) + result[k] = reparent_root(v) + else: + return reparent_root(v) else: if k is not ROOT_KEY: result[k] = v @@ -449,8 +448,8 @@ def reparent_root(d: 'DictStrAny') -> 'DictStrAny': return v return result - data = reparent_root(data) - + res = reparent_root(data) + data = res return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs) @classmethod diff --git a/tests/test_json.py b/tests/test_json.py index b0f0346e9d..816d95573e 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -218,6 +218,23 @@ class PetHouse(BaseModel): ) +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 From 2c477dc58ac926073a4e684eeedca6681af0f95e Mon Sep 17 00:00:00 2001 From: silversurfer34 Date: Fri, 24 Apr 2020 17:38:47 +0200 Subject: [PATCH 3/4] missing md file --- changes/1414-silversurfer34.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changes/1414-silversurfer34.md diff --git a/changes/1414-silversurfer34.md b/changes/1414-silversurfer34.md new file mode 100644 index 0000000000..8c8220feed --- /dev/null +++ b/changes/1414-silversurfer34.md @@ -0,0 +1,2 @@ +Fix proposal for the issue 1414, wrong behavior of json method when there are nested models with custom root. +Once the dict is generated, it is parsed and each time an item has the __root__ key, its associated value is attached to the parent key \ No newline at end of file From 0b9bae0ef43540a52eacebca2a35d131f8bc26fd Mon Sep 17 00:00:00 2001 From: silversurfer34 Date: Mon, 4 May 2020 10:48:57 +0200 Subject: [PATCH 4/4] Updating changes file according to pull request comments --- changes/1414-silversurfer34.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/changes/1414-silversurfer34.md b/changes/1414-silversurfer34.md index 8c8220feed..52c02392d6 100644 --- a/changes/1414-silversurfer34.md +++ b/changes/1414-silversurfer34.md @@ -1,2 +1 @@ -Fix proposal for the issue 1414, wrong behavior of json method when there are nested models with custom root. -Once the dict is generated, it is parsed and each time an item has the __root__ key, its associated value is attached to the parent key \ No newline at end of file +Change the behavior of json() method to remove the extra `__root__` keys in the result when using nested models with custom root types \ No newline at end of file