JSON to Code (Model Classes)

Paste JSON and get typed model classes in Python (dataclass or Pydantic), Go, Java, Kotlin, C# or Swift, with real naming and nullability rules.

🌐 Español

🔒 Private by design: your text is processed locally in your browser and never uploaded to any server.

Seven targets, and what each one uses to remember the original key

Printing name: str for a key called name is the easy half. The half that decides whether the generated file is usable is what happens once post_code has become a property called postCode. Unless the original spelling is recorded somewhere, the model has quietly stopped describing the payload it came from, and no compiler will mention it.

Each renderer answers that with the mechanism its own language has. Go writes a json:"post_code" struct tag on every field, renamed or not. Pydantic writes a Field(alias=...) holding the original key, but only where the Python name came out different, and Java, Kotlin and C# follow that same rule with a Jackson @JsonProperty("..."), a kotlinx.serialization @SerialName("...") and a System.Text.Json [JsonPropertyName("...")]. Since C# properties are PascalCase, an ordinary lowercase key counts as renamed there, so nearly every property picks up an attribute. Swift emits a CodingKeys enum only when something was renamed, and then lists every property in it, because the enum has to cover them all once it exists.

The plain Python dataclass is the exception: it has no aliasing at all. To build one directly from a parsed dict, either choose Python Pydantic or untick the naming checkbox and keep your keys as they arrived.

Two records in, a dataclass and a Go struct out

This is a real run rather than an illustration. The input:

[
  { "user_id": 1, "first_name": "Ada", "is_active": true, "tags": [],
    "home_address": { "city": "London", "post_code": "NW1" } },
  { "user_id": 2, "first_name": "Alan", "is_active": false, "tags": [],
    "home_address": { "city": "Wilmslow", "post_code": "SK9" },
    "last_login": "2026-01-04" }
]

On the defaults, that produces:

from dataclasses import dataclass
from typing import Any, Optional, List

@dataclass
class HomeAddress:
    city: str
    post_code: str

@dataclass
class Root:
    user_id: int
    first_name: str
    is_active: bool
    tags: List[Any]
    home_address: HomeAddress
    last_login: Optional[str] = None

# Your JSON is a list of these: List[Root]

Four details there came from the data, not from a template. HomeAddress is named after the key that held it and is written above Root, because a Python type hint needs the class to exist already. last_login is optional because the first record has no such key, and in the dataclass renderer an optional field always sinks to the bottom of its class. tags became List[Any] since an empty array reveals nothing about its contents. And only the imports the file genuinely uses are written, so switching the nullable style to X | None drops Optional from that line.

Set Target language to Go structs and the same input gives you this, indented with a tab in the real output:

package models

type HomeAddress struct {
    City string `json:"city"`
    PostCode string `json:"post_code"`
}

type Root struct {
    UserId int `json:"user_id"`
    FirstName string `json:"first_name"`
    IsActive bool `json:"is_active"`
    Tags []interface{} `json:"tags"`
    HomeAddress HomeAddress `json:"home_address"`
    LastLogin *string `json:"last_login,omitempty"`
}

// Your JSON is a list: []Root

The optional string became *string with omitempty on its tag, while Tags did not become a pointer at all. A nil slice is already Go’s way of saying absent, and *[]T is an idiom worth not generating.

The Go renderer ignores the naming checkbox, and one key shape defeats it

Untick Convert field names to the target language’s naming convention, run that same JSON through Go structs, and you get character for character what you got with the box ticked. Go exports a struct field by capitalizing it, and encoding/json silently skips an unexported field in both directions, so lowercase names would give you a struct that round-trips nothing. The renderer forces PascalCase whatever the option says.

One key shape defeats that rule, and it is a genuine bug. A key beginning with a digit cannot be an identifier in any of these languages, so an underscore is prefixed to make it valid. Feed in an object whose keys are first-name, 2fa and HTTPServer:

type Root struct {
    FirstName string `json:"first-name"`
    _2fa bool `json:"2fa"`
    HttpServer string `json:"HTTPServer"`
}

_2fa starts with an underscore, which in Go means unexported, which means encoding/json ignores it in both directions. Rename that field by hand before compiling. The two lines around it show the word splitter behaving well on a hyphenated key and on a run of capitals.

Java allows one public class per file, and that decides the file name

Java is the only target here with an opinion about files. A .java file may declare at most one public top-level class, and its name has to match the file, so a file full of public model classes will not compile at all. The renderer marks only the root class public and leaves the nested ones at package scope, still usable from anywhere in the same package. The output therefore has to be saved as Root.java to compile as it stands.

Two smaller specifics. Every field is boxed, Integer and Boolean rather than the bare primitives, so a JSON null deserializes without an unboxing crash even into a field the inference thought was always present. And accessors are built from the converted field name, so is_active becomes a field isActive with a getter called getIsActive() rather than isActive(). That compiles; it just reads oddly next to hand-written Java.

Which of the seven a compiler has actually seen

