DEV Community

arenasbob2024-cell
arenasbob2024-cell

Posted on • Originally published at viadreams.cc

JSON to Rust Struct: Complete Guide with serde_json and serde Derive

Convert JSON to Rust structs using serde_json. Here's everything Rust developers need.

Cargo.toml Setup

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Enter fullscreen mode Exit fullscreen mode

Basic Struct

use serde::{Serialize, Deserialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    email: String,
    active: bool,
}

// Deserialize
let user: User = serde_json::from_str(json_str)?;

// Serialize
let json = serde_json::to_string(&user)?;
let pretty = serde_json::to_string_pretty(&user)?;
Enter fullscreen mode Exit fullscreen mode

Field Renaming

use serde::{Serialize, Deserialize};

// camelCase JSON → snake_case Rust
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ApiResponse {
    user_id: u32,        // maps to "userId"
    first_name: String,  // maps to "firstName"
    avatar_url: String,  // maps to "avatarUrl"
}

// Individual field rename
#[derive(Serialize, Deserialize)]
struct Product {
    #[serde(rename = "product_id")]
    id: u32,
    name: String,
}
Enter fullscreen mode Exit fullscreen mode

Option for Nullable Fields

#[derive(Serialize, Deserialize)]
struct User {
    id: u32,
    name: String,
    bio: Option<String>,          // null → None

    #[serde(skip_serializing_if = "Option::is_none")]
    avatar_url: Option<String>,   // omit from JSON if None
}

// Deserialize: null or missing → None
let user: User = serde_json::from_str(r#"{"id":1,"name":"Alice","bio":null}"#)?;
assert_eq!(user.bio, None);
Enter fullscreen mode Exit fullscreen mode

Nested Structs and Vec

#[derive(Serialize, Deserialize)]
struct Order {
    order_id: String,
    customer: Customer,     // nested struct
    items: Vec<OrderItem>,  // array of structs
    tags: Vec<String>,      // array of strings
}

// Deserialize list
let orders: Vec<Order> = serde_json::from_str(json_array)?;
Enter fullscreen mode Exit fullscreen mode

Enums

// External tagging (default): {"Circle": {"radius": 1.0}}
#[derive(Serialize, Deserialize)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

// Internal tagging: {"type": "circle", "radius": 1.0}
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Event {
    Click { x: i32, y: i32 },
    Scroll { delta: f32 },
}

// Adjacent tagging: {"t": "circle", "c": {"radius": 1.0}}
#[derive(Serialize, Deserialize)]
#[serde(tag = "t", content = "c")]
enum Shape2 {
    Circle { radius: f64 },
}
Enter fullscreen mode Exit fullscreen mode

Default Values

#[derive(Serialize, Deserialize)]
struct Config {
    #[serde(default = "default_timeout")]
    timeout: u32,           // use function if field missing

    #[serde(default)]
    enabled: bool,          // use Default::default() (false)

    #[serde(default)]
    tags: Vec<String>,      // empty Vec if missing
}

fn default_timeout() -> u32 { 30 }
Enter fullscreen mode Exit fullscreen mode

Error Handling

use serde_json::Error;

fn parse_user(json: &str) -> Result<User, String> {
    serde_json::from_str(json).map_err(|e| {
        match e.classify() {
            serde_json::error::Category::Syntax => format!("Invalid JSON: {}", e),
            serde_json::error::Category::Data => format!("Type mismatch: {}", e),
            serde_json::error::Category::Eof => "Unexpected end of input".to_string(),
            _ => format!("Parse error: {}", e),
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

Axum Integration

use axum::{extract::Json, routing::post, Router};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct UserResponse {
    id: u32,
    name: String,
}

async fn create_user(Json(payload): Json<CreateUser>) -> Json<UserResponse> {
    Json(UserResponse { id: 1, name: payload.name })
}

let app = Router::new().route("/users", post(create_user));
Enter fullscreen mode Exit fullscreen mode

Quick Tool

For automatic Rust struct generation from JSON, use DevToolBox JSON to Rust converter — paste JSON, get Rust struct with serde derive macros instantly.


Generate Rust structs from JSON instantly with DevToolBox's free JSON to Rust tool.

Top comments (0)