Initializing a pydantic dataclass from json
Question:
I’m in the process of converting existing dataclasses
in my project to pydantic-dataclasses
, I’m using these dataclasses
to represent models I need to both encode-to and parse-from json.
Here’s an example of my current approach that is not good enough for my use case, I have a class A
that I want to both convert into a dict (to later be converted written as json) and to read from that dict.
But the only way I can find to parse the json back into a model gives me back the underlying BaseModel
and not the dataclass
.
note that I’m using the asdict
function to convert the dataclass
to a dict
as it’s what the pydantic_encoder
uses to convert the dataclass
to json, and using the pydantic_encoder
what the documentation recommends to convert a pydantic-dataclass
to json:
https://pydantic-docs.helpmanual.io/usage/dataclasses/
from dataclasses import asdict
from pydantic.dataclasses import dataclass
from pydantic import BaseModel
@dataclass
class A:
x: str
a = A("string")
a_dict = asdict(a)
parsed_a = A.__pydantic_model__.parse_obj(a_dict)
print(f"type of a: {type(a)}")
print(f"type of parsed_a: {type(parsed_a)}")
print(f"a is instance of A: {isinstance(a, A)}")
print(f"parsed_a is instance of A: {isinstance(parsed_a, A)}")
print(f"a is instance of BaseModel: {isinstance(a, BaseModel)}")
print(f"parsed_a is instance of BaseModel: {isinstance(parsed_a, BaseModel)}")
output:
type of a: <class '__main__.A'>
type of parsed_a: <class '__main__.A'>
a is instance of A: True
parsed_a is instance of A: False
a is instance of BaseModel: False
parsed_a is instance of BaseModel: True
Is there maybe a way to initialize A
from the parsed BaseModel
?
Answers:
I managed to address this issue by unpacking the properties of the parsed BaseModel
and initializing the dataclass
with them.
this solution works recursively for Pydantic dataclasses whose properties are either pydantic-dataclasses or primitives
please note that this solution doesn’t work for for Unions and Generics (yet).
def pydantic_dataclass_from_json_dict(json_dict: dict, pydantic_dataclass_type) -> Any:
base_model = pydantic_dataclass_type.__pydantic_model__.parse_obj(json_dict)
base_mode_fields = base_model.__fields__
dataclass_fields = dataclasses.fields(pydantic_dataclass_type)
values = []
for base_model_field_name, base_model_field in base_mode_fields.items():
value = getattr(base_model, base_model_field_name)
dataclass_field = [field for field in dataclass_fields if field.name == base_model_field.name][0]
if is_dataclass(dataclass_field):
converted_value = pydantic_dataclass_from_json_dict(value, dataclass_field.type)
values.append(converted_value)
else:
values.append(value)
dataclass_object = pydantic_dataclass_type(*values)
return dataclass_object
I think I arrive a little bit late to the party, but I think this answer may come handy for future users having the same question.
To convert the dataclass
to json you can use the combination that you are already using using (asdict
plus json.dump
).
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str
user = User(id=123, name="James")
d = asdict(user) # {'id': 123, 'name': 'James'
user_json = json.dumps(d)
print(user_json) # '{"id": 123, "name": "James"}'
# Or directly with pydantic_encoder
json.dumps(user, default=pydantic_encoder)
Then from the raw json
you can use a BaseModel
and the parse_raw
method.
If you want to deserialize json into pydantic
instances, I recommend you using the parse_raw
method:
user = User.__pydantic_model__.parse_raw('{"id": 123, "name": "James"}')
print(user)
# id=123 name='James'
Otherwise, if you want to keep the dataclass:
json_raw = '{"id": 123, "name": "James"}'
user_dict = json.loads(json_raw)
user = User(**user_dict)
Well, I tried the above ways but it’s error because pydantic_model is not existing BaseModel attribute.
So I checked functions in the class model.
I found ‘parse_raw()’ in it and tried instead the above ways
It’s good working
Let use parse_raw() function
OK
class definition
from pydantic import BaseModel
class Response_data(BaseModel):
status_code: int
text: str
reason: str
class Config:
orm_mode = True
Json to BaseModel convert
json = f(x)
response = Response_data.parse_raw(json)
print(response.dist())
then response is just Json string
we can convert json string to pedantic BaseModel using the above way
Now it’s possible to solve this issue in a simple manner, in my opinion.
Adding to @Guillem example:
from pydantic.dataclasses import dataclass
from pydantic.tools import parse_obj_as
import dataclasses
import json
@dataclass
class User:
id: int
name: str
user = User(id=123, name="James")
user_json = json.dumps(dataclasses.asdict(user))
print(user_json) # '{"id": 123, "name": "James"}'
user_dict = json.loads(user_json)
user = parse_obj_as(User, user_dict)
print(user) # User(id=123, name='James')
It works for recursively as well.
I’m in the process of converting existing dataclasses
in my project to pydantic-dataclasses
, I’m using these dataclasses
to represent models I need to both encode-to and parse-from json.
Here’s an example of my current approach that is not good enough for my use case, I have a class A
that I want to both convert into a dict (to later be converted written as json) and to read from that dict.
But the only way I can find to parse the json back into a model gives me back the underlying BaseModel
and not the dataclass
.
note that I’m using the asdict
function to convert the dataclass
to a dict
as it’s what the pydantic_encoder
uses to convert the dataclass
to json, and using the pydantic_encoder
what the documentation recommends to convert a pydantic-dataclass
to json:
https://pydantic-docs.helpmanual.io/usage/dataclasses/
from dataclasses import asdict
from pydantic.dataclasses import dataclass
from pydantic import BaseModel
@dataclass
class A:
x: str
a = A("string")
a_dict = asdict(a)
parsed_a = A.__pydantic_model__.parse_obj(a_dict)
print(f"type of a: {type(a)}")
print(f"type of parsed_a: {type(parsed_a)}")
print(f"a is instance of A: {isinstance(a, A)}")
print(f"parsed_a is instance of A: {isinstance(parsed_a, A)}")
print(f"a is instance of BaseModel: {isinstance(a, BaseModel)}")
print(f"parsed_a is instance of BaseModel: {isinstance(parsed_a, BaseModel)}")
output:
type of a: <class '__main__.A'>
type of parsed_a: <class '__main__.A'>
a is instance of A: True
parsed_a is instance of A: False
a is instance of BaseModel: False
parsed_a is instance of BaseModel: True
Is there maybe a way to initialize A
from the parsed BaseModel
?
I managed to address this issue by unpacking the properties of the parsed BaseModel
and initializing the dataclass
with them.
this solution works recursively for Pydantic dataclasses whose properties are either pydantic-dataclasses or primitives
please note that this solution doesn’t work for for Unions and Generics (yet).
def pydantic_dataclass_from_json_dict(json_dict: dict, pydantic_dataclass_type) -> Any:
base_model = pydantic_dataclass_type.__pydantic_model__.parse_obj(json_dict)
base_mode_fields = base_model.__fields__
dataclass_fields = dataclasses.fields(pydantic_dataclass_type)
values = []
for base_model_field_name, base_model_field in base_mode_fields.items():
value = getattr(base_model, base_model_field_name)
dataclass_field = [field for field in dataclass_fields if field.name == base_model_field.name][0]
if is_dataclass(dataclass_field):
converted_value = pydantic_dataclass_from_json_dict(value, dataclass_field.type)
values.append(converted_value)
else:
values.append(value)
dataclass_object = pydantic_dataclass_type(*values)
return dataclass_object
I think I arrive a little bit late to the party, but I think this answer may come handy for future users having the same question.
To convert the dataclass
to json you can use the combination that you are already using using (asdict
plus json.dump
).
from pydantic.dataclasses import dataclass
@dataclass
class User:
id: int
name: str
user = User(id=123, name="James")
d = asdict(user) # {'id': 123, 'name': 'James'
user_json = json.dumps(d)
print(user_json) # '{"id": 123, "name": "James"}'
# Or directly with pydantic_encoder
json.dumps(user, default=pydantic_encoder)
Then from the raw json
you can use a BaseModel
and the parse_raw
method.
If you want to deserialize json into pydantic
instances, I recommend you using the parse_raw
method:
user = User.__pydantic_model__.parse_raw('{"id": 123, "name": "James"}')
print(user)
# id=123 name='James'
Otherwise, if you want to keep the dataclass:
json_raw = '{"id": 123, "name": "James"}'
user_dict = json.loads(json_raw)
user = User(**user_dict)
Well, I tried the above ways but it’s error because pydantic_model is not existing BaseModel attribute.
So I checked functions in the class model.
I found ‘parse_raw()’ in it and tried instead the above ways
It’s good working
Let use parse_raw() function
OK
class definition
from pydantic import BaseModel
class Response_data(BaseModel):
status_code: int
text: str
reason: str
class Config:
orm_mode = True
Json to BaseModel convert
json = f(x)
response = Response_data.parse_raw(json)
print(response.dist())
then response is just Json string
we can convert json string to pedantic BaseModel using the above way
Now it’s possible to solve this issue in a simple manner, in my opinion.
Adding to @Guillem example:
from pydantic.dataclasses import dataclass
from pydantic.tools import parse_obj_as
import dataclasses
import json
@dataclass
class User:
id: int
name: str
user = User(id=123, name="James")
user_json = json.dumps(dataclasses.asdict(user))
print(user_json) # '{"id": 123, "name": "James"}'
user_dict = json.loads(user_json)
user = parse_obj_as(User, user_dict)
print(user) # User(id=123, name='James')
It works for recursively as well.