Сериализация моделей pydantic с переводом имен полей в camelCase
Есть сервис на Java и клиент на Python. Сервис принимает/отдает json с именами полей в camelCase стиле, в соответствующих pydantic моделях на Python хотелось бы использовать snake_case (идиоматично для Python).
Пробую делать модель с псевдонимом (alias):
from pydantic import BaseModel, Field
class Test(BaseModel):
test_test: str = Field(..., alias = "testTest")
При таком коде при инициализации модели требуется указывать имена полей по алиасу (в camelCase), а при сериализации выводитcя в snake_case:
test = Test(testTest="test") # При указании test_test выдает ошибку валидации Field required [type=missing, input_value={'test_test': 'test'}, input_type=dict]
print(test.model_dump())
Вывод:
{'test_test': 'test'}
Мне нужно наоборот, чтобы инициализировать можно было по обычным именам (в snake_case), а сериализовалось в camelCase. В идеале, чтобы не нужно было прописывать алиас для каждого поля.
Ответы (1 шт):
Чтобы использовать обычные имена полей при инициализации, нужно добавить в модель настройку populate_by_name=True (документация):
class Test(BaseModel):
test_test: str = Field(..., alias="testTest")
model_config = ConfigDict(populate_by_name=True)
Чтобы сериализовалось с алиасами полей, нужно указать параметр by_alias=True (см., параметр by_alias в документации метода model_dump, аналогичный параметр есть у метода model_dump_json и у соответствующих методов класса TypeAdapter - актуально, если нужно через тайп адаптер сериализовать, например, список объектов):
test = Test(test_test="test")
print(test.model_dump(by_alias=True))
Вывод:
{'testTest': 'test'}
Чтобы автоматически генерировать алиасы, можно функцию конвертации строки из snake_case в camelCase указать в поле alias_generator в конфигурации модели (документация alias_generator, в модуле pydantic.alias_generators есть несколько готовых функций конвертации, в том числе для конвертации в camelCase):
from pydantic.alias_generators import to_camel
# Аналогичный конвертер реализованный "на коленке":
# def to_camel(string: str) -> str:
# words = string.split("_")
# return words[0] + ''.join(word.capitalize() for word in words[1:])
class Test(BaseModel):
test_test: str
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
)
Если конфиг модели становится громоздким и постоянно повторяющимся во всех моделях, можно его вынести в отдельный класс, и этот класс указать базовым для всех моделей вместо BaseModel. Полный пример:
from pydantic import BaseModel, ConfigDict, Field
from pydantic.alias_generators import to_camel
class MyBaseModel(BaseModel):
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
)
class Test(MyBaseModel):
test_test: str
test = Test(test_test="test")
encoded = test.model_dump(by_alias=True)
print(encoded)
decoded = Test.model_validate(encoded)
print(repr(decoded))
Вывод:
{'testTest': 'test'}
Test(test_test='test')