json2rs: Generate Struct Definitions from JSON, Without the Magic

Published: (February 26, 2026 at 11:12 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

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

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"
object = "$T"
null = "serde_json::Value"
optional = "Option"
file_header = "use serde_json::Value;\n\n"
file_footer = ""
indent = "    "
field_separator = ""

Template variables, prefix/suffix separation, and per‑type mapping let you swap the config file to target a different language while keeping the core logic unchanged. That’s why the output reads like hand‑written code: you define 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
  • lazy_static for certain static initializations

No async runtime, no heavy code‑gen 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

  • Not a schema generator.
  • Not bidirectional.
  • It won’t handle arbitrarily nested polymorphic types gracefully — it will tell you it can’t.

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

Source

github.com/BlophyNova/json2rs – issues and PRs welcome.

0 views
Back to Blog

Related posts

Read more »