Page title
Section title
Generating Code from JSON Typedef Schemas with jtd-codegen
JSON Type Definition, aka RFC 8927, is an easy-to-learn, standardized way to define a schema for JSON data. You can use JSON Typedef to portably validate data across programming languages, create dummy data, generate code, and more.
jtd-codegen is a tool that can generate types (clases, interfaces, structs,
etc.) in many programming languages from a JSON Typedef schema. It lives on
GitHub here.
This article will get you started on why jtd-codegen may be useful to you, how
to install it, and how you can use jtd-codegen-generated code in your
applications. Because every language has its own conventions and paradigms,
using the generated code will vary a bit from language to language. For specific
guidance for your preferred language, see:
- TypeScript-specific documentation
- Go-specific documentation
- Java-specific documentation
- C#-specific documentation
- Python-specific documentation
- Rust-specific documentation
Why generating code from schemas is useful
At a high level, generating code from schemas is useful because it fixes the missing link between your program’s type system and the JSON your program reads/writes. The alternatives to generating code from schemas is:
-
Writing the JSON mapping layer yourself. This entails writing classes, structs, or whatever else is idiomatic to your language that know how to load themselves from JSON, and know how to write themselves out as JSON.
Writing this sort of code is an error-prone and tedious process. If you have clients and servers written in different languages, you’ll have to repeat this work for every language in use every time you change your schema.
-
Not writing a JSON mapping layer at all. This means that you pass around some generic JSON data blob throughout your code.
When you do this, you lose the ability to lean on your compiler and IDE to tell you what properties do and don’t exist in your JSON, and what type those properties have. Very often, you’ll run into runtime errors due to missing properties, properties of the wrong type, etc. Plus, the result is usually less performant, because JSON blobs can’t be optimized like native types can.
JSON Type Definition is oftentimes a better alternative. It can do runtime
validation of JSON inputs and give you compile-time
data structures for valid data (via jtd-codegen), all from a single source of
truth: your JSON Typedef schema. Your code will be reliable against bad inputs,
you’ll save yourself the pain of writing mudane JSON mapping code, and you’ll be
able to write type-safe code end-to-end.
Installing jtd-codegen
If you’re on macOS, the easiest way to install jtd-codegen is via Homebrew:
brew install jsontypedef/jsontypedef/jtd-codegen
For all other platforms, you can install a prebuilt binary from the latest
release of
jtd-codegen.
Supported platforms are:
- Windows (x86_64-pc-windows-gnu.zip)
- macOS (x86_64-apple-darwin.zip)
- Linux (x86_64-unknown-linux-gnu.zip)
Finally, you can also install jtd-codegen from source. See the jtd-codegen
repo for more information
if you go this route.
Using jtd-codegen
You can always run jtd-codegen --help to get details, but at a high level
here’s how you usually use jtd-codegen:
-
First, you need a schema to generate code from. Let’s pretend you have this in a file called
schemas/user.jtd.json. -
Then, you need to know what programming languages you want to generate code for, and where you want
jtd-codegento put the generated code. Let’s pretend you’re interested in TypeScript, and you want to put the code in asrc/userdirectory. -
Finally, you invoke
jtd-codegenon your file, passing the CLI flags for the language(s) you’re interested in. You telljtd-codegenwhat language(s) to generate using the appropriate--XXX-outflag. For TypeScript, that’s--typescript-out.
Putting it all together, you get this invocation for TypeScript:
jtd-codegen schemas/user.jtd.json --typescript-out src/user
When you run that, you’ll get output like this:
📝 Writing TypeScript code to: src/user
📦 Generated TypeScript code.
📦 Root schema converted into type: User
And, assuming your user.jtd.json looked something like this:
{
"properties": {
"id": { "type": "string" },
"createdAt": { "type": "timestamp" },
"karma": { "type": "int32" },
"isAdmin": { "type": "boolean" }
}
}
Then the generated src/user/index.ts would contain:
export interface User {
createdAt: string;
id: string;
isAdmin: boolean;
karma: number;
}
Some languages require additional configuration, however. For example, if you
were to try to do the same thing, but with Go (i.e. replacing --typescript-out
with --go-out), you’ll get this error:
error: The following required arguments were not provided:
--go-package <package>
As the error suggests, you can fix this issue by passing the Go-specific required arguments. For instance, this invocation would work:
jtd-codegen schemas/user.jtd.json --go-out src/user --go-package user
And generates the following inside src/user/user.go:
package user
import "time"
type User struct {
CreatedAt time.Time `json:"createdAt"`
ID string `json:"id"`
IsAdmin bool `json:"isAdmin"`
Karma int32 `json:"karma"`
}
Integrating jtd-codegen
Every programming language is different, and jtd-codegen strives to generate
code that is idiomatic to each language. For that reason, exactly how you
integrate generated code in your codebase will depend on the language. See the
language-specific documentation (links at the top of this article) for
specifics.
Here are some general considerations on how you can integrate jtd-codegen into
your codebase’s workflow.
-
jtd-codegenis deterministic. If you give it the same schema, you’ll get the same code generated each time. So if it makes your life easier, you can consider invokingjtd-codegenon every build. You can consider checking generated code into source control for the same reason. -
jtd-codegenismake-friendly. In particular, if you want to regeneratesrc/user/index.tswheneverschemas/user/user.jtd.jsonchanges, the followingMakefilerecipe could do the trick:src/user/index.ts: schemas/user/user.jtd.json jtd-codegen --typescript-out src/user schemas/user.jtd.json -
You can pass
jtd-codegena schema via standard in. This can be useful if you’re writing your schemas as YAML, because that means you can do something like this:# yaml2json is a program that takes YAML and prints JSON to stdout yaml2json schemas/user.jtd.yml | jtd-codegen - --root-name user [...]Where
[...]is whatever CLI flags you would normally pass tojtd-codegen. Note that the schema “file” is just-: that tellsjtd-codegento read the schema from stdin.Note also the
--root-nameargument, which tellsjtd-codegenwhat the “top-level” name of the schema is. When you use a file as input tojtd-codegen, it can infer the value of--root-namefrom the name of the file. But stdin doesn’t have a name, so you’ll need to givejtd-codegena hint if you want it to generate good type names.
Finally, if you’re using jtd-codegen in your own tooling – for instance, if
you’re building some sort of custom interface description language (à la
OpenAPI) on top of JTD, see the jtd-codegen guidance in “Embedding
JTD” for how you can plug
jtd-codegen-generated code into your tooling.
Section title
-
Documentation
-
Learn JTD in 5 Minutes
-
Implementations
-
Tooling
-
Advanced Concepts
-
Language-Specific Documentation