Before GitHub Copilot, before ChatGPT, there was already a tool quietly writing more code than any human ever could.
Ten billion lines. That's how much code quicktype has generated, by conservative estimates. It was arguably the most prolific code generator on the planet.
In July 2017, Mark and I launched quicktype as an open-source tool that generates strongly-typed models and serializers from JSON. By its first birthday, we'd crossed a billion lines. Today? Ten billion and counting.
And it speaks everything: TypeScript, Python, Go, Rust, Swift, Kotlin, Java, C#, C++—plus Haskell for the purists, Dart for the Flutter folks, Elm for the functional faithful, and even Pike for the... okay, I don't actually know who uses Pike, but someone asked for it and we delivered. Over 30 languages in total, each generating idiomatic, human-quality code.
The architecture we built—with a clean separation between the type graph and language-specific renderers—made it possible for the community to add many of these languages. Here's the inside story of how we built it, the technical innovations we developed, and what we learned.
The Origin Story
In 2016, I was building a Swift app with data stored in Firebase. This was before Swift 4 and the Codable protocol—back then, there was no nice built-in way to serialize and deserialize JSON. You had to write tedious boilerplate code 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 these serialization methods by hand. Add a field? Edit two places. Rename a property? Edit two places. It was maddening.
One day, staring at yet another round of tedious edits, I thought: "This is a job for computers."
I hacked together a quick prototype—paste JSON, get Swift structs with serialization code. It worked. I walked over to Mark's desk and showed him. "Can we build this together?"
Mark had deep expertise in type systems and compilers from his work on language tooling. He saw immediately that this wasn't just a string-munging problem—there were real computer science challenges here. 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?
We started collaborating, and quicktype was born.
I searched for existing tools but found them lacking—most only supported one language, had obvious bugs, or produced ugly, non-idiomatic code. We knew we could do better.
The premise was simple: paste JSON, get type-safe code in your preferred language. But the execution required solving some surprisingly deep computer science problems.
The Architecture: Read, Simplify, Render
quicktype processes JSON in three stages, much like a classic compiler (see quicktype Under the Hood for the deep dive):
Stage 1: Read
In the Read stage, quicktype parses input JSON and infers an initial type graph. Each unique structure in the JSON becomes a node in the graph. For example, an array of objects creates an Array type containing a Class type.
The core data structures are elegantly simple:
- Type: The base class for all types (primitives, classes, arrays, maps, unions, enums)
- TypeGraph: A directed graph of types that represents the complete schema
- TypeBuilder: A builder pattern for constructing type graphs
Stage 2: Simplify
The Simplify stage is where the magic happens. It optimizes the type graph through several transformations:
Union Types: When the same position in JSON can contain different types, quicktype creates a union type. Given the JSON [1, "1"], quicktype infers Array<Int | String>. This handles the heterogeneous data that's extremely common in real-world JSON.
Type Unification: Equivalent types with different names are merged into a single type. This prevents the explosion of duplicate classes you'd get from a naive approach.
Map Detection: This was one of our most interesting innovations. JSON doesn't distinguish between objects (fixed properties) and maps (dynamic keys)—both look like { "key": value }. quicktype uses a Markov chain to analyze property names and determine whether they look like class property names or like data values.
Stage 3: Render
The Render stage converts the simplified type graph into source code. Each target language has its own renderer that handles:
- Naming conventions (camelCase, snake_case, PascalCase)
- Serialization/deserialization code
- Type syntax and annotations
- Import statements and dependencies
This architecture made it easy to add new languages—you only need to implement a new Renderer. That's how we went from supporting 2 languages at launch to over 30 today.
quicktype also supports custom renderers, allowing you to extend existing language support or create entirely new output formats. This extensibility was key to adoption—organizations could customize output to match their coding standards without forking the entire project.
Little Big Details
What separates good code generation from great code generation is attention to detail. We called these "Little Big Details"—the subtle niceties that make quicktype's output look like code a human would write.
Perfect Property Names
JSON property names come in all shapes and sizes:
{
"user_name": "alice",
"firstName": "Alice",
"LAST-NAME": "Smith",
"EmailAddress": "alice@example.com"
}
Different languages have different conventions—Swift uses camelCase, Python uses snake_case, C# uses PascalCase. quicktype:
- Detects the naming style of each property
- Splits the name into words
- Reconstructs the name in the target language's convention
- Generates mapping code when the JSON name differs
For Swift, the above JSON becomes:
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"
}
}
The property names follow Swift convention, but the CodingKeys enum preserves perfect JSON compatibility.
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 user and company have an address, but they have different structures. A naive approach might name both Address and cause 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;
}
The system only adds prefixes when necessary—if there's no collision, it keeps the simple name.
Detecting Maps with Markov Chains
This is my favorite technical innovation in quicktype (I wrote a whole 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 programmer would immediately recognize this as a Map<String, Block>.
How do human programmers know? The property names don't look like property names—they look like data. We needed a way to quantify this intuition.
The solution: a Markov chain trained on typical class property names. A Markov chain models the probability of character sequences. For English property names, the transition from qu to i is high probability (think "acquire", "equip"), while qu to x is nearly zero (no English words contain "qux").
We built a Markov chain from thousands of real property names. When quicktype encounters an object, it scores each property name. Names like firstName score high; names like 000000000000000000c846dee2e13c6408f5 score near zero. Below a threshold, quicktype generates a map instead of a class.
The result:
typealias Blocks = [String: Block]
struct Block: Codable {
let hash: String
let height: Int
let time: Int
}
Exactly what a human would write.
Multiple Samples for Better Types
A single JSON sample might not represent all possible values (see our post on multi-sample inference). If your first sample has "lastSeen": null, quicktype infers lastSeen is always null. But 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:
interface User {
name: string;
status: string;
lastSeen: string | null;
}
quicktype merges the type graphs from all samples, creating unions where values differ. This produces more robust types that handle real-world data variations.
TypeScript as a Schema Language
One of my favorite features: 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 for most developers, making it an excellent "schema language":
# Infer TypeScript from a sample (or just write it!)
quicktype pokedex.json -o pokedex.ts --just-types
# Generate Swift from TypeScript
quicktype pokedex.ts -o src/ios/models.swift
Cross-Language Type Conversion
quicktype can also read source files from one language and generate equivalent types in another. Have a C# backend and need Swift types for your iOS app?
quicktype --src-lang csharp Models.cs -o Models.swift
This became invaluable for teams building cross-platform applications who wanted to keep their types synchronized.
What We Learned
Many developers don't actually know JSON. This was the biggest surprise. We expected quicktype users to paste valid JSON and get code. Instead, we got an endless stream of malformed input—trailing commas, single quotes, unquoted keys, comments, JavaScript objects that weren't JSON at all. We ended up building increasingly sophisticated error recovery just to handle the garbage people pasted in. It was humbling.
Attention to detail matters. The "Little Big Details"—perfect property naming, contextual class names, map detection—are what made quicktype feel magical. Generated code that looks like human code encourages adoption.
Meet developers where they are. The web app was a great demo, but adoption exploded when we added CLI and IDE integrations. Fitting into existing workflows beats asking developers to change their workflow.
Type inference is hard. JSON is loosely typed, but programming languages are (increasingly) strongly typed. Bridging that gap required creative solutions like Markov chains for map detection and multi-sample inference.
The problem is universal. Every developer who works with JSON APIs has written tedious type definitions by hand. We solved a problem everyone has.
Open source works. The Rust, Kotlin, and Python renderers all came from contributors. We couldn't have built support for 30+ languages ourselves.
Why quicktype Still Matters
In the age of GitHub Copilot and ChatGPT, you might wonder if quicktype is obsolete. It's not—and here's why.
quicktype does something that large language models fundamentally struggle with: the more data you give it, the better it understands your schema. Feed quicktype a hundred JSON samples and it will produce more accurate, more complete types. Feed an LLM a hundred samples and it will probably get confused, hit context limits, or hallucinate properties that don't exist.
quicktype's approach is deterministic. Given the same input, you get the same output. It doesn't guess—it infers types through principled algorithms. That predictability matters when you're generating code that will run in production.
The community keeps quicktype alive with new features, bug fixes, and language support. Looking back, it represents something I'm proud of: a tool that genuinely makes developers' lives easier, built with care for the details that matter.
If you haven't tried quicktype, give it a spin at quicktype.io. And if you want to contribute, check out the GitHub repo.