JSON with serde_json
Parse and emit JSON with typed structs, dynamic Value, and streaming for large payloads.
Search across all documentation pages
Parse and emit JSON with typed structs, dynamic Value, and streaming for large payloads.
Quick-reference recipe card - copy-paste ready.
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
#[derive(Serialize, Deserialize)]
struct Event { kind: String, payload: Value }
let raw = r#"{"kind":"click","payload":{"x":1}}"#;
let ev: Event = serde_json::from_str(raw)?;
let dynamic = json!({ "ok": true, "count": ev.payload["x"] });
let out = serde_json::to_string(&dynamic)?;When to reach for this: HTTP APIs, config files, and logs overwhelmingly use JSON in Rust services.
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: u64,
email: String,
#[serde(default)]
tags: Vec<String>,
}
fn main() -> serde_json::Result<()> {
let u = User { id: 1, email: "a@b.co".into(), tags: vec![] };
let compact = serde_json::to_string(&u)?;
let pretty = serde_json::to_string_pretty(&u)?;
let parsed: User = serde_json::from_str(&compact)?;
assert_eq!(parsed.email, "a@b.co");
let mut v: Value = serde_json::from_str(r#"{"meta":{"version":1}}"#)?;
v["meta"]["source"] = json!("api");
assert_eq!(v["meta"]["version"], 1);
Ok(())
}What this demonstrates:
to_string vs to_string_pretty for wire vs debug output.from_str into strongly typed User.Value for partially structured or evolving JSON.json! macro for literal construction.serde_json implements Serde's Serializer and Deserializer for JSON.Value defers schema enforcement until you index fields.| Function | Use |
|---|---|
from_str / from_slice | Parse to T |
to_string / to_vec | Serialize T |
from_value / to_value | Convert Value ↔ T |
json! | Build Value literals |
// Fallible field access on Value
let n = v.get("count").and_then(|x| x.as_u64()).unwrap_or(0);serde_json::Error or map to 400/422.Value allocates entire document. Fix: serde_json::Deserializer::from_reader with stream types.Value; typed structs error on duplicates by default. Fix: Know your producer behavior.| Alternative | Use When | Don't Use When |
|---|---|---|
simd-json | Maximum parse throughput | Portable builds without SIMD |
sonic-rs | High performance JSON | Ecosystem maturity matters most |
miniserde | Minimal dependency binary | Complex derive needs |
Compact for wire; pretty for logs and fixtures only.
from_slice on &[u8] or from_reader on impl Read.
#[serde(deny_unknown_fields)] on structs for strict APIs.
Manipulate Value or deserialize to HashMap<String, Value>.
Read line by line with BufRead::lines and from_str per line.
serde_json::raw::RawValue preserves sub-tree as bytes.
Json<T> extractor uses serde_json under the hood.
Not standard JSON; serde_json rejects non-finite floats by default.
Use criterion with representative payload sizes.
Add jsonschema crate after serde parse for contract checks.
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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026