Skip to content

Commit a834e62

Browse files
Add documentation for reducing duplicate field types (#2896)
* Add documentation for reducing duplicate field types with --use-type-alias * docs: update CLI reference documentation and prompt data 🤖 Generated by GitHub Actions * Use heading instead of bold for Result section --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent f4b240a commit a834e62

5 files changed

Lines changed: 187 additions & 1 deletion

File tree

docs/cli-reference/typing-customization.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4198,6 +4198,8 @@ syntax. This feature is experimental.
41984198

41994199
**Related:** [`--target-python-version`](model-customization.md#target-python-version)
42004200

4201+
**See also:** [Model Reuse and Deduplication](../model-reuse.md)
4202+
42014203
!!! tip "Usage"
42024204

42034205
```bash

docs/model-reuse.md

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<!-- related-cli-options: --reuse-model, --reuse-scope, --shared-module-name, --collapse-root-models, --disable-warnings -->
1+
<!-- related-cli-options: --reuse-model, --reuse-scope, --shared-module-name, --collapse-root-models, --disable-warnings, --use-type-alias -->
22

33
# Model Reuse and Deduplication
44

@@ -12,6 +12,7 @@ When generating models from schemas, you may encounter duplicate model definitio
1212
| `--reuse-scope` | Control scope of deduplication (`root` or `tree`) |
1313
| `--shared-module-name` | Name for shared module in multi-file output |
1414
| `--collapse-root-models` | Inline root models instead of creating wrappers |
15+
| `--use-type-alias` | Create TypeAlias for reusable field types (see [Reducing Duplicate Field Types](#reducing-duplicate-field-types)) |
1516

1617
---
1718

@@ -243,10 +244,98 @@ models/
243244

244245
---
245246

247+
## Reducing Duplicate Field Types
248+
249+
When multiple classes share the same field type with identical constraints or metadata, you can reduce duplication by defining the type once in `$defs` and referencing it with `$ref`. Combined with `--use-type-alias`, this creates a single TypeAlias that's reused across all classes.
250+
251+
### Problem: Duplicate Annotated Fields
252+
253+
Without using `$ref`, each class gets its own inline field definition:
254+
255+
```python
256+
class ClassA(BaseModel):
257+
place_name: Annotated[str, Field(alias='placeName')] # Duplicate!
258+
259+
class ClassB(BaseModel):
260+
place_name: Annotated[str, Field(alias='placeName')] # Duplicate!
261+
```
262+
263+
### Solution: Use `$defs` with `--use-type-alias`
264+
265+
**Step 1: Define the shared type in `$defs`**
266+
267+
```json
268+
{
269+
"$defs": {
270+
"PlaceName": {
271+
"type": "string",
272+
"title": "PlaceName",
273+
"description": "A place name"
274+
},
275+
"ClassA": {
276+
"type": "object",
277+
"properties": {
278+
"place_name": { "$ref": "#/$defs/PlaceName" }
279+
}
280+
},
281+
"ClassB": {
282+
"type": "object",
283+
"properties": {
284+
"place_name": { "$ref": "#/$defs/PlaceName" }
285+
}
286+
}
287+
}
288+
}
289+
```
290+
291+
**Step 2: Generate with `--use-type-alias`**
292+
293+
```bash
294+
datamodel-codegen \
295+
--input schema.json \
296+
--output model.py \
297+
--use-type-alias
298+
```
299+
300+
### Result: Single TypeAlias reused across classes
301+
302+
```python
303+
PlaceName = TypeAliasType(
304+
"PlaceName",
305+
Annotated[str, Field(..., description='A place name', title='PlaceName')],
306+
)
307+
308+
309+
class ClassA(BaseModel):
310+
place_name: PlaceName # Reuses the TypeAlias
311+
312+
313+
class ClassB(BaseModel):
314+
place_name: PlaceName # Reuses the TypeAlias
315+
```
316+
317+
### Benefits
318+
319+
- **Single source of truth** - Field type is defined once
320+
- **Easier maintenance** - Change the type in one place
321+
- **Cleaner generated code** - No redundant annotations
322+
- **Type safety** - All fields share the exact same type
323+
324+
### When to Use This Pattern
325+
326+
This pattern is ideal when:
327+
328+
- Multiple classes share fields with the same constraints (e.g., `minLength`, `pattern`)
329+
- Fields have identical metadata (e.g., `description`, `examples`)
330+
- You want to ensure type consistency across your schema
331+
332+
---
333+
246334
## See Also
247335

248336
- [CLI Reference: `--reuse-model`](cli-reference/model-customization.md#reuse-model)
249337
- [CLI Reference: `--reuse-scope`](cli-reference/model-customization.md#reuse-scope)
250338
- [CLI Reference: `--collapse-root-models`](cli-reference/model-customization.md#collapse-root-models)
339+
- [CLI Reference: `--use-type-alias`](cli-reference/typing-customization.md#use-type-alias)
251340
- [Root Models and Type Aliases](root-model-and-type-alias.md)
252341
- [FAQ: Performance](faq.md#-performance)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# generated by datamodel-codegen:
2+
# filename: reduce_duplicate_field_types.json
3+
# timestamp: 2019-07-26T00:00:00+00:00
4+
5+
from __future__ import annotations
6+
7+
from typing import Annotated
8+
9+
from pydantic import BaseModel, Field
10+
from typing_extensions import TypeAliasType
11+
12+
PlaceName = TypeAliasType(
13+
"PlaceName",
14+
Annotated[
15+
str,
16+
Field(
17+
...,
18+
description='A place name with alias',
19+
examples=['Tokyo', 'New York'],
20+
title='PlaceName',
21+
),
22+
],
23+
)
24+
25+
26+
class ClassA(BaseModel):
27+
place_name: PlaceName
28+
other_field: int | None = None
29+
30+
31+
class ClassB(BaseModel):
32+
place_name: PlaceName
33+
description: str | None = None
34+
35+
36+
Model = TypeAliasType("Model", Annotated[ClassA | ClassB, Field(..., title='Model')])
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"title": "Model",
4+
"$defs": {
5+
"PlaceName": {
6+
"type": "string",
7+
"title": "PlaceName",
8+
"description": "A place name with alias",
9+
"examples": ["Tokyo", "New York"]
10+
},
11+
"ClassA": {
12+
"type": "object",
13+
"title": "ClassA",
14+
"properties": {
15+
"place_name": {
16+
"$ref": "#/$defs/PlaceName"
17+
},
18+
"other_field": {
19+
"type": "integer"
20+
}
21+
},
22+
"required": ["place_name"]
23+
},
24+
"ClassB": {
25+
"type": "object",
26+
"title": "ClassB",
27+
"properties": {
28+
"place_name": {
29+
"$ref": "#/$defs/PlaceName"
30+
},
31+
"description": {
32+
"type": "string"
33+
}
34+
},
35+
"required": ["place_name"]
36+
}
37+
},
38+
"anyOf": [
39+
{"$ref": "#/$defs/ClassA"},
40+
{"$ref": "#/$defs/ClassB"}
41+
]
42+
}

tests/main/jsonschema/test_main_jsonschema.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7540,3 +7540,20 @@ def test_ref_nullable_with_extra_creates_model(output_file: Path) -> None:
75407540
expected_file="ref_nullable_with_extra.py",
75417541
extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--strict-nullable"],
75427542
)
7543+
7544+
7545+
def test_reduce_duplicate_field_types(output_file: Path) -> None:
7546+
"""Test reducing duplicate field types using $ref and --use-type-alias.
7547+
7548+
When multiple classes share the same field type defined in $defs,
7549+
using --use-type-alias creates a single TypeAlias that's reused across classes.
7550+
This is the recommended pattern to avoid duplicate Annotated field definitions.
7551+
"""
7552+
run_main_and_assert(
7553+
input_path=JSON_SCHEMA_DATA_PATH / "reduce_duplicate_field_types.json",
7554+
output_path=output_file,
7555+
input_file_type="jsonschema",
7556+
assert_func=assert_file_content,
7557+
expected_file="reduce_duplicate_field_types.py",
7558+
extra_args=["--output-model-type", "pydantic_v2.BaseModel", "--use-type-alias"],
7559+
)

0 commit comments

Comments
 (0)