JSON to Python Converter

Paste a payload and read back typed Python. Nested objects become their own classes, arrays of objects are merged so a key missing from one entry shows up as optional, and identical shapes share a single definition instead of being duplicated. Pick dataclasses, Pydantic v2, TypedDict or a plain class with a from_dict helper.

JSON to Python conversion bench

  • Merges every array element
  • Reuses matching shapes
  • Nothing leaves the tab
Output
JSON input.json
Python outputWaiting for JSON
0Classes written
0Fields typed
0Nesting depth
0Optional fields

One payload, four shapes of Python

JSON carries six types. Python carries a type system deep enough to describe all of them and then some. The gap between those two facts is where a hand written model class goes wrong, usually on the field somebody forgot was nullable. Generating the class from real data closes most of the gap in one pass, and the four output styles above cover the reasons you would want a class in the first place.

StyleRuntime validationInstance typeReach for it when
@dataclassNoneObject with attributesThe data already arrived clean and you want dot access plus a free __eq__ and __repr__
BaseModelFull, on constructionObject with attributesThe JSON crosses a trust boundary, so a wrong type should raise before it reaches your logic
TypedDictNoneStill a plain dictThe code already passes dicts around and you want the type checker to see the keys
Plain classNoneObject with attributesThe target runtime has no third party packages and you want an explicit from_dict

The gap worth understanding is the third column. A TypedDict is a static annotation over a dict, so order["total"] keeps working and mypy learns the keys, with zero cost at runtime. A dataclass and a Pydantic model both give you order.total, and both need something to build them. Pydantic builds itself from the raw dict with Order.model_validate(payload). A dataclass does not, which is why the plain class option writes a from_dict for you and the dataclass option leaves construction to you.

How each JSON value is turned into a type hint

Type inference runs over the values, not the keys. Every sample of a field contributes, so the type widens as the generator reads more of your payload.

What the JSON holdsPython type writtenReasoning
"text"strDefault for any string with no recognised format
"2026-03-14T09:21:05Z"datetimeOnly when every sample of the field parses as a timestamp
"2026-03-14"dateDate with no time part, all samples matching
"8f2b1c4e-0d3a-..."UUIDCanonical 8-4-4-4-12 hex form
42intNo fractional part anywhere in the samples
42.0 or a mixfloatOne decimal sample widens the whole field
trueboolChecked before number, since bool subclasses int in Python
null with other valuesOptional[T]Null is folded into the type rather than replacing it
null and nothing elseOptional[Any]No sample ever showed the real type
[]list[Any]An empty array carries no element type
[1, "a"]list[Union[int, str]]Element types are unioned, not narrowed to the first
{ ... }A generated classNamed after the key that held it

Generic collections are written as list[str] rather than List[str], which needs Python 3.9 or newer. The X | None toggle switches the rest of the syntax to PEP 604 and drops the Optional import, which needs 3.10. Leave it off and the output runs on 3.9.

What happens to an array of objects

This is the part most generators get wrong, and it is the reason to paste a whole response rather than one trimmed record. Every element of an array is folded into one shape. A key present in all of them stays required. A key present in some of them becomes optional.

JSON in
"line-items": [{ "sku": "KB-88", "qty": 1, "price": 189 },{ "sku": "MS-12", "qty": 2,"price": 30.25, "giftWrap": true }]
Python out
@dataclass
class LineItem:sku: str
qty: int
price: float
gift_wrap: Optional[bool] = None

Three decisions landed in those six lines. The array key was singularised, so the class is LineItem and the field is list[LineItem]. The price field saw 189 and 30.25, so it widened to float. And giftWrap appeared once out of two entries, so it came through optional with a default.

Field order shifted too. In a dataclass, a field carrying a default cannot sit above one without, or Python raises TypeError at class creation time. Optional fields are moved to the bottom for that reason, which means the attribute order stops matching your JSON key order. If the order matters to you, use Pydantic, where defaults are free to sit anywhere.

Key names Python will not accept

JSON keys are arbitrary strings. Python attribute names are not. Four collisions come up constantly in real payloads, and each style handles them differently.

Only Pydantic keeps a record of the original key. Every renamed field gets Field(alias="originalKey") plus model_config = ConfigDict(populate_by_name=True) on the class, so the model accepts the wire name and the Python name both. A dataclass has nowhere to record the mapping, so if you rename fields you own the translation in your parsing code.

TypedDict sidesteps the problem by keeping your keys exactly as they are, since they are dict keys rather than identifiers. When a key is not a valid identifier, the class switches to the functional form Order = TypedDict("Order", { ... }), which accepts any string.

Where a single sample misleads you

Inference is a reading of the data in front of it, not a schema. Four failures are worth knowing before you paste the output into a repository.

If you need guarantees rather than a reading, generate a JSON Schema from the same payload and treat the schema as the source of truth. Inference from examples is a fast start, not a contract.

