Skip to content

Commit df5b218

Browse files
authored
Add serialization aliases (#3146)
* Add serialization aliases * Address serialization aliases feedback * Address remaining serialization aliases feedback * Reduce inherited schema traversal duplication
1 parent f6f5fc2 commit df5b218

42 files changed

Lines changed: 1156 additions & 64 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/cli-reference/field-customization.md

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
| [`--no-alias`](#no-alias) | Disable Field alias generation for non-Python-safe property ... |
1818
| [`--original-field-name-delimiter`](#original-field-name-delimiter) | Specify delimiter for original field names when using snake-... |
1919
| [`--remove-special-field-name-prefix`](#remove-special-field-name-prefix) | Remove the special prefix from field names. |
20+
| [`--serialization-aliases`](#serialization-aliases) | Apply custom Pydantic v2 serialization aliases from JSON fil... |
2021
| [`--set-default-enum-member`](#set-default-enum-member) | Set the first enum member as the default value for enum fiel... |
2122
| [`--snake-case-field`](#snake-case-field) | Convert field names to snake_case format. |
2223
| [`--special-field-name-prefix`](#special-field-name-prefix) | Prefix to add to special field names (like reserved keywords... |
@@ -1951,6 +1952,294 @@ The `--remove-special-field-name-prefix` flag configures the code generation beh
19511952

19521953
---
19531954

1955+
## `--serialization-aliases` {#serialization-aliases}
1956+
1957+
Apply custom Pydantic v2 serialization aliases from JSON file.
1958+
1959+
The `--serialization-aliases` option lets Pydantic v2 models keep input aliases
1960+
or validation aliases while using separate output-only names for serialization.
1961+
1962+
!!! tip "Usage"
1963+
1964+
```bash
1965+
datamodel-codegen --input schema.json --aliases aliases/serialization_aliases.json --serialization-aliases aliases/serialization_aliases_output.json --output-model-type pydantic_v2.BaseModel # (1)!
1966+
```
1967+
1968+
1. :material-arrow-left: `--serialization-aliases` - the option documented here
1969+
1970+
??? example "Examples"
1971+
1972+
**Input Schema:**
1973+
1974+
```json
1975+
{
1976+
"$schema": "http://json-schema.org/draft-07/schema#",
1977+
"title": "Messaging",
1978+
"type": "object",
1979+
"definitions": {
1980+
"SendMessageV2": {
1981+
"type": "object",
1982+
"properties": {
1983+
"textMessage": {
1984+
"type": "string"
1985+
}
1986+
},
1987+
"required": ["textMessage"]
1988+
},
1989+
"OtherMessage": {
1990+
"type": "object",
1991+
"properties": {
1992+
"other-name": {
1993+
"type": "string"
1994+
}
1995+
},
1996+
"required": ["other-name"]
1997+
},
1998+
"SameNameMessage": {
1999+
"type": "object",
2000+
"properties": {
2001+
"same-name": {
2002+
"type": "string"
2003+
}
2004+
},
2005+
"required": ["same-name"]
2006+
},
2007+
"RequiredOnlyMessage": {
2008+
"type": "object",
2009+
"allOf": [
2010+
{
2011+
"type": "object",
2012+
"properties": {
2013+
"known-name": {
2014+
"type": "string"
2015+
}
2016+
},
2017+
"required": ["known-name", "missing-required", "plain-missing"]
2018+
}
2019+
],
2020+
"required": ["known-name", "missing-required", "plain-missing"]
2021+
},
2022+
"RequiredOnlyGrandBase": {
2023+
"type": "object",
2024+
"properties": {
2025+
"grand-name": {
2026+
"type": "integer"
2027+
},
2028+
"bare-name": {}
2029+
},
2030+
"required": ["grand-name", "bare-name"]
2031+
},
2032+
"RequiredOnlyBase": {
2033+
"type": "object",
2034+
"properties": {
2035+
"base-name": {
2036+
"type": "string"
2037+
}
2038+
},
2039+
"allOf": [
2040+
{
2041+
"$ref": "#/definitions/RequiredOnlyGrandBase"
2042+
}
2043+
],
2044+
"required": ["base-name"]
2045+
},
2046+
"RequiredOnlyRefMessage": {
2047+
"type": "object",
2048+
"properties": {
2049+
"own-name": {
2050+
"type": "string"
2051+
}
2052+
},
2053+
"allOf": [
2054+
{
2055+
"$ref": "#/definitions/RequiredOnlyBase"
2056+
}
2057+
],
2058+
"required": [
2059+
"own-name",
2060+
"grand-name",
2061+
"bare-name",
2062+
"ignored-missing",
2063+
"ref-missing-required",
2064+
"ref-plain-missing"
2065+
]
2066+
},
2067+
"RequiredOnlyEmptyAllOfBase": {
2068+
"type": "object",
2069+
"allOf": [
2070+
{
2071+
"type": "object"
2072+
}
2073+
]
2074+
},
2075+
"RequiredOnlyEmptyAllOfMessage": {
2076+
"type": "object",
2077+
"allOf": [
2078+
{
2079+
"$ref": "#/definitions/RequiredOnlyEmptyAllOfBase"
2080+
}
2081+
],
2082+
"required": ["empty-missing"]
2083+
},
2084+
"RequiredOnlyRecursiveEmptyGrandBase": {
2085+
"type": "object",
2086+
"properties": {
2087+
"unrelated-name": {
2088+
"type": "string"
2089+
}
2090+
}
2091+
},
2092+
"RequiredOnlyRecursiveEmptyBase": {
2093+
"type": "object",
2094+
"allOf": [
2095+
{
2096+
"$ref": "#/definitions/RequiredOnlyRecursiveEmptyGrandBase"
2097+
}
2098+
]
2099+
},
2100+
"RequiredOnlyRecursiveEmptyMessage": {
2101+
"type": "object",
2102+
"allOf": [
2103+
{
2104+
"$ref": "#/definitions/RequiredOnlyRecursiveEmptyBase"
2105+
}
2106+
],
2107+
"required": ["recursive-missing"]
2108+
}
2109+
},
2110+
"properties": {
2111+
"send": {
2112+
"$ref": "#/definitions/SendMessageV2"
2113+
},
2114+
"other": {
2115+
"$ref": "#/definitions/OtherMessage"
2116+
},
2117+
"same": {
2118+
"$ref": "#/definitions/SameNameMessage"
2119+
},
2120+
"requiredOnly": {
2121+
"$ref": "#/definitions/RequiredOnlyMessage"
2122+
},
2123+
"requiredOnlyRef": {
2124+
"$ref": "#/definitions/RequiredOnlyRefMessage"
2125+
},
2126+
"requiredOnlyEmptyAllOf": {
2127+
"$ref": "#/definitions/RequiredOnlyEmptyAllOfMessage"
2128+
},
2129+
"requiredOnlyRecursiveEmpty": {
2130+
"$ref": "#/definitions/RequiredOnlyRecursiveEmptyMessage"
2131+
}
2132+
}
2133+
}
2134+
```
2135+
2136+
**Output:**
2137+
2138+
```python
2139+
# generated by datamodel-codegen:
2140+
# filename: serialization_aliases.json
2141+
# timestamp: 2019-07-26T00:00:00+00:00
2142+
2143+
from __future__ import annotations
2144+
2145+
from typing import Any
2146+
2147+
from pydantic import AliasChoices, BaseModel, Field
2148+
2149+
2150+
class SendMessageV2(BaseModel):
2151+
message: str = Field(
2152+
...,
2153+
serialization_alias='messageText',
2154+
validation_alias=AliasChoices('textMessage', 'message', 'text'),
2155+
)
2156+
2157+
2158+
class OtherMessage(BaseModel):
2159+
other_name: str = Field(..., alias='other-name', serialization_alias='otherName')
2160+
2161+
2162+
class SameNameMessage(BaseModel):
2163+
same_name: str = Field(..., alias='same-name', serialization_alias='same_name')
2164+
2165+
2166+
class RequiredOnlyMessage(BaseModel):
2167+
known_name: str = Field(..., alias='known-name')
2168+
missingRequiredInput: Any = Field(
2169+
...,
2170+
serialization_alias='missingRequired',
2171+
validation_alias=AliasChoices('missing-required', 'missingRequiredInput'),
2172+
)
2173+
plainMissingInput: Any = Field(
2174+
..., alias='plain-missing', serialization_alias='plainMissing'
2175+
)
2176+
2177+
2178+
class RequiredOnlyGrandBase(BaseModel):
2179+
grand_name: int = Field(..., alias='grand-name')
2180+
bare_name: Any = Field(..., alias='bare-name')
2181+
2182+
2183+
class RequiredOnlyBase(RequiredOnlyGrandBase):
2184+
base_name: str = Field(..., alias='base-name')
2185+
2186+
2187+
class RequiredOnlyRefMessage(RequiredOnlyBase):
2188+
own_name: str = Field(..., alias='own-name')
2189+
grand_name: int = Field(
2190+
...,
2191+
serialization_alias='grandName',
2192+
validation_alias=AliasChoices('grand-name', 'grandInput'),
2193+
)
2194+
bare_name: Any = Field(..., alias='bare-name', serialization_alias='bareName')
2195+
refMissingInput: Any = Field(
2196+
...,
2197+
serialization_alias='refMissingRequired',
2198+
validation_alias=AliasChoices('ref-missing-required', 'refMissingInput'),
2199+
)
2200+
refPlainMissingInput: Any = Field(
2201+
..., alias='ref-plain-missing', serialization_alias='refPlainMissing'
2202+
)
2203+
2204+
2205+
class RequiredOnlyEmptyAllOfBase(BaseModel):
2206+
pass
2207+
2208+
2209+
class RequiredOnlyEmptyAllOfMessage(RequiredOnlyEmptyAllOfBase):
2210+
emptyMissingInput: Any = Field(
2211+
...,
2212+
serialization_alias='emptyMissing',
2213+
validation_alias=AliasChoices('empty-missing', 'emptyMissingInput'),
2214+
)
2215+
2216+
2217+
class RequiredOnlyRecursiveEmptyGrandBase(BaseModel):
2218+
unrelated_name: str | None = Field(None, alias='unrelated-name')
2219+
2220+
2221+
class RequiredOnlyRecursiveEmptyBase(RequiredOnlyRecursiveEmptyGrandBase):
2222+
pass
2223+
2224+
2225+
class RequiredOnlyRecursiveEmptyMessage(RequiredOnlyRecursiveEmptyBase):
2226+
recursiveMissingInput: Any = Field(
2227+
..., alias='recursive-missing', serialization_alias='recursiveMissing'
2228+
)
2229+
2230+
2231+
class Messaging(BaseModel):
2232+
send: SendMessageV2 | None = None
2233+
other: OtherMessage | None = None
2234+
same: SameNameMessage | None = None
2235+
requiredOnly: RequiredOnlyMessage | None = None
2236+
requiredOnlyRef: RequiredOnlyRefMessage | None = None
2237+
requiredOnlyEmptyAllOf: RequiredOnlyEmptyAllOfMessage | None = None
2238+
requiredOnlyRecursiveEmpty: RequiredOnlyRecursiveEmptyMessage | None = None
2239+
```
2240+
2241+
---
2242+
19542243
## `--set-default-enum-member` {#set-default-enum-member}
19552244

19562245
Set the first enum member as the default value for enum fields.

docs/cli-reference/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This documentation is auto-generated from test cases.
1010
|----------|---------|-------------|
1111
| 📁 [Base Options](base-options.md) | 10 | Input/output configuration |
1212
| 🔧 [Typing Customization](typing-customization.md) | 29 | Type annotation and import behavior |
13-
| 🏷️ [Field Customization](field-customization.md) | 25 | Field naming and docstring behavior |
13+
| 🏷️ [Field Customization](field-customization.md) | 26 | Field naming and docstring behavior |
1414
| 🏗️ [Model Customization](model-customization.md) | 39 | Model generation behavior |
1515
| 🎨 [Template Customization](template-customization.md) | 21 | Output formatting and custom rendering |
1616
| 📘 [OpenAPI-only Options](openapi-only-options.md) | 7 | OpenAPI-specific features |
@@ -168,6 +168,7 @@ This documentation is auto-generated from test cases.
168168

169169
- [`--schema-version`](base-options.md#schema-version)
170170
- [`--schema-version-mode`](base-options.md#schema-version-mode)
171+
- [`--serialization-aliases`](field-customization.md#serialization-aliases)
171172
- [`--set-default-enum-member`](field-customization.md#set-default-enum-member)
172173
- [`--shared-module-name`](general-options.md#shared-module-name)
173174
- [`--skip-root-model`](model-customization.md#skip-root-model)

docs/cli-reference/quick-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ datamodel-codegen [OPTIONS]
7878
| [`--no-alias`](field-customization.md#no-alias) | Disable Field alias generation for non-Python-safe property names. |
7979
| [`--original-field-name-delimiter`](field-customization.md#original-field-name-delimiter) | Specify delimiter for original field names when using snake-case conversion. |
8080
| [`--remove-special-field-name-prefix`](field-customization.md#remove-special-field-name-prefix) | Remove the special prefix from field names. |
81+
| [`--serialization-aliases`](field-customization.md#serialization-aliases) | Apply custom Pydantic v2 serialization aliases from JSON file. |
8182
| [`--set-default-enum-member`](field-customization.md#set-default-enum-member) | Set the first enum member as the default value for enum fields. |
8283
| [`--snake-case-field`](field-customization.md#snake-case-field) | Convert field names to snake_case format. |
8384
| [`--special-field-name-prefix`](field-customization.md#special-field-name-prefix) | Prefix to add to special field names (like reserved keywords). |
@@ -316,6 +317,7 @@ All options sorted alphabetically:
316317
- [`--reuse-scope`](model-customization.md#reuse-scope) - Scope for model reuse detection (root or tree).
317318
- [`--schema-version`](base-options.md#schema-version) - Schema version to use for parsing.
318319
- [`--schema-version-mode`](base-options.md#schema-version-mode) - Schema version validation mode.
320+
- [`--serialization-aliases`](field-customization.md#serialization-aliases) - Apply custom Pydantic v2 serialization aliases from JSON fil...
319321
- [`--set-default-enum-member`](field-customization.md#set-default-enum-member) - Set the first enum member as the default value for enum fiel...
320322
- [`--shared-module-name`](general-options.md#shared-module-name) - Customize the name of the shared module for deduplicated mod...
321323
- [`--skip-root-model`](model-customization.md#skip-root-model) - Skip generation of root model when schema contains nested de...

src/datamodel_code_generator/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]:
710710
"data_type_manager_type": data_model_types.data_type_manager,
711711
"dump_resolve_reference_action": data_model_types.dump_resolve_reference_action,
712712
"extra_template_data": extra_template_data,
713+
"serialization_aliases": config.serialization_aliases,
713714
"base_path": input_.parent if isinstance(input_, Path) and input_.is_file() else None,
714715
"remote_text_cache": remote_text_cache,
715716
"known_third_party": data_model_types.known_third_party,

0 commit comments

Comments
 (0)