Home JSON to Pydantic

JSON to Pydantic model generator

Paste a JSON array and get the Pydantic models that parse it — nested models, Optional fields, and aliases wherever a JSON key is not a legal Python attribute name.

Why it reads every record, not the first one

The usual failure of a JSON-to-type tool is that it looks at one object. The first record's null becomes the type null; a key that happens to be absent from it never appears at all; a field that is 7 in the first row and 7.5 in the fortieth is typed as an integer. Each of those compiles, passes review and breaks on the second page of results.

This reads every record you paste and merges what it finds:

  • A key missing from any record is optional — the marker is on the key, not the value.
  • A key that is sometimes null is nullable — a different statement from optional, and both can be true.
  • Mixed numbers collapse to one number type. A field holding 7 and 9.5 is a number, not "an integer or a number".
  • Recognised string shapes keep their meaning where the target can express it — UUIDs, ISO timestamps, dates, emails and URLs.
  • An empty array says nothing about its elements, so it stays unknown rather than being guessed from a sibling record.

So paste more than one record. Paste the awkward ones — the row with the null, the row missing the optional field, the one from the second page. The output is only as good as the range of the sample, and that is a property of the input, not of the tool.

JSON keys can contain spaces, dots and hyphens; Python attributes cannot. A key like "full name" becomes an attribute named full_name with an alias back to the original, so parsing the untouched payload still works:

class User(BaseModel):
    full_name: str = Field(alias="full name")

Populate by alias when constructing from JSON — model_config = ConfigDict(populate_by_name=True) in Pydantic v2 if you also want to construct by the Python name. Without the alias the field would simply never populate, and it would do so silently.

Imports are emitted only where used

Optional, List, Union, UUID, date, datetime and Field each appear in the import block only if the models below actually use them. An unused import is a lint error in most Python projects, which turns a generated file into something you have to edit before it passes CI — and a generator whose output needs editing is one people stop using.

Model order and forward references

Nested models are emitted before the models that annotate fields with them. A class annotation naming a class defined further down the file is a NameError at import time, so the ordering is correctness rather than preference.

What is deliberately left to you

  • EmailStr is not used. It requires the email-validator package; the generated file has no dependencies beyond Pydantic itself. Swap it in if you already have that installed.
  • No validators. Ranges, regexes and cross-field rules are business logic, and a sample cannot see them.
  • No enums. A field holding only two values in the sample may hold a third tomorrow.
  • Pydantic v2 syntax. Optional[X] with Field(default=None) works on v1 too, but model_config and the v2 methods do not exist on v1.

Common questions

Does this target Pydantic v1 or v2?

The generated models use syntax valid in both: Optional[X] with Field(default=None), aliases via Field(alias=...), and standard typing constructs. Configuration you add on top — model_config versus class Config — differs between the versions.

Why is a JSON key with a space turned into an attribute with an underscore?

Because a Python attribute cannot contain a space. The field gets an alias back to the original key so parsing the untouched payload still works — without it the field would silently never populate.

Why is EmailStr not used for email fields?

EmailStr requires the email-validator package. The generated file is meant to run against Pydantic alone; swap str for EmailStr if you already depend on it.

Why are nested models defined first?

A class annotation that names a class defined further down the file raises a NameError when the module is imported. Emission order is correctness, not style.

Are unused imports included?

No. Optional, List, Union, UUID, date, datetime and Field are each imported only if the models use them, so the output does not fail a lint run before you have touched it.

Last updated