Rust #[derive(Serialize)]浅析

代码

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 惯例。

相关推荐
子兮曰4 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰4 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
小羊没烦恼!4 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
爱勇宝4 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
胡写代码4 天前
别再前后端各写一套表单校验了
java·后端
伞伞悦读4 天前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
大勇前进4 天前
原生 PHP 还是 Laravel?小项目到底要不要上框架
后端
yuzhi_liu4 天前
我用 LangGraph4j 实现 Multi-Agent Supervisor
后端
alsmile4 天前
Node-RED 之外,国产规则引擎的新方案:基于标准语法,Go 先行实现
后端·开源·go
大白804 天前
PHP 内存溢出排查思路:看懂报错日志,精准定位问题
后端