📋 Generate from JSON Schema¶
Generate Pydantic models from JSON Schema definitions. See Supported Data Types for supported JSON Schema features.
🚀 Quick Start¶
datamodel-codegen \
--input person.json \
--input-file-type jsonschema \
--output-model-type pydantic_v2.BaseModel \
--use-annotated \
--output model.py
📝 Example¶
person.json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Person",
"type": "object",
"properties": {
"firstName": {
"type": "string",
"description": "The person's first name."
},
"lastName": {
"type": "string",
"description": "The person's last name."
},
"age": {
"description": "Age in years which must be equal to or greater than zero.",
"type": "integer",
"minimum": 0
},
"friends": {
"type": "array"
},
"comment": {
"type": "null"
}
}
}
✨ Generated model.py
# generated by datamodel-codegen:
# filename: person.json
# timestamp: 2020-04-27T16:12:27+00:00
from __future__ import annotations
from typing import Annotated, Any
from pydantic import BaseModel, Field
class Person(BaseModel):
firstName: Annotated[str | None, Field(description="The person's first name.")] = None
lastName: Annotated[str | None, Field(description="The person's last name.")] = None
age: Annotated[
int | None,
Field(
description="Age in years which must be equal to or greater than zero.",
ge=0,
),
] = None
friends: list[Any] | None = None
comment: Annotated[None, Field(None)] = None
Tuple validation¶
JSON Schema's prefixItems syntax lets you describe heterogeneous arrays.
When:
prefixItemsis present- no
itemsare specified minItems/maxItemsmatch the number of prefix entries
datamodel-code-generator emits precise tuple annotations.
Example¶
{
"$defs": {
"Span": {
"type": "object",
"properties": {
"value": { "type": "integer" }
},
"required": ["value"]
}
},
"title": "defaults",
"type": "object",
"properties": {
"a": {
"type": "array",
"prefixItems": [
{ "$ref": "#/$defs/Span" },
{ "type": "string" }
],
"minItems": 2,
"maxItems": 2
}
},
"required": ["a"]
}
from pydantic import BaseModel
class Span(BaseModel):
value: int
class Defaults(BaseModel):
a: tuple[Span, str]
Custom Base Class with customBasePath¶
You can specify custom base classes directly in your JSON Schema using the customBasePath extension. This allows you to define base classes at the schema level without using CLI options.
Single Base Class¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "User",
"type": "object",
"customBasePath": "myapp.models.UserBase",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name", "email"]
}
Generated Output:
from __future__ import annotations
from myapp.models import UserBase
class User(UserBase):
name: str
email: str
Multiple Base Classes (Mixins)¶
You can also specify multiple base classes as a list to implement mixin patterns:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "User",
"type": "object",
"customBasePath": ["mixins.AuditMixin", "mixins.TimestampMixin"],
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name", "email"]
}
Generated Output:
from __future__ import annotations
from mixins import AuditMixin, TimestampMixin
class User(AuditMixin, TimestampMixin):
name: str
email: str
Mixin Usage
When using multiple base classes, the specified classes are used directly without adding BaseModel.
Ensure your mixins inherit from pydantic.BaseModel if you need Pydantic model behavior.
Priority Resolution¶
When multiple base class configurations are present, they are resolved in this order:
--base-class-map(CLI option) - Highest prioritycustomBasePath(JSON Schema extension)--base-class(CLI option) - Lowest priority (default for all models)
This allows you to set a default base class with --base-class, override specific models in the schema with customBasePath, and further override at the CLI level with --base-class-map.
Supported JSON Schema Features¶
| Feature | Generation behavior |
|---|---|
| Object properties and required fields | Generated as model fields |
$ref, $defs, and definitions |
Resolved before model generation |
oneOf, anyOf, allOf |
Converted into unions and composed models where possible |
| Scalar constraints | Generated as Field(...) metadata with --field-constraints / --use-annotated |
format values |
Mapped to Python/Pydantic types where supported |
| Custom extensions | customBasePath and related options can steer generated base classes |
Regular Expression Patterns¶
Pydantic v2 validates pattern with pydantic-core's Rust regex engine by default, and pydantic-core compiles the
pattern when the model class is created. Some syntax that Python's re accepts is rejected by every
pydantic-core release, so a model that uses it fails at import. For Pydantic v2 output from JSON Schema and
OpenAPI, the generator detects this syntax statically and sets regex_engine="python-re" on the model that owns
the pattern. The decision depends only on the pattern text and --target-python-version. It does not depend on
the installed pydantic-core or on the Python running the generator, and it is the same for every
--target-pydantic-version. regex_engine needs Pydantic 2.5: on Pydantic 2.0 to 2.4 such a model fails at import
whatever the target. A pattern in a type alias has no model that owns it, so the alias carries a compiled pattern, or,
with --target-pydantic-version 2, every generated model that reaches the alias selects the engine (see
Targeting a Pydantic version).
| Pattern syntax | Example | Generated model |
|---|---|---|
| Lookahead and lookbehind | (?=x), (?<!x) |
python-re |
| Numbered backreferences and octal escapes | ([a-z])\1, \0, [\101] |
python-re |
| Named backreferences | (?P<c>x)(?P=c) |
python-re |
| Conditionals | (<)?a(?(1)>) |
python-re |
| End of string | \Z |
python-re |
| Comments | (?#note) |
python-re |
| Named characters | \N{LATIN SMALL LETTER A} |
python-re |
| Backspace in a character class | [\b] |
python-re |
| ASCII flag | (?a), (?a:x), (?ia) |
python-re |
| Braces that cannot start a counted repetition | a{,3}, {[a-z]+} |
python-re |
| Atomic groups | (?>a+) |
python-re with --target-python-version 3.11 or later; unchanged for 3.10 |
| Escaped characters and character class members | \\1, \(\?>, [(?>{] |
Rust engine (default); the lookaround check still matches (?= and (?<! even when escaped or inside a class |
| Rust-only syntax | a{2, 3}, \p{L}, \x{41}, \b{start}, [[a-z]{], (?P<ids[]>x) |
Rust engine |
| Syntax both engines reject | (?L), ([a-z])\1\p{L} |
python-re; still fails at import, with Python's error |
| Possessive quantifiers | a++, a*+ |
Rust engine; pydantic-core accepts them as nested repetitions |
| Syntax only Pydantic 2.0.0-2.0.2 rejects | \/, \:, (?<name>x), [a&&b] |
Rust engine |
| Patterns over Rust's compiled size limit | [\w.-]{1,256} |
Rust engine; not detectable from the syntax |
| Verbose patterns | (?x) a b |
Rust engine; the scan does not model verbose whitespace and comments |
Apart from the lookaround check, the scan follows the Rust grammar of every pydantic-core release from 2.3.0 on,
so a pattern pydantic-core accepts is never switched. A switched pattern that also contains syntax the target
Python's re does not parse keeps the default engine: possessive quantifiers need Python 3.11 and \z needs
Python 3.14. The generator does not compile the pattern with Python's re. Lookaround keeps its existing detection
for every field. The other rules apply only to patterns emitted for string fields, including unions of strings such
as type: ["string", "null"]: a pattern on format: uri (AnyUrl) is not emitted. A pattern on bytes
(format: binary) is written only with --field-constraints or --use-annotated. On plain and optional bytes
fields Pydantic 2.0 to 2.10 rejects any such pattern at import, Pydantic 2.11 to 2.13 ignores it, and Pydantic 2.14
compiles it like a string pattern, so the rules also apply to it with --target-pydantic-version 2.14. With an
unset or older target the model keeps the default engine for its other patterns, and a bytes pattern that the Rust
engine rejects fails at import on Pydantic 2.14. Pydantic 2.12 also compiles the pattern of a bytes | MISSING
field (--use-missing-sentinel), so such a pattern fails at import there too. A union that has a string member
beside bytes, such as str | bytes, follows the string rules for every target.
Because regex_engine is a model setting, every pattern in that model then uses Python's re semantics, for
example $ also matches before a trailing newline. Python's re is a backtracking engine without the Rust engine's
linear-time guarantee: review such patterns, and bound input length (for example with maxLength) when they
validate untrusted input.
Limitations¶
JSON Schema input generates Python model definitions. It does not perform runtime validation by itself, and some schema keywords are represented as type hints or field metadata rather than full JSON Schema validator behavior. See Schema Version Support for version-specific coverage.
📖 See Also¶
- 🚀 Getting Started - Installation and first model
- 🖥️ CLI Reference - Complete CLI options reference
- 🔧 CLI Reference: Typing Customization - Type annotation options
- 🏷️ CLI Reference: Field Customization - Field naming and constraint options
- 📊 Supported Data Types - JSON Schema data type support
- 🏗️ CLI Reference: Model Customization - Base class and model customization options