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_casemovie_id),而前端 JavaScript / JSON 规范用 camelCasemovieId)。#[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 惯例。

相关推荐
步行cgn7 分钟前
MyBatis <where> 标签详解:让你的动态 SQL 更优雅、更安全
后端
灵析表格8 分钟前
灵析表格财务函数深度实用性分析与实操教程
开发语言·ai·json·excel·wps
RainCity17 分钟前
Java Swing 自定义组件库分享(十六)
java·笔记·后端
沙盘客17 分钟前
AFSIM 14篇 C++ 插件开发:扩展你的仿真能力
c++·后端
桦说编程18 分钟前
如何对待中断异常:一个被吞掉的 InterruptedException 引发的思考
后端
LONGZETECH39 分钟前
无人机组装调试实训高损耗痛点分析与虚拟仿真教学落地方案
c语言·开发语言·架构·无人机
菜冻鱼1 小时前
Python-sklearn-降维
开发语言·人工智能·python·机器学习·支持向量机·sklearn
luj_17681 小时前
大航海时代:沉浸式财商实战沙盒
c语言·开发语言·网络·经验分享·算法
赵广陆2 小时前
企业实战:Markdown图片检索
android·java·开发语言
C++、Java和Python的菜鸟2 小时前
第3章 从0开始用若依
java·开发语言