Free & open source — no account required

v1.2.5-PRICING-19
Web & Frontend • Engineering Documentation

Rust Enum Generation Mastery

This technical guide provides an in-depth analysis of the json to rust enum engine, best practices for implementation, and data security standards.

JSON to Rust Enum: Tagged Unions, Discriminated Variants, and Serde

When your API returns different object shapes on the same endpoint — a type field that determines the rest of the structure — you need a Rust enum, not a struct. Serde handles this with three representation modes: #[serde(tag)] for internally tagged unions, #[serde(tag, content)] for adjacently tagged, and #[serde(untagged)] for shape-based inference. The generator outputs a struct from your JSON sample — this page shows how to evolve that into the correct enum representation for your API's discriminant pattern.

Live Example: API Response to Rust Struct

// Input JSON
{
  "event_id": "evt_5521",
  "event_type": "purchase",
  "user_id": "usr_4421",
  "amount": 149.99,
  "currency": "USD",
  "processed": true,
  "metadata": null
}

// Generated Rust Struct
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Root {
    pub event_id: String,
    pub event_type: String,
    pub user_id: String,
    pub amount: f64,
    pub currency: String,
    pub processed: bool,
    pub metadata: Option<serde_json::Value>,
}

Refining the Generated Struct

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PaymentEvent {
    pub event_id: String,
    pub event_type: String,

    #[serde(rename = "user_id")]
    pub user_id: String,

    pub amount: f64,
    pub currency: String,
    pub processed: bool,

    // Option<T> maps null → None, missing key → None
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<serde_json::Value>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let json = r#"{
        "event_id": "evt_5521",
        "event_type": "purchase",
        "user_id": "usr_4421",
        "amount": 149.99,
        "currency": "USD",
        "processed": true,
        "metadata": null
    }"#;

    // Deserialize
    let event: PaymentEvent = serde_json::from_str(json)?;
    println!("{}: {} {}", event.event_type, event.currency, event.amount);

    // Serialize back to JSON
    let output = serde_json::to_string_pretty(&event)?;
    println!("{}", output);

    Ok(())
}

When You Actually Need a Rust Enum: Tagged Unions

If your API returns different object shapes under one key (a discriminated union), you need a Rust enum with serde's #[serde(tag)]:

use serde::{Serialize, Deserialize};

// JSON with a "type" discriminant field:
// {"type": "purchase", "amount": 149.99, "currency": "USD"}
// {"type": "refund", "original_id": "evt_5521", "reason": "customer_request"}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PaymentEvent {
    Purchase {
        amount: f64,
        currency: String,
    },
    Refund {
        original_id: String,
        reason: String,
    },
    Chargeback {
        original_id: String,
        dispute_id: String,
    },
}

// Serde reads the "type" field and picks the right variant
let event: PaymentEvent = serde_json::from_str(json)?;
match event {
    PaymentEvent::Purchase { amount, currency } => {
        println!("Purchase: {} {}", currency, amount);
    }
    PaymentEvent::Refund { original_id, reason } => {
        println!("Refund of {} — reason: {}", original_id, reason);
    }
    PaymentEvent::Chargeback { .. } => {
        println!("Chargeback dispute");
    }
}

Common Serde Attributes

#[derive(Serialize, Deserialize, Debug)]
pub struct UserConfig {
    // Map JSON "user_name" to Rust field "username"
    #[serde(rename = "user_name")]
    pub username: String,

    // Skip field when serializing if it's None
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bio: Option<String>,

    // Use a default value if the key is missing from JSON
    #[serde(default = "default_role")]
    pub role: String,

    // Skip this field entirely (neither serialize nor deserialize)
    #[serde(skip)]
    pub internal_state: u32,

    // Rename all fields: snake_case JSON <-> camelCase Rust (or vice versa)
    // Put at the struct level: #[serde(rename_all = "camelCase")]
}

fn default_role() -> String { "viewer".to_string() }

Frequently Asked Questions

What's Option<T> vs a required field? Required fields are plain T — serde returns an error if the key is missing. Option<T> accepts both a missing key and an explicit null value, mapping both to None. If you need to distinguish "missing key" from "explicitly null", use serde_with::NoneAsEmptyString or a custom deserializer.

What Cargo dependencies do I need? Add to Cargo.toml: serde = { version = "1", features = ["derive"] } and serde_json = "1". The derive feature enables the #[derive(Serialize, Deserialize)] macros.

Why does the generator use f64 for all numbers? JSON numbers are untyped. f64 is the safe default. Change to u32, i64, usize, etc. for fields you know are integers. Serde will return an error if a JSON float (e.g., 3.14) is parsed into an integer type.

Is my JSON sent to a server? No. TypeMorph runs entirely in your browser — none of your data leaves your machine.

Developer FAQ

Is the processing local-only?

Absolutely. TypeMorph operates entirely within your browser's sandbox. We use Web Workers for high-performance computation without ever transmitting your JSON, SQL, or API data to a remote server.

Can I use this for enterprise projects?

Yes. The tool is designed for professional software engineers who require GDPR compliance and data privacy. It is trusted by developers at top-tier startups and financial institutions.