代码
rust
/// JSON 序列化输出结构(驼峰命名)
#[derive(Serialize)]
pub struct MovieJson {
#[serde(rename = "movieId")]
movie_id: i32,
title: String,
#[serde(rename = "releaseYear")]
release_year: i32,
#[serde(rename = "imdbId")]
imdb_id: String,
#[serde(rename = "tmdbId")]
tmdb_id: String,
genres: Vec<String>,
#[serde(rename = "ratingNumber")]
rating_number: i32,
#[serde(rename = "averageRating")]
average_rating: f64,
#[serde(rename = "topRatings")]
top_ratings: Vec<RatingWrapper>,
}
rust
/// 转换为 JSON 输出结构
pub fn to_json(&self) -> MovieJson {
MovieJson {
movie_id: self.movie_id,
title: self.title.clone(),
release_year: self.release_year,
imdb_id: self.imdb_id.clone(),
tmdb_id: self.tmdb_id.clone(),
genres: self.genres.clone(),
rating_number: self.rating_number,
average_rating: self.average_rating,
top_ratings: self.top_ratings.iter().map(|r| r.into()).collect(),
}
}
一、#[derive(Serialize)] --- 自动生成序列化代码
rust
#[derive(Serialize)] // ← 这个宏告诉编译器:"请为 MovieJson 自动生成 to_json 的代码"
pub struct MovieJson { ... }
它做了什么?
derive 宏在编译期 展开,自动为 MovieJson 实现 serde::Serialize trait。你不需要手写如何把每个字段转成 JSON 字符串,serde 自动完成。
手写 vs 自动生成的对比
rust
// ========== 如果没有 #[derive(Serialize)],你需要手写 ==========
impl serde::Serialize for MovieJson {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut state = serializer.serialize_struct("MovieJson", 9)?;
state.serialize_field("movieId", &self.movie_id)?;
state.serialize_field("title", &self.title)?;
state.serialize_field("releaseYear", &self.release_year)?;
state.serialize_field("imdbId", &self.imdb_id)?;
state.serialize_field("tmdbId", &self.tmdb_id)?;
state.serialize_field("genres", &self.genres)?;
state.serialize_field("ratingNumber", &self.rating_number)?;
state.serialize_field("averageRating", &self.average_rating)?;
state.serialize_field("topRatings", &self.top_ratings)?;
state.end()
}
}
// ========== 有了 #[derive(Serialize)],上面所有代码自动生成 ==========
// 你只需要写:
#[derive(Serialize)]
pub struct MovieJson { ... }
二、#[serde(rename = "xxx")] --- 序列化时重命名字段
rust
#[serde(rename = "movieId")] // ← JSON 中叫 "movieId"
movie_id: i32, // ← Rust 中叫 movie_id(snake_case 蛇形命名)
为什么需要它?
Rust 命名规范用 snake_case (movie_id),而前端 JavaScript / JSON 规范用 camelCase (movieId)。#[serde(rename)] 在两者之间做映射。
三、详细例子
假设数据库中有一部电影:
| 字段 | Rust 内部值 |
|---|---|
movie_id |
1 |
title |
"Toy Story" |
release_year |
1995 |
imdb_id |
"tt0114709" |
tmdb_id |
"862" |
genres |
["Animation", "Adventure", "Comedy"] |
rating_number |
215 |
average_rating |
3.92 |
top_ratings |
[{user_id: 15, score: 5.0, timestamp: 964982703}, ...] |
Rust 代码调用:
rust
let movie = dm.get_movie_by_id(1).unwrap();
let json_value = movie.to_json(); // 返回 MovieJson 结构体
// actix-web 自动调用 serde 序列化:
HttpResponse::Ok().json(json_value) // ← 这里触发 #[derive(Serialize)]
序列化流程(编译期已生成好代码):
Rust 结构体字段 #[serde(rename)] 映射 JSON 输出字段
───────────────── ───────────────────── ─────────────────
movie_id: 1 → "movieId" → "movieId": 1
title: "Toy Story" → (无rename,同名) → "title": "Toy Story"
release_year: 1995 → "releaseYear" → "releaseYear": 1995
imdb_id: "tt0114709" → "imdbId" → "imdbId": "tt0114709"
tmdb_id: "862" → "tmdbId" → "tmdbId": "862"
genres: [...] → (无rename) → "genres": ["Animation","Adventure","Comedy"]
rating_number: 215 → "ratingNumber" → "ratingNumber": 215
average_rating: 3.92 → "averageRating" → "averageRating": 3.92
top_ratings: [...] → "topRatings" → "topRatings": [{...}]
最终浏览器收到的 JSON:
json
{
"movieId": 1,
"title": "Toy Story",
"releaseYear": 1995,
"imdbId": "tt0114709",
"tmdbId": "862",
"genres": ["Animation", "Adventure", "Comedy"],
"ratingNumber": 215,
"averageRating": 3.92,
"topRatings": [
{
"userId": 15,
"score": 5.0,
"timestamp": 964982703
}
]
}
四、对比总结
| 概念 | 作用 | 类比 |
|---|---|---|
#[derive(Serialize)] |
自动生成"结构体→JSON字符串"的转换逻辑 | 就像给一台机器装上自动打包流水线 |
#[serde(rename = "xxx")] |
序列化时把 Rust 字段名换一个名字 | 就像出口商品换一个外文标签 |
完整工作流:
┌──────────────┐ #[derive(Serialize)] ┌──────────────┐
│ MovieJson │ ─────────────────────────────→ │ JSON 字符串 │
│ (Rust 结构体) │ #[serde(rename)] 改名 │ (camelCase) │
└──────────────┘ └──────────────┘
↑ ↓
movie.to_json() HttpResponse::Ok().json(...)
↓
前端 JavaScript 收到
res.movieId // ← 驼峰命名
res.releaseYear
没有 #[derive(Serialize)],HttpResponse::Ok().json() 根本不知道如何把 MovieJson 转成 JSON。没有 #[serde(rename)],前端收到的字段会是 movie_id(蛇形),不符合 JavaScript 惯例。