DEV Community

Mojave Hao
Mojave Hao

Posted on

json2rs: Generate Struct Definitions from JSON, Without the Magic

I built a small CLI tool that converts JSON files into struct definitions for Rust, Python, TypeScript, Kotlin, and Java.

It's called json2rs.

The Problem

You have a JSON file — maybe an API response, maybe a config schema — and you need to write struct/class definitions for it. Doing it by hand is tedious. Existing generators are often too clever: they guess, they infer, they silently produce something that almost compiles.

I wanted something different.

Design Philosophy

Fail fast and loud. If the input JSON is malformed or ambiguous in a way that would produce bad output, the tool stops and tells you. No silent failures, no generated code that compiles but lies about your data.

Not intelligent. json2rs does not try to infer whether two similar structures should be the same type. It does not guess optional fields. It does not merge anything. What it sees is what it generates.

Output that reads like hand-written code. The goal is that you could have written the output yourself. No extra derives you didn't ask for, no wrapper types, no framework assumptions.

Minimal configuration. The config file is 17 lines. If you need more than that to describe what you want, the tool is probably doing too much.

Usage

json2rs input.json
json2rs input.json -c typescript
json2rs input.json -c python
Enter fullscreen mode Exit fullscreen mode

The output goes to stdout. Pipe it wherever you want.

Configuration

The entire config file for Rust output looks like this:

ext = "rs"
before_struct = "#[derive(Debug, serde::Serialize, serde::Deserialize)]\npub struct "
after_struct = "}\n\n"
before_struct_name = ""
after_struct_name = " {\n"
each_attr_format = "    pub $NAME: $TYPE,\n"
number = "i64"
string = "String"
boolean = "bool"
array = "Vec<$T>"
object = "$T"
null = "serde_json::Value"
optional = "Option<$T>"
file_header = "use serde_json::Value;\n\n"
file_footer = ""
indent = "    "
field_separator = ""
Enter fullscreen mode Exit fullscreen mode

Template variables, prefix/suffix separation, per-type mapping. Switching to a different language means swapping the config file — the core logic doesn't change. That's why the output reads like hand-written code: you're the one defining what hand-written looks like.

Implementation Notes

The core is ~400 lines of Rust, leveraging a small set of dependencies: serde_json for JSON parsing, clap for CLI argument handling, serde for serialization/deserialization of the config, anyhow for error handling, toml for config file parsing, and lazy_static for certain static initializations. No async runtime, no heavy codegen framework — the dependency surface remains intentionally clean.

In a performance test, json2rs processed a 14.5 MB JSON file containing 1,065,218 lines with a nesting depth of 11 levels, generating Rust struct definitions in just 148 ms. This demonstrates its efficiency even with large inputs.

The type inference is shallow by design: it walks the JSON once, maps each value to the most literal type it can, and emits. Edge cases that would require heuristics are instead reported as errors.

What It's Not

It's not a schema generator. It's not bidirectional. It won't handle arbitrarily nested polymorphic types gracefully — it will tell you it can't.

If you want something that tries hard and sometimes gets it wrong silently, there are other tools. This one tries less and fails loudly.

Source

github.com/BlophyNova/json2rs

Issues and PRs welcome.

Top comments (0)