Skip to content

Output Model Types

datamodel-code-generator supports multiple output model types. This page compares them to help you choose the right one for your project.

Quick Comparison

Model Type Validation Serialization Performance Use Case
Pydantic v2 Runtime Built-in Fast New projects, APIs, data validation
Pydantic v2 dataclass Runtime Built-in Fast Pydantic validation with dataclass syntax
dataclasses None Manual Fastest Simple data containers, no validation needed
TypedDict Static only Dict-compatible N/A Type hints for dicts, JSON APIs
msgspec Runtime Built-in Fastest High-performance serialization

Use --output-model-type pydantic_v2.BaseModel

Pydantic v2 is recommended for new projects. It offers better performance and a modern API.

datamodel-codegen --input schema.json --output-model-type pydantic_v2.BaseModel --output model.py
from pydantic import BaseModel, Field, RootModel

class Pet(BaseModel):
    id: int = Field(..., ge=0)
    name: str = Field(..., max_length=256)
    tag: str | None = None

class Pets(RootModel[list[Pet]]):
    root: list[Pet]

When to use

  • New projects requiring data validation
  • APIs requiring data validation
  • Projects needing JSON Schema generation from models

Pydantic v2 dataclass

Use --output-model-type pydantic_v2.dataclass

Pydantic v2 dataclass combines the familiar dataclass syntax with Pydantic's validation capabilities.

datamodel-codegen --input schema.json --output-model-type pydantic_v2.dataclass --output model.py
from pydantic.dataclasses import dataclass
from typing import Optional

@dataclass
class Pet:
    id: int
    name: str
    tag: Optional[str] = None

When to use

  • Want to use dataclass syntax with Pydantic validation
  • Migrating from dataclasses but need validation
  • Prefer decorator-based class definition

dataclasses

Use --output-model-type dataclasses.dataclass

Python's built-in dataclasses for simple data containers without runtime validation.

datamodel-codegen --input schema.json --output-model-type dataclasses.dataclass --output model.py
from dataclasses import dataclass
from typing import Optional

@dataclass
class Pet:
    id: int
    name: str
    tag: Optional[str] = None

Options for dataclasses

Option Description
--frozen-dataclasses Generate immutable dataclasses (frozen=True)
--keyword-only Require keyword arguments (kw_only=True, Python 3.10+)
--dataclass-arguments Custom decorator arguments as JSON
# Frozen, keyword-only dataclasses
datamodel-codegen --input schema.json --output-model-type dataclasses.dataclass \
  --frozen-dataclasses --keyword-only --target-python-version 3.10

When to use

  • Simple data structures without validation needs
  • Performance-critical code where validation overhead matters
  • Interoperability with code expecting dataclasses

TypedDict

Use --output-model-type typing.TypedDict

TypedDict provides static type checking for dictionary structures.

datamodel-codegen --input schema.json --output-model-type typing.TypedDict --output model.py
from typing import TypedDict, NotRequired

class Pet(TypedDict):
    id: int
    name: str
    tag: NotRequired[str]

When to use

  • Working with JSON APIs where data remains as dicts
  • Static type checking without runtime overhead
  • Gradual typing of existing dict-based code

Boundary payloads and partial updates

TypedDict is a good fit for data at an HTTP boundary. A PATCH payload can distinguish three cases:

  • the key is missing, so the existing value should stay unchanged
  • the key is present with null, so the value should be cleared
  • the key is present with a value, so the value should be replaced

Use --strict-nullable when the difference between a missing key and a nullable value matters:

datamodel-codegen --input user-patch.schema.json \
  --output-model-type typing.TypedDict \
  --strict-nullable \
  --target-python-version 3.13 \
  --output model.py
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "UserPatch",
  "type": "object",
  "required": ["id"],
  "properties": {
    "id": {"type": "string", "readOnly": true},
    "nickname": {"type": ["string", "null"]},
    "bio": {"type": "string"}
  },
  "additionalProperties": false
}
from typing import NotRequired
from typing_extensions import TypedDict

class UserPatch(TypedDict, closed=True):
    id: str
    nickname: NotRequired[str | None]
    bio: NotRequired[str]

Here nickname: NotRequired[str | None] means the key may be omitted, and None is still a real value when the key is present. This follows PEP 655, which added Required and NotRequired for per-key presence in TypedDict.

If the schema marks response-only fields with readOnly, combine this with --use-frozen-field to generate ReadOnly for TypedDict output:

datamodel-codegen --input user-patch.schema.json \
  --output-model-type typing.TypedDict \
  --strict-nullable \
  --use-frozen-field \
  --target-python-version 3.13 \
  --output model.py
from typing import NotRequired, ReadOnly
from typing_extensions import TypedDict

class UserPatch(TypedDict, closed=True):
    id: ReadOnly[str]
    nickname: NotRequired[str | None]
    bio: NotRequired[str]

ReadOnly comes from PEP 705. The closed=True output comes from PEP 728 and represents additionalProperties: false for type checkers that understand closed TypedDicts.

Background

The distinction between missing keys, None, and unset update arguments is covered in Koudai Aono's PyCon US 2026 talk Beyond Optional in Real-World Projects. In datamodel-code-generator today, TypedDict and msgspec preserve that boundary shape most directly. Pydantic models and dataclasses are usually a better fit after the boundary has been normalized into application data.


msgspec

Use --output-model-type msgspec.Struct

msgspec offers high-performance serialization with validation.

pip install 'datamodel-code-generator[msgspec]'
datamodel-codegen --input schema.json --output-model-type msgspec.Struct --output model.py
from msgspec import Struct, field
from typing import Union, UnsetType
from msgspec import UNSET

class Pet(Struct):
    id: int
    name: str
    tag: Union[str, UnsetType] = UNSET

When to use

  • High-performance JSON/MessagePack serialization
  • Memory-efficient data structures
  • APIs with strict performance requirements

Choosing the Right Type

graph TD
    A[Need runtime validation?] -->|Yes| B[Need best performance?]
    A -->|No| C[Need type hints for dicts?]
    B -->|Yes| D[msgspec.Struct]
    B -->|No| G[Prefer dataclass syntax?]
    G -->|Yes| H[pydantic_v2.dataclass]
    G -->|No| I[pydantic_v2.BaseModel]
    C -->|Yes| J[typing.TypedDict]
    C -->|No| K[dataclasses.dataclass]

Decision Guide

  1. API with validation → Pydantic v2
  2. Validation with dataclass syntax → Pydantic v2 dataclass
  3. High-performance serialization → msgspec
  4. Simple data containers → dataclasses
  5. Dict-based JSON handling → TypedDict

See Also