如何用Serde基于game字段反序列化含相同结构变体的枚举向量?
问题描述
当前的Rust代码结构如下(使用serde反序列化JSON):
#[derive(Deserialize)] struct GameData { game: String, player_data: Vec<MarioPartyData>, // 更多公共字段 } #[derive(Deserialize)] #[serde(untagged)] enum MarioPartyData { MarioParty(MarioParty), MarioParty2(MarioParty2), MarioParty3(MarioParty3), // 更多变体 } #[derive(Deserialize)] struct MarioParty { stars: i32, coins: i32, spaces_walked: i32 } #[derive(Deserialize)] struct MarioParty2 { stars: i32, coins: i32 } #[derive(Deserialize)] struct MarioParty3 { stars: i32, coins: i32 }
存在两个问题:
- 部分游戏的数据结构完全一致(比如
MarioParty2和MarioParty3),反序列化时会优先匹配到定义在前的变体(比如MarioParty3的JSON会被解析成MarioParty2)。 - JSON中
game字段的值是带空格的格式(比如"Mario Party 3"),需要对应到Rust中的变体名称。
合法的MarioParty3 JSON输入示例:
{ "game": "Mario Party 3", "player_data": [ { "stars": 3, "coins": 45 } ] }
希望避免自行实现反序列化器,可修改Rust结构,但JSON格式固定。
解决方案
可以通过调整结构,利用serde的标签化枚举特性来解决这两个问题:
1. 重构核心结构
把原来的GameData和MarioPartyData合并为一个带标签的枚举,用game字段作为标签,同时将公共字段抽离到单独的结构体中:
use serde::Deserialize; // 公共数据结构体,包含所有游戏共享的字段 #[derive(Deserialize)] struct CommonGameData { // 这里放所有游戏都有的公共字段 } #[derive(Deserialize)] #[serde(tag = "game")] // 指定JSON中的"game"字段作为枚举的标签 enum GameData { #[serde(rename = "Mario Party")] // 对应JSON中的"game"值 MarioParty { #[serde(flatten)] // 嵌入公共字段 common: CommonGameData, player_data: Vec<MarioParty>, }, #[serde(rename = "Mario Party 2")] MarioParty2 { #[serde(flatten)] common: CommonGameData, player_data: Vec<MarioParty2>, }, #[serde(rename = "Mario Party 3")] MarioParty3 { #[serde(flatten)] common: CommonGameData, player_data: Vec<MarioParty3>, }, // 更多游戏变体... } #[derive(Deserialize)] struct MarioParty { stars: i32, coins: i32, spaces_walked: i32 } #[derive(Deserialize)] struct MarioParty2 { stars: i32, coins: i32 } #[derive(Deserialize)] struct MarioParty3 { stars: i32, coins: i32 }
2. 方案说明
- 标签化枚举:
#[serde(tag = "game")]告诉serde直接用JSON中的game字段值来匹配枚举变体,彻底解决了结构相同导致的误匹配问题——不管结构体字段是否一致,只要game字段对应,就会解析到正确的变体。 - 重命名变体:每个变体的
#[serde(rename = "...")]属性可以精准对应JSON中带空格的game值(比如"Mario Party 3"对应MarioParty3变体)。 - 公共字段复用:用
#[serde(flatten)]把公共数据结构体嵌入到每个变体中,避免重复编写公共字段。
3. 简化可选方案(如果部分游戏结构体完全相同)
如果像MarioParty2和MarioParty3这类结构完全相同的游戏,可以复用同一个结构体,减少冗余代码:
#[derive(Deserialize)] struct StandardMarioParty { stars: i32, coins: i32 } #[derive(Deserialize)] #[serde(tag = "game")] enum GameData { // ...其他变体 #[serde(rename = "Mario Party 2")] MarioParty2 { #[serde(flatten)] common: CommonGameData, player_data: Vec<StandardMarioParty>, }, #[serde(rename = "Mario Party 3")] MarioParty3 { #[serde(flatten)] common: CommonGameData, player_data: Vec<StandardMarioParty>, }, }
内容的提问来源于stack exchange,提问作者ToxicGLaDOS
相关产品推荐
相关产品推荐

