Enums & Tagging
Control how Rust enums map to JSON with serde's tagging attributes - critical for polymorphic APIs and event streams.
Busca en todas las páginas de la documentación
Control how Rust enums map to JSON with serde's tagging attributes - critical for polymorphic APIs and event streams.
Quick-reference recipe card - copy-paste ready.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
enum Event {
Click { x: i32, y: i32 },
KeyPress { key: String },
}
// {"type":"Click","data":{"x":1,"y":2}}When to reach for this: APIs carry variant-discriminated messages that JavaScript or other languages must parse reliably.
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
enum Message {
Text { body: String },
Image { url: String, width: u32 },
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
enum LooseId {
Number(u64),
Text(String),
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(tag = "kind", content = "value")]
enum Payment {
Card { last4: String },
Bank { routing: String },
}
fn main() {
let m = Message::Text { body: "hi".into() };
let json = serde_json::to_string(&m).unwrap();
assert!(json.contains(r#""type":"Text""#));
let id_num: LooseId = serde_json::from_str("42").unwrap();
let id_str: LooseId = serde_json::from_str(r#""abc""#).unwrap();
assert!(matches!(id_num, LooseId::Number(42)));
let p = Payment::Card { last4: "4242".into() };
let pjson = serde_json::to_string(&p).unwrap();
assert!(pjson.contains(r#""kind":"Card""#));
}What this demonstrates:
tag = "type"): variant name as discriminator field.tag + content): separates kind and payload objects.| Style | Attribute | JSON shape |
|---|---|---|
| External (default) | none | {"Text":{"body":"hi"}} |
| Internal | tag = "type" | {"type":"Text","body":"hi"} |
| Adjacent | tag + content | {"type":"Text","data":{"body":"hi"}} |
| Untagged | untagged | No wrapper - shape implies variant |
| Flattened | tag + content + flatten fields | Variant-specific |
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "event")]
enum Audit { UserCreated { id: u64 }, UserDeleted { id: u64 } }#[serde(rename = "legacy_name")] for compatibility.| Alternative | Use When | Don't Use When |
|---|---|---|
type field + struct | Manual polymorphism | Many variants with shared tagging rules |
serde_json::Value | Fully dynamic events | You need compile-time safety |
| Protobuf oneof | gRPC services | REST JSON APIs |
Internal or adjacent tags are most JavaScript-friendly.
#[serde(tag = "t", skip)] advanced - see serde enum docs.
Combine flatten on struct field holding enum.
Use #[serde(other)] on fallback variant (serde 1.0+).
Apply on struct variants individually.
rename_all on enum container for constant-style names.
Test every variant JSON snapshot in CI.
Maps to internally/adjacently tagged enums with code generators.
Wrap in Envelope { v: u32, event: Event }.
Tagging attributes affect MessagePack layout too - test per format.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Revisado por Chris St. John·Última actualización: 16 jul 2026