The module is straight about this, so this page will be too. While it was written, the Python and Java output were checked against real toolchains, Python 3.12 parsing the generated file and importing dataclasses, and javac compiling the Java against a stub annotation standing in for Jackson. No Go, Kotlin, C# or Swift compiler was available in that environment, so those four were verified against each language’s documented conventions instead. Weaker, and the source comment says so rather than implying all seven were equal.

Two conventions they encode are easy to overlook. A Kotlin data class must have at least one constructor parameter, so an empty {} in your JSON comes out as a plain @Serializable class Meta. And Swift has no built-in type for an arbitrary JSON value, so when your data needs one the file gains a small self-contained AnyCodable struct, added only in that case.

Pasting, generating, and the button that empties the box

  1. Paste a JSON object, or an array of objects, into the box above. More records is better, since a key missing from one of them is the only evidence that the key is optional.
  2. Choose Target language. Python dataclass is the default; the rest are Python Pydantic, Go structs, Java, Kotlin, C# and Swift.
  3. For either Python target, set Python nullable-type style (Python only) to Optional[X], from typing, works on any Python 3 or to X | None, PEP 604, Python 3.10 and up. The other five ignore it.
  4. Leave Convert field names to the target language’s naming convention ticked for idiomatic naming, or untick it to keep your keys as written.
  5. Click JSON to Code (Model Classes). The code replaces the input box, Copy to clipboard takes it, and Process another starts over.

That last button clears the input as well as the output, so producing the same payload in a second language means pasting it in again. The dropdowns keep what you last chose, so hold the JSON on your clipboard until you have every file you need. If it will not parse at all, the JSON Formatter is the quickest way to find out and the JSON Editor is where to repair it as a tree; if the real API does not exist yet, the Fake Data Generator builds a JSON array to generate from.

Working in TypeScript instead? JSON to TypeScript imports the very inference core this page runs on, with one deliberate difference: it never renames a key, because a TypeScript property name is the JSON key and there is no alias to fall back on. And when the question is whether an incoming payload matches a contract rather than what shape it happens to have, the JSON Schema Validator is the one that answers it.

See it in action

Screenshot of the JSON to Code (Model Classes) tool with the sample input “{"tool":"json-to-code","runsLocally":true,"forma…”, Target language set to Python dataclass, Python nullable-type style (Python only) set to Optional[X], from typing, works on any Python 3
JSON to Code (Model Classes) mid-process: the sample input “{"tool":"json-to-code","runsLocally":true,"forma…”, Target language set to Python dataclass, Python nullable-type style (Python only) set to Optional[X], from typing, works on any Python 3.
Screenshot of the JSON to Code (Model Classes) result screen showing the generated output “from dataclasses import dataclass from typing im…”
The finished result: the generated output “from dataclasses import dataclass from typing im…”. The download link is a local blob URL — the file never leaves your device.

Frequently asked questions

Where is the .py or .java file the generator names?

Nowhere you can see, which is worth knowing before you save the Java output. The module does pick a name for every language, models.py, models.go, Models.kt, Models.cs, Models.swift, and for Java the root class name followed by .java. This page uses the paste-in, copy-out layout, which reads the generated text out of the result and throws the name away, so you save the file yourself. For Java that name matters, since the file has to be called Root.java to compile as it stands.

Does the nullable-type style setting change anything outside Python?

No, and its label says so in brackets. It only chooses between the two Python spellings of an optional field. Each of the other targets has one convention and nothing to pick, so Go takes a pointer for an optional scalar, Java uses boxed types such as Integer and Boolean throughout, Kotlin and Swift add a question mark to the type, and C# adds one to value types and reference types alike.

Why did the dataclass move one of my fields to the bottom of the class?

Because a plain dataclass refuses to declare a required field after one that carries a default, and raises at class definition time if you try. Every field that can be absent or null is therefore emitted last with a default of None, so the file always imports cleanly. The Pydantic renderer deliberately does not reorder anything, since a BaseModel takes keyword data rather than a positional signature, so there your original key order survives.

I pasted an array of numbers and got three lines of comment instead of a class.

That is the honest answer for an array holding no object structure at all, including an empty array. The comment explains that there is nothing there to turn into a class and asks for an object or an array of objects instead. A bare string, number, boolean or null at the top level is treated differently and raises an error, which this page's shared layout replaces on screen with one generic sentence while writing the real message to the browser console.

My Go struct has a field starting with an underscore. Will it deserialize?

No, and this one is a real defect rather than a trade-off. A JSON key beginning with a digit, 2fa for example, cannot be an identifier in any of these languages, so the generator prefixes an underscore to make it valid. In Go a leading underscore leaves the field unexported, and encoding/json skips unexported fields in both directions, so that single field is silently dropped. Rename it before you compile.

If I untick the naming conversion, does every language still work?

Yes, though it changes more than the spelling. Keys are kept as written with only invalid characters replaced, which is what you want if you plan to build a plain Python dataclass straight from a parsed dict. Go ignores the checkbox completely and capitalizes regardless. Java, Kotlin and C# only attach their annotation to a field whose name actually changed, and Swift only writes its CodingKeys enum when at least one name changed, so unticking the box strips most of that scaffolding out along with the renaming.

Related tools