JSON 响应 #

JSON 响应概述 #

JSON 是现代 Web API 的标准数据格式。Actix Web 提供了多种方式来生成 JSON 响应。

text
┌─────────────────────────────────────────────────────────────┐
│                      JSON 响应方式                           │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. web::Json<T>                                             │
│     自动设置 Content-Type: application/json                  │
│                                                              │
│  2. HttpResponse::Ok().json()                                │
│     手动构建响应并序列化为 JSON                               │
│                                                              │
│  3. serde_json::json!()                                      │
│     快速构建 JSON 值                                          │
│                                                              │
│  4. 自定义 Responder                                         │
│     完全控制响应格式                                          │
│                                                              │
└─────────────────────────────────────────────────────────────┘

基本 JSON 响应 #

使用 web::Json #

rust
use actix_web::{web, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

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

#[actix_web::get("/user")]
async fn get_user() -> impl Responder {
    web::Json(User {
        id: 1,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    })
}

使用 HttpResponse::json #

rust
#[actix_web::get("/user/http")]
async fn get_user_http() -> impl Responder {
    HttpResponse::Ok().json(User {
        id: 1,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    })
}

使用 json! 宏 #

rust
#[actix_web::get("/json-macro")]
async fn json_macro() -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({
        "success": true,
        "data": {
            "id": 1,
            "name": "Alice"
        }
    }))
}

标准化 API 响应 #

统一响应格式 #

rust
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse<T> {
    success: bool,
    code: u16,
    message: String,
    data: Option<T>,
}

impl<T: Serialize> ApiResponse<T> {
    fn success(data: T) -> Self {
        Self {
            success: true,
            code: 200,
            message: "Success".to_string(),
            data: Some(data),
        }
    }
    
    fn error(code: u16, message: &str) -> ApiResponse<()> {
        ApiResponse {
            success: false,
            code,
            message: message.to_string(),
            data: None,
        }
    }
}

#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
    let user = User {
        id: path.into_inner(),
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    };
    
    HttpResponse::Ok().json(ApiResponse::success(user))
}

分页响应 #

rust
#[derive(Serialize)]
struct PaginatedResponse<T> {
    success: bool,
    data: Vec<T>,
    pagination: Pagination,
}

#[derive(Serialize)]
struct Pagination {
    page: u32,
    per_page: u32,
    total: u64,
    total_pages: u32,
}

#[actix_web::get("/users")]
async fn list_users(query: web::Query<PageQuery>) -> impl Responder {
    let page = query.page.unwrap_or(1);
    let per_page = query.per_page.unwrap_or(10);
    
    let users = vec![
        User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ];
    
    HttpResponse::Ok().json(PaginatedResponse {
        success: true,
        data: users,
        pagination: Pagination {
            page,
            per_page,
            total: 100,
            total_pages: 10,
        },
    })
}

#[derive(Deserialize)]
struct PageQuery {
    page: Option<u32>,
    per_page: Option<u32>,
}

Serde 序列化配置 #

重命名字段 #

rust
#[derive(Serialize)]
struct User {
    #[serde(rename = "userId")]
    id: u32,
    
    #[serde(rename = "userName")]
    name: String,
    
    #[serde(rename = "userEmail")]
    email: String,
}

跳过空值 #

rust
#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    email: Option<String>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    phone: Option<String>,
}

格式化日期 #

rust
use chrono::{DateTime, Utc};
use serde_with::{serde_as, TimestampSeconds};

#[serde_as]
#[derive(Serialize)]
struct Event {
    id: u32,
    name: String,
    
    #[serde_as(as = "TimestampSeconds<i64>")]
    created_at: DateTime<Utc>,
}

枚举序列化 #

rust
#[derive(Serialize)]
#[serde(tag = "type", content = "value")]
enum Status {
    Active,
    Inactive { reason: String },
    Pending,
}

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

自定义 JSON 响应 #

实现 Responder #

rust
use actix_web::{HttpRequest, HttpResponse, Responder};

struct JsonApiResponse<T> {
    data: T,
}

impl<T: Serialize> Responder for JsonApiResponse<T> {
    type Body = actix_web::body::BoxBody;

    fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
        HttpResponse::Ok()
            .content_type("application/vnd.api+json")
            .json(serde_json::json!({
                "data": self.data
            }))
            .map_into_boxed_body()
    }
}

#[actix_web::get("/api/user")]
async fn api_user() -> impl Responder {
    JsonApiResponse {
        data: User {
            id: 1,
            name: "Alice".to_string(),
            email: "alice@example.com".to_string(),
        },
    }
}

错误响应 #

rust
#[derive(Serialize)]
struct ErrorResponse {
    success: bool,
    error: ErrorDetail,
}

