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

相关推荐
前鼻音太阳熊1 小时前
【MES系统】- 工业HMI状态机可视化实践:从手写SVG到Cytoscape.js的技术选型与踩坑
开发语言·javascript·ecmascript
旺仔学长 哈哈1 小时前
基于SpringBoot的在线招聘测评系统的设计与实现----附源码35253+数据库文档
数据库·spring boot·后端·在线招聘
阿米亚波1 小时前
【C++ STL】std::array
java·开发语言·c++
wuyk5552 小时前
66.嵌入式C语言进阶:共用体(Union)实战指南——单片机内存省一半的神级技巧
c语言·开发语言·stm32·单片机·嵌入式硬件
怪奇云呼军2 小时前
闪电智能 Voice Agent 怎样根据语速、停顿和追问方式切换话术?策略引擎拆解
开发语言·人工智能·网络协议·算法·语音识别·web app
卷无止境2 小时前
Python的魔术方法:那些藏在双下划线背后的魔法
后端·python
Ivanqhz2 小时前
Rust parse() 浅析
开发语言·后端·rust
卷无止境2 小时前
别再被"乱码"吓到了:Python文件操作的门道
后端·python
神仙别闹2 小时前
基于C++实现(控制台)景区旅游管理系统
开发语言·c++·旅游