How to Efficiently Map Dictionaries to Python Classes: From Namedtuple to Pydantic
Mapping external data such as MongoDB query results or API responses to Python class attributes can be done manually, but this article explores several automated approaches—including namedtuple, setattr, a custom ModelBase/Type mapper, and Pydantic—detailing implementation, error handling, and performance benchmarks to guide developers in choosing the optimal solution.
When we need to map dictionaries from external sources (e.g., MongoDB query results or API responses) to class attributes, manual mapping is time‑consuming and error‑prone. This article presents several automated approaches and compares their performance.
Existing Methods
Example dictionary:
<code>_dictionary = {
'name': 'Bob',
'age': 23,
'title': 'SSE',
'department': 'SWE'
}</code>Using Namedtuples
<code>from collections import namedtuple
_employee = {
'name': 'Bob',
'age': 23,
'title': 'SSE',
'department': 'SWE'
}
EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department')
e_record = EmployeeRecord._make(_employee.values())
print(e_record.name, e_record.age)
</code>Namedtuple requires the dictionary to contain all defined fields; missing or extra keys raise TypeError .
Using setattr
<code>class EmployeeRecord:
def __init__(self, d=None):
if d is not None:
for key, value in d.items():
setattr(self, key, value)
_employee = {
'name': 'Danushka',
'age': 23,
'title': 'SSE',
'department': 'SWE'
}
o = EmployeeRecord(_employee)
print(o.name)
</code>This method creates attributes dynamically but lacks defaults and type safety; accessing a missing attribute raises AttributeError .
Custom Class Mapper
To overcome the limitations of namedtuple and setattr , a custom mapper using two core classes— Type and ModelBase —is introduced.
Type Class
<code>class Type:
alias: str
default_factory: callable
def __init__(self, alias: str = None, default_factory: callable = None) -> None:
self.alias = alias
self.default_factory = default_factory
</code>Type holds metadata for class attributes, allowing an alias for a different dictionary key and a callable default_factory for value transformation.
ModelBase Class
<code>class ModelBase:
def __init__(self, payload):
for key, _class in self.__annotations__.items():
_attr_key_in_payload = key
_attr_mapper = None
if hasattr(self, key) and type(self.__getattribute__(key)) is Type:
attr: Type = self.__getattribute__(key)
if attr.alias:
_attr_key_in_payload = attr.alias
if attr.default_factory and callable(attr.default_factory):
_attr_mapper = attr.default_factory
try:
if _attr_key_in_payload in payload:
if getattr(_class, '_name', None) == 'List' and len(getattr(_class, '__args__', [])):
_list = [_class.__args__[0](e) for e in payload[_attr_key_in_payload]]
self.__setattr__(key, _list)
else:
self.__setattr__(key, _class(payload[_attr_key_in_payload]))
if _attr_mapper:
self.__setattr__(key, _attr_mapper(self.__getattribute__(key)))
else:
self.__setattr__(key, None)
except Exception as e:
print(f"Error setting attribute {key}: {e}")
self.__setattr__(key, None)
</code>ModelBase iterates over annotated attributes, applies aliases, default factories, handles list types, and safely assigns values, falling back to None on errors.
Example Employee Class
<code>class Employee(ModelBase):
id: str = Type(alias='_id')
name: str
age: int
title: str
department: str
image_url: str = Type(alias='image', default_factory=PathMapper('image/employee').map_url)
</code>Creating an Employee instance with a dictionary automatically maps keys (including aliases) and applies the URL‑building factory for image_url .
Using Pydantic
Pydantic leverages Python type hints for data validation and management.
<code>pip install pydantic</code> <code>from typing import List
from pydantic import BaseModel, Field, field_validator
from .path_mapper import PathMapper
class Employee(BaseModel):
id: str = Field(alias='_id')
name: str
age: int
title: str
department: List[str]
image_url: str = Field(alias='image')
@field_validator('image_url', mode='before')
@classmethod
def to_image_url(cls, raw: str) -> str:
return PathMapper('image/employee').to_url(raw)
</code>Instantiating the model with a dictionary validates types, applies alias mapping, and runs the custom validator for image_url .
Performance Comparison
Benchmarks were run on up to 100,000 dictionary objects for four methods: custom class mapper, Pydantic, namedtuple, and setattr . The results show that setattr is the fastest, namedtuple is stable, Pydantic offers the most validation, and the custom mapper balances performance with flexibility.
Overall, while Pydantic excels at data validation, the custom class approach provides a versatile solution that balances speed and functionality, making it suitable for applications requiring reliable performance and complex data handling.
Code Mala Tang
Read source code together, write articles together, and enjoy spicy hot pot together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.