You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:44:57