#[derive(Serialize)]
struct ErrorDetail {
    code: u16,
    message: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    details: Option<Vec<String>>,
}

impl ErrorResponse {
    fn bad_request(message: &str) -> Self {
        Self {
            success: false,
            error: ErrorDetail {
                code: 400,
                message: message.to_string(),
                details: None,
            },
        }
    }
    
    fn validation_error(errors: Vec<String>) -> Self {
        Self {
            success: false,
            error: ErrorDetail {
                code: 422,
                message: "Validation failed".to_string(),
                details: Some(errors),
            },
        }
    }
}

#[actix_web::post("/users")]
async fn create_user(body: web::Json<CreateUser>) -> impl Responder {
    if body.name.is_empty() {
        return HttpResponse::BadRequest()
            .json(ErrorResponse::validation_error(vec![
                "Name cannot be empty".to_string()
            ]));
    }
    
    HttpResponse::Created().json(ApiResponse::success(User {
        id: 1,
        name: body.name.clone(),
        email: body.email.clone(),
    }))
}

条件响应 #

根据请求头响应 #

rust
#[actix_web::get("/user/{id}")]
async fn get_user_conditional(
    path: web::Path<u32>,
    req: HttpRequest,
) -> impl Responder {
    let user = User {
        id: path.into_inner(),
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    };
    
    let accept = req.headers().get("Accept")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("application/json");
    
    if accept.contains("application/xml") {
        HttpResponse::Ok()
            .content_type("application/xml")
            .body(format!(
                r#"<?xml version="1.0"?><user><id>{}</id><name>{}</name></user>"#,
                user.id, user.name
            ))
    } else {
        HttpResponse::Ok().json(user)
    }
}

完整示例 #

rust
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};

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

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

#[derive(Deserialize)]
struct UpdateUser {
    name: Option<String>,
    email: Option<String>,
}

#[derive(Deserialize)]
struct ListQuery {
    page: Option<u32>,
    per_page: Option<u32>,
}

#[derive(Serialize)]
struct ApiResponse<T> {
    success: bool,
    data: T,
}

#[derive(Serialize)]
struct ListResponse<T> {
    success: bool,
    data: Vec<T>,
    page: u32,
    per_page: u32,
    total: u64,
}

#[derive(Serialize)]
struct ErrorResponse {
    success: bool,
    error: String,
}

#[actix_web::get("/users")]
async fn list_users(query: web::Query<ListQuery>) -> impl Responder {
    let page = query.page.unwrap_or(1);
    let per_page = query.per_page.unwrap_or(10);
    
    let users = vec![
        User { id: 1, name: "Alice".to_string(), email: "alice@example.com".to_string() },
        User { id: 2, name: "Bob".to_string(), email: "bob@example.com".to_string() },
    ];
    
    HttpResponse::Ok().json(ListResponse {
        success: true,
        data: users,
        page,
        per_page,
        total: 100,
    })
}

#[actix_web::get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> impl Responder {
    let id = path.into_inner();
    
    if id > 100 {
        return HttpResponse::NotFound().json(ErrorResponse {
            success: false,
            error: format!("User {} not found", id),
        });
    }
    
    HttpResponse::Ok().json(ApiResponse {
        success: true,
        data: User {
            id,
            name: "Alice".to_string(),
            email: "alice@example.com".to_string(),
        },
    })
}

#[actix_web::post("/users")]
async fn create_user(body: web::Json<CreateUser>) -> impl Responder {
    if body.name.is_empty() {
        return HttpResponse::BadRequest().json(ErrorResponse {
            success: false,
            error: "Name cannot be empty".to_string(),
        });
    }
    
    HttpResponse::Created().json(ApiResponse {
        success: true,
        data: User {
            id: 1,
            name: body.name.clone(),
            email: body.email.clone(),
        },
    })
}

#[actix_web::put("/users/{id}")]
async fn update_user(
    path: web::Path<u32>,
    body: web::Json<UpdateUser>,
) -> impl Responder {
    HttpResponse::Ok().json(ApiResponse {
        success: true,
        data: User {
            id: path.into_inner(),
            name: body.name.clone().unwrap_or_default(),
            email: body.email.clone().unwrap_or_default(),
        },
    })
}

#[actix_web::delete("/users/{id}")]
async fn delete_user(path: web::Path<u32>) -> impl Responder {
    HttpResponse::Ok().json(ApiResponse {
        success: true,
        data: serde_json::json!({ "deleted": path.into_inner() }),
    })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(list_users)
            .service(get_user)
            .service(create_user)
            .service(update_user)
            .service(delete_user)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

下一步 #

现在你已经掌握了 JSON 响应,继续学习 模板渲染,深入了解模板引擎!

最后更新:2026-03-29