Duplicate shapes collapse into one class

Two objects with matching field names, matching types and matching optionality get one class between them. In the sample payload, customer and shipping both hold a name, an email and a flag, so both fields are typed Customer and no Shipping class is written.

customer: Customer
shipping: Customer

This keeps the output readable on a large payload, where a naive generator would write forty near identical classes. It also occasionally merges two things you consider distinct. If shipping and customer are separate domain concepts in your code, split the class after generation. The tool is matching structure, and structure is all it has to go on.

A working example, start to finish

Assume you are wiring up an API client and want the response validated before it reaches your code.

  1. Call the endpoint and copy a full response body, ideally two or three of them concatenated into an array so optional fields reveal themselves.
  2. Paste it above, set the root class name to match the resource, and switch the output to Pydantic v2.
  3. Read the notes panel. Anything marked as a warning is a field where the payload did not give enough information.
  4. Download the file into your project, then fix the two or three fields the data could not describe: money to Decimal, status strings to Literal, and any array that came through as list[Any].
  5. Parse with Order.model_validate(response.json()) and let Pydantic raise on anything the endpoint sends that does not match.

Step four is the one people skip. The generator gets you to roughly ninety percent of a usable model in a second. The last ten percent is domain knowledge no tool reads off a payload.

What this converter does not do

Nothing you paste is uploaded. The parser and the generator both run in this page, so a payload with production data in it stays on your machine. Load the page once, drop your network connection, and everything above keeps working.

Questions about generating Python from JSON

Type inference, optional fields, key renaming, and picking between dataclasses and Pydantic.

Should I generate a dataclass or a Pydantic model?

It depends on where the JSON comes from. Data you produced yourself and already trust fits a dataclass, which costs nothing at runtime and gives you attribute access with a readable repr. Data arriving from an API, a webhook or a user upload fits Pydantic, because construction validates every field and raises a clear error naming the key at fault. The rule of thumb is simple: if a wrong type in the payload should stop your program early, use Pydantic.

Why is a field marked Optional when it has a value in my JSON?

Two situations produce that. The field held null in at least one place, so null became part of its type. Or the field lives inside an array of objects and was missing from some of the entries, which makes it optional across the merged shape. The notes panel under the editors distinguishes the two, and the second one is worth reading closely, since a key missing from one record often signals an API that omits empty values rather than sending null.

What Python version does the output need?

Python 3.9 by default. Built-in generics such as list[str] and dict[str, int] arrived in 3.9, and everything else in the default output is older than that. Switching on the X | None option raises the floor to 3.10, where PEP 604 union syntax became available. The TypedDict style is the exception: NotRequired landed in typing in 3.11, so on 3.9 or 3.10 change that one import to typing_extensions.

How does it decide a string is a datetime?

Every sample of the field has to match. A field where nine values parse as ISO 8601 timestamps and the tenth is the word pending stays str, because typing it as datetime would break on that tenth value. UUIDs are checked against the canonical 8-4-4-4-12 hex form, dates against a bare YYYY-MM-DD. Turn the detection checkbox off to keep every string as str, which is the safer choice when the payload is a sample rather than the full range of values.

My JSON key is a Python keyword. What happens?

The generator appends an underscore, so a key named class becomes the attribute class_, matching the convention the standard library uses in the same situation. In Pydantic the original spelling survives as an alias, so the model still accepts the raw payload and still serialises back with the correct key. In a dataclass or a plain class there is no alias mechanism, so the mapping lives in your parsing code. TypedDict keeps the original key untouched, since dict keys have no such restriction.

Why did two of my objects share a single class?

Because their shapes matched exactly, down to field names, inferred types and which fields were optional. Emitting one class instead of two duplicates keeps the file readable, and on a large payload it is the difference between six classes and sixty. When the two objects are genuinely separate concepts in your domain, copy the class, rename the copy, and point one field at each. The tool compares structure, which is all a payload exposes.

Can I convert an array of objects at the top level?

Yes. Every element is merged into one shape, a class is generated from it, and a type alias is added at the bottom in the form OrderList = list[Order]. Pasting an array of several records is the best way to use this tool, because optional fields only show up when the generator has more than one object to compare.

Does the plain class option build nested objects for me?

No, and this is its main limitation. The generated from_dict reads each key out of the dictionary and passes it to the constructor, so a nested object arrives as a raw dict rather than an instance of the nested class. Wire the nested calls in yourself, or switch to Pydantic, which walks the whole tree during validation and hands you fully built objects at every level.

Is my JSON sent to a server?

No. Parsing, type inference and code generation all run inside this page in JavaScript. There is no request to any server after the page loads, so a payload containing customer records or credentials never leaves your machine. Nothing is stored between visits either, and closing the tab clears both editors.