serde_json (Rust): Dynamic JSON & Error Handling Guide

Max WellsMax WellsFounder of Rustify

TL;DR: serde_json is the standard crate for JSON in Rust; built on top of serde. Use serde_json::to_string(&value) to serialize, serde_json::from_str::<T>(json) to deserialize, and serde_json::Value for dynamic JSON without a known schema. Add #[derive(Serialize, Deserialize)] to your structs and they work with serde_json automatically. It is the most downloaded crate in the Rust ecosystem after serde itself.


How Do You Serialize a Struct to JSON?

Add #[derive(Serialize)] and call serde_json::to_string() or serde_json::to_string_pretty().

# Cargo.toml
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
use serde::{Serialize, Deserialize};
 
#[derive(Serialize, Deserialize, Debug)]
struct User {
    id: u64,
    name: String,
    email: String,
    active: bool,
}
 
fn main() {
    let user = User {
        id: 1,
        name: "Alice".to_string(),
        email: "[email protected]".to_string(),
        active: true,
    };
 
    // Compact JSON
    let json = serde_json::to_string(&user).unwrap();
    println!("{json}");
    // {"id":1,"name":"Alice","email":"[email protected]","active":true}
 
    // Pretty-printed JSON
    let pretty = serde_json::to_string_pretty(&user).unwrap();
    println!("{pretty}");
}

How Do You Deserialize JSON Into a Struct?

Use serde_json::from_str() with a type annotation; Rust infers which type to deserialize into.

use serde::Deserialize;
 
#[derive(Deserialize, Debug)]
struct Config {
    host: String,
    port: u16,
    debug: bool,
}
 
fn main() {
    let json = r#"{"host":"localhost","port":8080,"debug":true}"#;
 
    let config: Config = serde_json::from_str(json).unwrap();
    println!("{:?}", config);
    // Config { host: "localhost", port: 8080, debug: true }
 
    // From a file
    let file = std::fs::File::open("config.json").unwrap();
    let config: Config = serde_json::from_reader(file).unwrap();
}

How Do You Work With Dynamic JSON (Unknown Schema)?

Use serde_json::Value; an enum that represents any valid JSON value.

use serde_json::{Value, json};
 
fn main() {
    // Build JSON with the json! macro
    let payload = json!({
        "name": "Bob",
        "scores": [10, 20, 30],
        "meta": {
            "active": true,
            "tags": ["rust", "backend"]
        }
    });
 
    // Navigate the value
    println!("{}", payload["name"]);              // "Bob"
    println!("{}", payload["scores"][1]);         // 20
    println!("{}", payload["meta"]["active"]);    // true
 
    // Parse unknown JSON
    let raw = r#"{"status":"ok","count":42}"#;
    let v: Value = serde_json::from_str(raw).unwrap();
 
    if let Some(count) = v["count"].as_u64() {
        println!("count = {count}");
    }
}

How Do You Handle Serialization Errors?

serde_json functions return Result; handle errors with ? in fallible contexts.

use serde_json::Result;
 
fn serialize_user(user: &User) -> Result<String> {
    serde_json::to_string(user)
}
 
fn parse_config(json: &str) -> anyhow::Result<Config> {
    let config: Config = serde_json::from_str(json)
        .context("failed to parse config JSON")?;
    Ok(config)
}

Common errors:

  • from_str; invalid JSON syntax, wrong type for a field
  • to_string; rare; can fail if a type contains a non-string map key or a custom serializer errors

What Are the Key serde_json Functions?

FunctionInputOutput
to_string(&T)Any SerializeResult<String>
to_string_pretty(&T)Any SerializeResult<String>
to_vec(&T)Any SerializeResult<Vec<u8>>
to_writer(w, &T)Writer + SerializeResult<()>
from_str::<T>(s)&strResult<T>
from_slice::<T>(b)&[u8]Result<T>
from_reader::<T>(r)Read implResult<T>
from_value::<T>(v)ValueResult<T>
to_value(&T)Any SerializeResult<Value>

Frequently Asked Questions

Use #[serde(rename = "fieldName")] on struct fields or #[serde(rename_all = "camelCase")] on the struct:

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Response {
    user_id: u64,       // serialized as "userId"
    created_at: String, // serialized as "createdAt"
}

#[serde(skip_serializing_if = "Option::is_none")]
optional_field: Option<String>,

Value is dynamic; you can inspect or modify arbitrary JSON at runtime. Typed structs are validated at parse time and are safer for production code. Use Value for prototyping, proxying, or when the schema is genuinely unknown.

serde_json::to_string_pretty(&value) adds indentation. For custom indentation, use the PrettyFormatter directly via serde_json::ser::Serializer.


Sources


  • Serde: The serialization framework serde_json is built on
  • Struct: Structs are the primary target for JSON deserialization
  • Result: All serde_json operations return Result
  • Axum: Uses serde_json internally for Json<T> extractors
  • HashMap: JSON objects are often represented as map-like key-value structures
  • Reqwest: Reqwest and serde_json are the default pair for Rust HTTP APIs
  • Tauri: Tauri command payloads and frontend messages often flow through JSON values

Keep Reading

Ready to Land a $120k+ Rust Job in the US or Europe?