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

相关推荐
君顾16 小时前
智慧零售实战指南:从技术架构到落地方案全解析
java·开发语言·零售
南棱笑笑生6 小时前
20260904实测给飞凌OK3576-C开发板刷入Rockchip原厂的IMG固件【使用飞凌的DTS】切换串口波特率为1.5Mbps
c语言·开发语言·rockchip
平头哥技术团队7 小时前
Day 10 | 工欲善其事:VS Code 配置与项目归档
android·开发语言·前端·javascript·html·交互
乌萨奇也要立志学C++7 小时前
【洛谷】kmp算法
开发语言·算法
传奇开心果编程7 小时前
【Rust入门知识点学与练】第4课:条件判断 if / else
开发语言·学习·rust
m0_380743877 小时前
给 Claude API 调用补上超时重试和错误分类
开发语言·人工智能·php
Draw Stars7 小时前
Git BASH安装教程
开发语言·git·bash
步行cgn7 小时前
YAML 的语法规则
spring boot·后端
vortex57 小时前
一文讲透 Zsh 与 Bash 的区别
开发语言·bash
千谦阙听8 小时前
C++类和对象(中):默认成员函数、构造与析构、拷贝构造、运算符重载
开发语言·c++·学习