To check the result after converting JSON to a Rust struct, paste a representative strict JSON sample into the JSON to Rust Struct Converter, review the inferred numeric widths, Option wrappers, nested type names, and serde rename attributes it emits, then copy that source into a Serde-enabled Cargo project, format it, compile it, and round-trip your real JSON fixtures with serde_json. The generator emits source text without compiling, uploading, or executing anything, so verification has to happen on your side. Treat the emitted source as a reviewable draft shaped by one observation: every choice the tool makes — owned String, i64, f64, bool, Vec, Option, serde_json::Value, normalized snake_case fields, and keyword-safe suffixes — was derived from a single strict JSON input, not from an authoritative API description. The checking process therefore combines three passes: a static read of the emitted source, a build inside a real Rust project that pulls in serde derive support, and a behavioral test that decodes and re-encodes your real fixtures to confirm the inferred types accept what production actually sends.

Why the Generated Rust Source Still Needs a Review
Single-sample inference is intentionally conservative, so the output reflects what the parser saw and nothing more. A field present in your pasted JSON becomes required, while a key missing from one merged object sample becomes Option. Numeric observations that mix integers and decimals merge to f64, JSON integers that the JavaScript parser cannot represent exactly fall back from i64 inference, and incompatible evidence becomes serde_json::Value rather than a guessed enum. Because of those rules, the first verification step is reading the generated source and confirming each field matches the shape your real API or file format uses. Skipping that read is how production bugs appear: a small integer that requires u64, a UUID-shaped string, a borrowed slice, a date string, or a tagged enum cannot be inferred from one sample, and the converter is designed to flag that uncertainty rather than hide it.
Checking the Inferred Types, Options, and Renames
The verification pass on the generated source should walk every line and confirm the types align with what your data actually contains. The table below summarizes the mapping the converter applies when parsing strict JSON, drawn directly from the published inference rules.
| JSON observation | Inferred Rust type | Verification note |
|---|---|---|
| String value | String | Owned String; change to &str; or a newtype if the API borrows or carries a UUID, date, or URL. |
| Boolean | bool | No fallback needed unless the wire data uses 0/1. |
| Integer within safe range | i64 | JavaScript parses before inference, so unsafe spellings fall back to f64 — keep precision-sensitive ids as strings. |
| Decimal or mixed number | f64 | Confirm whether the field truly needs f64 or a fixed-point / Decimal type. |
| Array of observed elements | Vec of merged element type | Empty arrays become Vec<serde_json::Value> because no element type is established. |
| Null alone | Option<serde_json::Value> | Replaces null because no concrete type was observed. |
| Null combined with an observed type | Option<T> | Represents observed absence only; production may need a stricter or looser contract. |
| Object | Public struct with Serialize, Deserialize, Debug | Nested objects become named nested structs derived safely from the path. |
| Incompatible mixed values | serde_json::Value | Preserves uncertainty until you define the real contract. |
Identifiers also need a careful eye. JSON keys are converted to ASCII snake_case, with leading digits prefixed, Rust keywords receiving a trailing underscore, and normalized collisions receiving numeric suffixes. Whenever the resulting Rust field name differs from the original JSON key, the generator adds a serde rename attribute so the wire name still matches — for example, a JSON key like "userId" becomes user_id with #[serde(rename = "userId")]. Read each rename by hand: collisions and reserved-word handling are the most common silent sources of mismatches during deserialization.
Setting Up a Serde-Enabled Project for Verification
Because the converter only emits source text, the next part of checking the result is putting that text somewhere it can actually compile. Create a small Cargo project, pin compatible versions of serde and serde_json in its Cargo.toml, and add the generated module — the converter's import assumes serde derive support, and parsing on the destination side normally requires serde_json. The page itself adds neither dependency, so any compile failure caused by missing or mismatched versions belongs to your project, not the generator. Once the source compiles cleanly, run cargo fmt so the snippet matches your project style and the resulting diff is small when you copy it into the production crate.
A useful verification habit is to keep the generated source in a temporary module while you write a short sanity test that calls serde_json::from_str on a representative fixture and serde_json::to_string on the resulting struct. That round-trip is the strongest available check that the inferred types actually match what your data carries, and it surfaces problems the static read can hide — like a field that the API returns as a string on some paths and as an integer on others, where the generator's sample-based inference can only reflect one of those shapes.
Round-Tripping Real JSON With serde_json
To check the result after conversion in a behavioral sense, you need at least three fixtures: a success path with the canonical shape, a second success path that exercises an optional or renamed field, and a failure path that produces the kind of input you would not normally expect. Parse each one with serde_json::from_str::<YourStruct> and assert that the fields decode to the values you expect. Then re-serialize the structs with serde_json::to_string and confirm the output JSON matches the wire shape, paying particular attention to renamed fields and Option wrappers. If a field decodes as None when the API actually sends a value, the most likely cause is a serde rename attribute the static read missed, or a key collision the generator resolved with a numeric suffix you did not notice.
For an extra layer of confidence, run cargo clippy on the project and read the warnings — they often flag obvious inference gaps, such as a struct full of serde_json::Value fields that would benefit from a hand-written enum, or a numeric field where the inferred f64 should be a smaller integer type. Round-trip tests also help when fixtures come from different API versions: paste a fixture from each version into the converter and compare the generated sources side by side, because the divergence usually pinpoints the optional fields and rename rules you need to harden before adopting the generated types in production.
Limits of Single-Sample Inference
Inference from one sample cannot establish an API contract. A field present in your pasted JSON may be optional elsewhere, a small integer may require u64, i128, a fixed-point decimal, or a string in production, and a string may represent a UUID, a date, a URL, an enum, a borrowed slice, or a secret. Mixed objects that genuinely switch shape may deserve a tagged enum that the generator cannot invent from a single observation. The eight external fixtures locked into the tool cover String, i64, f64, bool, Vec, Option, null, and keyword rename syntax, and additional tests cover nested structs, normalized collisions, and invalid input — but they do not change the underlying rule that one sample is a starting draft, not a final contract.
Practical boundaries also matter for verification. Work is bounded at 500,000 input characters, 50,000 values, 40 nesting levels, and 1,000,000 output characters, and strict JSON is required, with comments, trailing commas, NaN, Infinity, BigInt syntax, undefined, and JavaScript object literals rejected. JSON numbers are parsed by JavaScript before inference, so unsafe integer spellings cannot be represented exactly and fall back away from i64 inference — keep precision-sensitive identifiers and decimals as strings in the source contract whenever the values cross a numeric boundary. The generator does not add crate paths, module visibility policy, lifetimes, generics, custom serializers, deny_unknown_fields, defaults, flattening, tagging, or validation, so any of those behaviours you need must be written by hand after the inferred source has been checked.
If you're weighing options, JSON to XML API Alternative: A Browser-Side Path covers this in detail.