Сериализация моделей 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 шт):

Автор решения: insolor

Чтобы использовать обычные имена полей при инициализации, нужно добавить в модель настройку 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')
→ Ссылка