很多人从 FastAPI、NestJS、Spring 那边转来,容易误解 Axum 的 Json<T>。
要记住:
Json<T> 的核心工作,是把请求体按 serde::Deserialize 反序列化成 T。
它很强,但它不是“自动帮你做完所有业务校验”。
看一个标准例子:
use axum::{
extract::{State, Path},
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
#[derive(Clone, Default)]
struct AppState {
permits: Arc<RwLock<HashMap<u64, Permit>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Permit {
id: u64,
owner: String,
zone: String,
status: String,
}
#[derive(Debug, Deserialize)]
struct CreatePermit {
owner: String,
zone: String,
}
#[derive(Debug)]
enum ApiError {
NotFound,
BadRequest(&'static str),
}
impl IntoResponse for ApiError {
fn into_response(self) -> Response {
let (status, message) = match self {
Self::NotFound => (StatusCode::NOT_FOUND, "报关单不存在"),
Self::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
};
(
status,
Json(serde_json::json!({
"error": message
})),
)
.into_response()
}
}
async fn create_permit(
State(state): State<AppState>,
Json(input): Json<CreatePermit>,
) -> Result<(StatusCode, Json<Permit>), ApiError> {
if input.owner.trim().len() < 2 {
return Err(ApiError::BadRequest("owner 太短"));
}
let mut permits = state.permits.write().await;
let id = permits.len() as u64 + 1;
let permit = Permit {
id,
owner: input.owner,
zone: input.zone,
status: "pending".to_owned(),
};
permits.insert(id, permit.clone());
Ok((StatusCode::CREATED, Json(permit)))
}这段代码里要看清三层:
Json<CreatePermit>负责把申报单读出来- 业务语义校验由你自己决定怎么做
- 最终放行回执由
IntoResponse体系兜底
所以 Axum 和某些“强声明即强校验即强文档”的框架不同:
Axum 默认提供:
- 请求提取
- 反序列化
- 拒绝机制
- 响应转换
Axum 默认不直接替你提供:
- 业务规则校验体系
- OpenAPI 自动全家桶
- ORM
- DI 容器这不是它弱,而是它的哲学:
海关负责验格式,不替你定义整部国家商检法。