JSON1

JSON1 / JSON to Rust

JSON to Rust

Generate Rust structs with Serde derives from a JSON sample. Optional fields become Option types, and any key that snake_case cannot reach gets an explicit serde rename.

Processed locally
InputJSON
Ready
OutputRust

Converted output appears here as you type.

Good to know

  • Every record in an array is inspected, not just the first, so a field that appears in only some of them is typed as optional rather than required.
  • Identical nested shapes are emitted once and reused, and reserved words are renamed to something legal in this language.

About this tool

Serde does the deserialising; what it needs from you is a struct that matches the payload, with the right derives and the right renames. This generates that — Debug, Clone, Serialize, and Deserialize on every struct, snake_case fields, nested objects hoisted out, and an explicit #[serde(rename)] on any key that snake_casing would not round-trip.

Rust is the strictest target here, and the mapping reflects that:

  • i64JSON integers. Signed by default because JSON does not distinguish, and 64-bit because an id from an API frequently is.
  • f64Anything fractional. Note that i64 and f64 are different types with no implicit conversion, so a field that arrives as 1 in one record and 1.5 in the next needs f64 or deserialisation fails.
  • Option<T>Optional or nullable. Rust has no null at all, so the absent case has to be handled at the point of use — the compiler will not let you forget.
  • Vec<T>Arrays, with T inferred from every element rather than the first.
  • ValueFrom serde_json, for a field with no observable type. It accepts whatever arrives and defers the decision.

Unlike most targets, a mismatch here is a hard failure rather than a silent one. Serde returns an error on a type that does not fit, which is what makes the generated struct worth getting right up front.

Why is a field wrapped in Option?
Because Rust has no null, so a field that is missing from some records or arrives as null has no other representation. Option<T> moves the absent case into the type, where the compiler makes you deal with it, rather than into a runtime surprise.
Why serde_json::Value for some fields?
A field that was null in every record carries no type information whatsoever, and Rust will not let you leave a type unspecified. Value accepts anything; swap it for the concrete type once you have a sample where the field is populated.
Should I add #[serde(deny_unknown_fields)]?
Only if you want new API fields to break your build. It is genuinely useful for a config file you own — a typo becomes an error rather than a default — and usually wrong for a third-party response, where the provider adding a field should not take your service down.
What about lifetimes and &str instead of String?
Borrowed fields (&'a str) avoid an allocation per string, and serde supports them — but only when the deserialised value cannot outlive the input buffer, which for most code means it does not work where you want to use it. String owns its data and compiles wherever you put it.
How are optional fields decided?
Every record in an array is inspected, not just the first. A field that appears in some records and not others is typed as optional, and a value that is sometimes null widens accordingly. Generators that read only the first element get this wrong on exactly the payloads where it matters.
What happens to a key that is a reserved word?
It is renamed to something legal in the target language, and where the language supports it, an annotation records the original JSON key so serialisation still round-trips.