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_nameto 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?

Asked By: azo91

||

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'
Answered By: alex_noname

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!

Answered By: funnydman

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

Answered By: Jorangutang
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.