13

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?

6 Answers 6

16

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'
Sign up to request clarification or add additional context in comments.

2 Comments

Is this the best way to do this? or is doing this bad practice....
how to do if billing is list of items instead of object? and is this best practice?
3

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!

Comments

0

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

Comments

0

You could inherit from this custom class instead of BaseModel (see below).

from collections.abc import MutableMapping
from typing import Any, Iterator

from pydantic import BaseModel


class BaseModelDict(BaseModel, MutableMapping):
    """Goodness of BaseModel and acts like a dictionary."""

    def __contains__(self, x: str) -> bool:
        return True if x in self.__dict__.keys() else False

    def __delitem__(self, x: str) -> None:
        del self.__dict__[x]

    def __getitem__(self, x: str) -> Any:
        return self.__dict__[x]

    def __iter__(self) -> Iterator:
        return iter(self.__dict__)

    def __json__(self) -> dict:
        return self.__dict__

    def __len__(self) -> int:
        return len(self.__dict__)

    def __setitem__(self, key: str, value: Any) -> None:
        self.__dict__[key] = value

Comments

0

you can also use the validator: @root_validator(pre=True). This way the function runs before Pydantic validates and populates fields. it doesn't override the init.

from pydantic import BaseModel, root_validator

d = {
    "p_id": 1,
    "billing": {
        "first_name": "test"
    }
}

class Order(BaseModel):
    p_id: int
    pre_name: str

    @root_validator(pre=True)
    def extract_nested_fields(clas, values):
        billing = values.get("billing", {})
        values["pre_name"] = billing.get("first_name")
        return values

print(Order.parse_obj(d)) 

Comments

-1

For my future self who lands here, a better solution is to use parse_obj of pydantic v2. You can use MyModel.parse_obj(my_dict) to generate a model from a dictionary. (documentation)

Example:

from pydantic import BaseModel, parse_obj_as
from typing import List

class TimeSlot(BaseModel):
    from_: str
    to: str

class DayActiveHours(BaseModel):
    days: List[str]
    time_slots: List[TimeSlot]

# Example nested dictionary
nested_data = {
    "days": ["Monday", "Tuesday"],
    "time_slots": [
        {"from_": "08:00", "to": "12:00"},
        {"from_": "13:00", "to": "17:00"}
    ]
}

# Parse the nested dictionary into Pydantic models
active_hours = DayActiveHours.parse_obj(nested_data)

# Now, you can access the parsed values using the Pydantic model attributes
print(active_hours.days)  # Output: ["Monday", "Tuesday"]
print(active_hours.time_slots)  # Output: [TimeSlot(from_='08:00', to='12:00'), TimeSlot(from_='13:00', to='17:00')]

Refer the solution here (pydantic v2) : How to parse list of models with Pydantic

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.