Practical guide

Generate Rust structs from JSON

Rust generation from JSON is valuable when you need a first struct draft and want field names, Option, nested arrays, and Serde attributes in front of you quickly. JSON All-in-One can infer Rust models from a local sample and generate plain or Serde-oriented output.

A generated struct is not the same as a working crate. Keep the generated code, the required Cargo.toml choices, and a fixture deserialization check as separate, visible steps.

Use a config/event sample with Rust edges

This webhook configuration includes snake_case keys, nested arrays, a nullable secret reference, and a nested rules object. It gives a Rust model enough texture to discuss Option, Serde output, serde_json::Value for unknown or null-only JSON values, and dependency features.

Use a small sample for the first decoding test. Compare the decoded field values with the original JSON, then add examples with missing fields, nulls, and unfamiliar values.

Illustrative root-object webhook config JSONjson
{
  "endpoint_id": "wh_01",
  "enabled": true,
  "delivery_modes": ["batch", "realtime"],
  "secret_ref": null,
  "retry_policy": { "max_attempts": 5, "backoff_seconds": [1, 5, 30] },
  "rules": { "kind": "prefix", "value": "/api/" }
}

Compare plain and Serde output

Rust exposes serde and None serializer options. Use Serde output when your next step is deserialization from JSON. Plain structs can still be useful for review, but they do not carry rename/derive behavior on their own.

The following excerpt shows the shape to look for. If your generated file uses different names, derives, or attributes, keep the generated code as the source of truth and adjust the integration example around it.

Illustrative Serde-oriented Rust model for the root objectrust
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WebhookConfig {
    pub delivery_modes: Vec<String>,
    pub enabled: bool,
    pub endpoint_id: String,
    pub retry_policy: RetryPolicy,
    pub rules: Rules,
    pub secret_ref: Option<serde_json::Value>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RetryPolicy {
    pub backoff_seconds: Vec<i64>,
    pub max_attempts: i64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Rules {
    pub kind: String,
    pub value: String,
}

Cargo dependencies are part of the example

Serde derives and serde_json::Value require crate dependencies. If the generated package emits a support file or Cargo.toml excerpt, keep it with the model. If you write the Cargo setup by hand, treat it as integration glue rather than generated output.

A minimal path can use serde plus serde_json first. Only add num-bigint, bigdecimal, or chrono-style examples after the selected primitive policies and crate features work in your crate.

Handwritten Cargo.toml excerpt to verifytoml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Handwritten Rust deserialization checkrust
let config: WebhookConfig = serde_json::from_str(json_text)?;
assert!(config.enabled);
assert_eq!(config.retry_policy.backoff_seconds.len(), 3);

Review Option, values, and numbers

Option<T> is the most important visible cue in a Rust model generated from JSON. It may represent a null value, a missing value, or a shape that varied across observed samples depending on the generated output. Review the specific fixture before treating optionality as a complete API rule.

The Rust target represents date/time values as strings. Integer policy can use i64, BigInt, or String; decimal can use f64 or bigdecimal::BigDecimal; unknown JSON can be serde_json::Value with Serde or String otherwise. Check dependencies and deserialization for each non-native choice.

ConcernRust review pointCheck
JSON key renameSerde rename attributeCheck the generated attributes.
Null secret referenceOption<String>Deserialize a fixture with null.
Null-only or unknown JSON valueserde_json::Value or StringShow the selected serializer and JSON-value policy.
Big numbersi64, BigInt, or StringCheck dependencies and parsing.

Workflow checklist

  1. Generate Rust from a sample with nested objects, arrays, nulls, and renamed keys.
  2. Choose Serde output if your application uses Serde, then add the dependencies and feature flags required by the generated types.
  3. Review Option, serde_json::Value, integer, and decimal policy.
  4. Copy the generated files into a scratch crate.
  5. Run cargo check and a fixture deserialize check before relying on the model.

Limitations to keep visible

cargo check proves the generated code type-checks with the chosen dependencies. It does not prove the fixture deserializes. Keep both checks separate when both are part of your workflow.

Not every crate and policy combination works without additional features or manual glue. Verify the Cargo dependencies and feature flags that match the primitive policies you selected before relying on the generated output.

Generated structs and Serde responsibilities

Generate Rust structs from a 600-event billing envelope, inspect Serde derives and Option fields, then compile and deserialize the exact generated crate with Cargo and serde_json.
Diagram showing generated Rust and Serde models between billing event JSON and handwritten Cargo and application policy.
Architecture diagram separating generated Rust structs, enums, Option fields, and Serde derives from handwritten Cargo features, validation, error handling, and schema-evolution policy.