Ten billion lines. That's how much code quicktype has generated. Before GitHub Copilot, before ChatGPT, it was arguably the most prolific code generator on the planet.
Mark and I launched quicktype in July 2017 as an open-source tool that generates strongly-typed models and serializers from JSON. By its first birthday, we'd crossed a billion lines.
It supports over 30 languages—TypeScript, Python, Go, Rust, Swift, Kotlin, Java, C#, C++, Haskell, Dart, Elm, and more—each generating idiomatic, human-quality code. The architecture we built, with clean separation between the type graph and language-specific renderers, made it possible for the community to add many of these.
The Origin Story
In 2016, I was building a Swift app before Swift 4 and Codable. There was no nice way to serialize JSON—you had to write tedious boilerplate for every model type:
struct User {
let name: String
let email: String
let age: Int
init?(json: [String: Any]) {
guard let name = json["name"] as? String,
let email = json["email"] as? String,
let age = json["age"] as? Int else {
return nil
}
self.name = name
self.email = email
self.age = age
}
func toJSON() -> [String: Any] {
return ["name": name, "email": email, "age": age]
}
}
Every time I changed my data model, I had to update serialization methods by hand. This is a job for computers.
I hacked together a prototype—paste JSON, get Swift structs—and showed it to Mark. He had deep expertise in type systems and compilers, and saw immediately that this wasn't just string munging. How do you infer the best types from sample data? How do you handle heterogeneous arrays? How do you know when an object is really a dictionary?
The premise was simple: paste JSON, get type-safe code. The execution required solving some surprisingly deep computer science problems.
The Architecture: Read, Simplify, Render
quicktype processes JSON in three stages, like a compiler (see quicktype Under the Hood for the deep dive):
Read: Parse JSON and infer an initial type graph. Each unique structure becomes a node—an array of objects creates an Array type containing a Class type.
Simplify: Optimize the type graph. Create union types when the same position can contain different types ([1, "1"] → Array<Int | String>). Merge equivalent types. Detect maps vs. objects using a Markov chain (more on this below).
Render: Convert the type graph to source code. Each language has its own renderer handling naming conventions, serialization, type syntax, and imports.
This architecture made it easy to add languages—just implement a new Renderer. That's how we went from 2 languages at launch to over 30.
Little Big Details
We called these "Little Big Details"—the subtle niceties that make quicktype's output look human-written.
Property Names
JSON property names come in all shapes:
{
"user_name": "alice",
"firstName": "Alice",
"LAST-NAME": "Smith",
"EmailAddress": "alice@example.com"
}
quicktype detects naming styles, splits names into words, reconstructs them in the target language's convention, and generates mapping code when needed. For Swift:
struct User: Codable {
let userName: String
let firstName: String
let lastName: String
let emailAddress: String
enum CodingKeys: String, CodingKey {
case userName = "user_name"
case firstName
case lastName = "LAST-NAME"
case emailAddress = "EmailAddress"
}
}
Contextual Class Names
Consider this JSON:
{
"user": {
"address": { "street": "123 Main St", "city": "Springfield" }
},
"company": {
"address": {
"street": "456 Business Ave",
"city": "Commerce City",
"suite": "100"
}
}
}
Both have an address with different structures. A naive approach names both Address and causes conflicts. quicktype uses contextual naming:
interface User {
address: UserAddress;
}
interface UserAddress {
street: string;
city: string;
}
interface Company {
address: CompanyAddress;
}
interface CompanyAddress {
street: string;
city: string;
suite: string;
}
Prefixes are only added when necessary.
Detecting Maps with Markov Chains
My favorite innovation (I wrote a blog post about it). Consider this Bitcoin blockchain data:
{
"000000000000000000c846dee2e13c6408f5": {
"hash": "000000000000000000c846dee2e13c6408f5",
"height": 503162,
"time": 1515187634
},
"00000000000000000024fb37364cbf81fd95": {
"hash": "00000000000000000024fb37364cbf81fd95",
"height": 503163,
"time": 1515188162
}
}
A naive translation would create a class with properties like the000000000000000000C846Dee2E13C6408F5—clearly wrong. A human would immediately recognize this as a Map<String, Block>.
How? The property names don't look like property names—they look like data. We quantified this intuition with a Markov chain trained on typical property names. The chain models character sequence probabilities: qu → i is high probability, qu → x is near zero.
quicktype scores each property name against this model. Names like firstName score high; hashes score near zero. Below a threshold, it generates a map:
typealias Blocks = [String: Block]
struct Block: Codable {
let hash: String
let height: Int
let time: Int
}
Multiple Samples for Better Types
A single JSON sample might not represent all possible values (more details). Provide two samples:
// Sample 1: User is online
{ "name": "Alice", "status": "online", "lastSeen": null }
// Sample 2: User is offline
{ "name": "Bob", "status": "offline", "lastSeen": "2018-03-08T10:30:00Z" }
And quicktype correctly infers lastSeen: string | null by merging type graphs from all samples.
TypeScript as a Schema Language
You can write TypeScript type definitions and use them as input:
interface Person {
name: string;
nickname?: string; // an optional property
luckyNumber: number;
}
TypeScript is more readable than JSON Schema, making it an excellent "schema language":
quicktype pokedex.json -o pokedex.ts --just-types # Infer types from JSON
quicktype pokedex.ts -o src/ios/models.swift # Generate Swift from TypeScript
Cross-Language Type Conversion
quicktype can also read source files from one language and generate equivalent types in another:
quicktype --src-lang csharp Models.cs -o Models.swift
What We Learned
Most developers don't know JSON. We expected valid JSON. We got trailing commas, single quotes, unquoted keys, comments, JavaScript objects. We built increasingly sophisticated error recovery to handle the garbage people pasted in.
Details matter. Property naming, contextual class names, map detection—these made quicktype feel magical. Generated code that looks human-written encourages adoption.
Meet developers where they are. The web app was a great demo, but adoption exploded with CLI and IDE integrations.
Open source works. The Rust, Kotlin, and Python renderers came from contributors. We couldn't have built 30+ languages ourselves.
Why quicktype Still Matters
In the age of Copilot and ChatGPT, quicktype does something LLMs struggle with: the more data you give it, the better it understands your schema. Feed quicktype a hundred JSON samples and it produces more accurate types. Feed an LLM a hundred samples and it will hit context limits or hallucinate properties.
quicktype is deterministic. Same input, same output. It doesn't guess—it infers types through principled algorithms. That predictability matters for production code.
Give it a spin at quicktype.io, or contribute on GitHub.