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

相关推荐
麦麦麦当劳大王43 分钟前
【油猴脚本】Boss直聘 岗位职责复制
开发语言·前端·javascript
深蓝AI1 小时前
JWT 不是登录方案:三种登录态架构怎么选
后端
starzy19901 小时前
深入理解Scala Map:从实现原理到工程实践
开发语言·后端·scala
只睡四小时2 小时前
Three.js Sub-tick:漏判 60.7%→0
开发语言·javascript·性能优化·ecmascript·游戏开发·three.js·碰撞检测
卷无止境2 小时前
凌晨三点的绿灯
后端
码农小河662 小时前
基于SSM+JSP的高校专业信息管理系统【附源码+数据库】
java·开发语言·数据库
excel3 小时前
基于 GPT 的自动发布文章接口方案:从一句话或一张图片生成并发布内容
前端·javascript·后端
东鹏特饮4 小时前
系统运维(传统/通用运维)面试题
开发语言·php
风若飞4 小时前
Spring Ai 入门遇到问题1
java·后端·spring
今天要早睡_5 小时前
C++01_入门基础复习
开发语言·c++