How to map values from nested dict to Pydantic Model?
Question:
I am trying to map a value from a nested dict/json to my Pydantic model. For me, this works well when my json/dict has a flat structure. However, I am struggling to map values from a nested structure to my Pydantic Model.
Lets assume I have a json/dict in the following format:
d = {
"p_id": 1,
"billing": {
"first_name": "test"
}
}
In addition, I have a Pydantic model with two attributes:
class Order(BaseModel):
p_id: int
pre_name: str
How can I map the value from the key first_name
to my Pydantic attribute pre_name
?
Is there an easy way instead of using a root_validator
to parse the given structure to my flat pydantic Model?
Answers:
You can customize __init__
of your model class:
from pydantic import BaseModel
d = {
"p_id": 1,
"billing": {
"first_name": "test"
}
}
class Order(BaseModel):
p_id: int
pre_name: str
def __init__(self, **kwargs):
kwargs["pre_name"] = kwargs["billing"]["first_name"]
super().__init__(**kwargs)
print(Order.parse_obj(d)) # p_id=1 pre_name='test'
There’s a package PyMapMe for mapping models, it supports nested models as well as helping functions and context, for example:
from typing import Any
from pydantic import BaseModel, Field
from pymapme.models.mapping import MappingModel
class Person(BaseModel):
name: str
surname: str
class Profile(BaseModel):
nickname: str
person: Person
class User(MappingModel):
nickname: str = Field(source='nickname')
first_name: str = Field(source='person.name')
surname: str = Field(source='person.surname')
full_name: str = Field(source_func='_get_full_name')
@staticmethod
def _get_full_name(model: Profile, default: Any):
return model.person.name + ' ' + model.person.surname
profile = Profile(nickname='baobab', person=Person(name='John', surname='Smith'))
user = User.build_from_model(profile)
print(user.dict()) # {'nickname': 'baobab', 'first_name': 'John', 'surname': 'Smith', 'full_name': 'John Smith'}
Or for your example, it would look like:
d = {
"p_id": 1,
"billing": {
"first_name": "test"
}
}
class Billing(BaseModel):
first_name: str
class Data(BaseModel):
p_id: int
billing: Billing
class Order(MappingModel):
p_id: int
pre_name: str = Field(source='billing.first_name')
order = Order.build_from_model(Data(**d))
print(order.dict())
Note: I’m the author, pull requests and any suggestions are welcome!
You can nest pydantic models by making your lower levels another pydantic model. See as follows:
class MtnPayer(BaseModel):
partyIdType: str
partyId: str
class MtnPayment(BaseModel):
financialTransactionId: str
externalId: str
amount: str
currency: str
payer: MtnPayer
payerMessage: str
payeeNote: str
status: str
reason: str
See the payer item in the second model
I am trying to map a value from a nested dict/json to my Pydantic model. For me, this works well when my json/dict has a flat structure. However, I am struggling to map values from a nested structure to my Pydantic Model.
Lets assume I have a json/dict in the following format:
d = {
"p_id": 1,
"billing": {
"first_name": "test"
}
}
In addition, I have a Pydantic model with two attributes:
class Order(BaseModel):
p_id: int
pre_name: str
How can I map the value from the key first_name
to my Pydantic attribute pre_name
?
Is there an easy way instead of using a root_validator
to parse the given structure to my flat pydantic Model?
You can customize __init__
of your model class:
from pydantic import BaseModel
d = {
"p_id": 1,
"billing": {
"first_name": "test"
}
}
class Order(BaseModel):
p_id: int
pre_name: str
def __init__(self, **kwargs):
kwargs["pre_name"] = kwargs["billing"]["first_name"]
super().__init__(**kwargs)
print(Order.parse_obj(d)) # p_id=1 pre_name='test'
There’s a package PyMapMe for mapping models, it supports nested models as well as helping functions and context, for example:
from typing import Any
from pydantic import BaseModel, Field
from pymapme.models.mapping import MappingModel
class Person(BaseModel):
name: str
surname: str
class Profile(BaseModel):
nickname: str
person: Person
class User(MappingModel):
nickname: str = Field(source='nickname')
first_name: str = Field(source='person.name')
surname: str = Field(source='person.surname')
full_name: str = Field(source_func='_get_full_name')
@staticmethod
def _get_full_name(model: Profile, default: Any):
return model.person.name + ' ' + model.person.surname
profile = Profile(nickname='baobab', person=Person(name='John', surname='Smith'))
user = User.build_from_model(profile)
print(user.dict()) # {'nickname': 'baobab', 'first_name': 'John', 'surname': 'Smith', 'full_name': 'John Smith'}
Or for your example, it would look like:
d = {
"p_id": 1,
"billing": {
"first_name": "test"
}
}
class Billing(BaseModel):
first_name: str
class Data(BaseModel):
p_id: int
billing: Billing
class Order(MappingModel):
p_id: int
pre_name: str = Field(source='billing.first_name')
order = Order.build_from_model(Data(**d))
print(order.dict())
Note: I’m the author, pull requests and any suggestions are welcome!
You can nest pydantic models by making your lower levels another pydantic model. See as follows:
class MtnPayer(BaseModel):
partyIdType: str
partyId: str
class MtnPayment(BaseModel):
financialTransactionId: str
externalId: str
amount: str
currency: str
payer: MtnPayer
payerMessage: str
payeeNote: str
status: str
reason: str
See the payer item in the second model