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

fix: keep old behaviour of json() by default #3542

Merged
merged 3 commits into from Dec 24, 2021
Merged
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
2 changes: 1 addition & 1 deletion docs/examples/exporting_models_json_forward_ref.py
Expand Up @@ -31,4 +31,4 @@ class Config:
User(name='John', address=Address(city='London', country='UK')),
],
)
print(wolfgang.json())
print(wolfgang.json(models_as_dict=False))
3 changes: 3 additions & 0 deletions docs/usage/exporting_models.md
Expand Up @@ -109,6 +109,9 @@ _(This script is complete, it should run "as is")_

### Serialising self-reference or other models

By default, models are serialised as dictionaries.
If you want to serialise them differently, you can add `models_as_dict=False` when calling `json()` method
and add the classes of the model in `json_encoders`.
In case of forward references, you can use a string with the class name instead of the class itself
```py
{!.tmp_examples/exporting_models_json_forward_ref.py!}
Expand Down
6 changes: 4 additions & 2 deletions pydantic/main.py
Expand Up @@ -454,6 +454,7 @@ def json(
exclude_defaults: bool = False,
exclude_none: bool = False,
encoder: Optional[Callable[[Any], Any]] = None,
models_as_dict: bool = True,
**dumps_kwargs: Any,
) -> str:
"""
Expand All @@ -469,11 +470,12 @@ def json(
exclude_unset = skip_defaults
encoder = cast(Callable[[Any], Any], encoder or self.__json_encoder__)

# We don't directly call `self.dict()`, which does exactly the same thing but
# with `to_dict = True` because we want to keep raw `BaseModel` instances and not as `dict`.
# We don't directly call `self.dict()`, which does exactly this with `to_dict=True`
# because we want to be able to keep raw `BaseModel` instances and not as `dict`.
# This allows users to write custom JSON encoders for given `BaseModel` classes.
data = dict(
self._iter(
to_dict=models_as_dict,
by_alias=by_alias,
include=include,
exclude=exclude,
Expand Down
58 changes: 54 additions & 4 deletions tests/test_json.py
Expand Up @@ -281,7 +281,7 @@ class Config:
assert m.json() == '{\n "a": 1,\n "b": "foo"\n}'


def test_json_nested_encode():
def test_json_nested_encode_models():
class Phone(BaseModel):
manufacturer: str
number: int
Expand Down Expand Up @@ -314,8 +314,58 @@ class Config:

timon.friend = pumbaa

assert iphone.json() == '{"manufacturer": "Apple", "number": 18002752273}'
assert iphone.json(models_as_dict=False) == '{"manufacturer": "Apple", "number": 18002752273}'
assert (
pumbaa.json() == '{"name": "Pumbaa", "SSN": 234, "birthday": 737424000.0, "phone": 18007267864, "friend": null}'
pumbaa.json(models_as_dict=False)
== '{"name": "Pumbaa", "SSN": 234, "birthday": 737424000.0, "phone": 18007267864, "friend": null}'
)
assert timon.json() == '{"name": "Timon", "SSN": 123, "birthday": 738892800.0, "phone": 18002752273, "friend": 234}'
assert (
timon.json(models_as_dict=False)
== '{"name": "Timon", "SSN": 123, "birthday": 738892800.0, "phone": 18002752273, "friend": 234}'
)


def test_custom_encode_fallback_basemodel():
class MyExoticType:
pass

def custom_encoder(o):
if isinstance(o, MyExoticType):
return 'exo'
raise TypeError('not serialisable')

class Foo(BaseModel):
x: MyExoticType

class Config:
arbitrary_types_allowed = True

class Bar(BaseModel):
foo: Foo

assert Bar(foo=Foo(x=MyExoticType())).json(encoder=custom_encoder) == '{"foo": {"x": "exo"}}'


def test_custom_encode_error():
class MyExoticType:
pass

def custom_encoder(o):
raise TypeError('not serialisable')

class Foo(BaseModel):
x: MyExoticType

class Config:
arbitrary_types_allowed = True

with pytest.raises(TypeError, match='not serialisable'):
Foo(x=MyExoticType()).json(encoder=custom_encoder)


def test_recursive():
class Model(BaseModel):
value: Optional[str]
nested: Optional[BaseModel]

assert Model(value=None, nested=Model(value=None)).json(exclude_none=True) == '{"nested": {}}'