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

如何防止Rust中序列化结构体被反序列化为其他结构体?

如何防止同名字段但语义不同的结构体被跨版本反序列化

问题场景

现有两个结构体RubyVersionV1和RubyVersionV2,二者都包含version字段,但语义完全不同:

  • RubyVersionV1的version是String类型,记录的是明确指定或默认的安装版本
  • RubyVersionV2的version是Option<String>类型,None代表使用默认版本,Some(String)代表明确指定版本

当前的问题是,RubyVersionV1序列化到磁盘的数据可能被误反序列化为RubyVersionV2,导致逻辑错误,需要从序列化层面彻底阻止这种情况,而非依赖开发者的谨慎操作。

原始代码:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV1 {
    // 记录要安装的明确版本或默认版本
    version: String,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV2 {
    // 区分明确版本与默认场景:None表示使用默认,Some表示明确指定
    version: Option<String>,
}

// 统一的 trait 定义程序所需的行为
trait RubyVersion {
    fn version_to_install(&self) -> String;
}

impl RubyVersion for RubyVersionV1 {
    fn version_to_install(&self) -> String {
        self.version.clone()
    }
}

const DEFAULT_VERSION: &str = "3.2.2";

impl RubyVersion for RubyVersionV2 {
    fn version_to_install(&self) -> String {
        self.version
            .clone()
            .unwrap_or_else(|| DEFAULT_VERSION.to_string())
    }
}

解决方案

方法1:添加显式版本标识字段

给每个版本的结构体添加固定值的版本字段,反序列化时强制校验该字段的值,同时结合deny_unknown_fields特性,确保旧版本数据无法被反序列化为新版本结构体。

修改后的代码:

use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV1 {
    // 记录要安装的明确版本或默认版本
    version: String,
    // V1 版本标识
    schema_version: u8,
}

// 初始化 V1 时固定版本号为1
impl RubyVersionV1 {
    fn new(version: String) -> Self {
        Self {
            version,
            schema_version: 1,
        }
    }
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV2 {
    // 区分明确版本与默认场景:None表示使用默认,Some表示明确指定
    version: Option<String>,
    // V2 版本标识,固定为2
    schema_version: u8,
}

// 初始化 V2 时固定版本号为2
impl RubyVersionV2 {
    fn new(version: Option<String>) -> Self {
        Self {
            version,
            schema_version: 2,
        }
    }
}

// 统一的 trait 定义程序所需的行为
trait RubyVersion {
    fn version_to_install(&self) -> String;
}

impl RubyVersion for RubyVersionV1 {
    fn version_to_install(&self) -> String {
        self.version.clone()
    }
}

const DEFAULT_VERSION: &str = "3.2.2";

impl RubyVersion for RubyVersionV2 {
    fn version_to_install(&self) -> String {
        self.version
            .clone()
            .unwrap_or_else(|| DEFAULT_VERSION.to_string())
    }
}

当尝试将V1的数据(包含schema_version:1)反序列化为V2时,由于V2要求schema_version必须为2,serde会直接报错;而旧的V1数据如果没有schema_version字段,反序列化为V2时会触发deny_unknown_fields的错误,彻底阻止跨版本反序列化。

方法2:使用Serde的标签特性(Tag)

利用Serde的tag属性为不同版本的结构体添加类型标识,反序列化时只有匹配标签的数据才能被正确解析。

修改后的代码:

use serde::{Deserialize, Serialize};

// 定义枚举包裹不同版本结构体,使用内部标签区分
#[derive(Deserialize, Serialize, Debug)]
#[serde(tag = "ruby_version_type")]
enum RubyVersionWrapper {
    V1(RubyVersionV1),
    V2(RubyVersionV2),
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV1 {
    // 记录要安装的明确版本或默认版本
    version: String,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV2 {
    // 区分明确版本与默认场景:None表示使用默认,Some表示明确指定
    version: Option<String>,
}

// 统一的 trait 定义程序所需的行为
trait RubyVersion {
    fn version_to_install(&self) -> String;
}

impl RubyVersion for RubyVersionV1 {
    fn version_to_install(&self) -> String {
        self.version.clone()
    }
}

const DEFAULT_VERSION: &str = "3.2.2";

impl RubyVersion for RubyVersionV2 {
    fn version_to_install(&self) -> String {
        self.version
            .clone()
            .unwrap_or_else(|| DEFAULT_VERSION.to_string())
    }
}

// 为枚举实现 RubyVersion trait,统一调用内部结构体方法
impl RubyVersion for RubyVersionWrapper {
    fn version_to_install(&self) -> String {
        match self {
            RubyVersionWrapper::V1(v1) => v1.version_to_install(),
            RubyVersionWrapper::V2(v2) => v2.version_to_install(),
        }
    }
}

序列化后的JSON会包含"ruby_version_type": "V1"或"ruby_version_type": "V2"的标签,反序列化时只有匹配对应标签的数据才能被解析为对应结构体。如果尝试将V1标签的数据解析为V2,或无标签的旧数据解析为V2,都会直接报错。

方法3:自定义反序列化逻辑

为RubyVersionV2实现自定义Deserialize逻辑,强制校验数据是否符合V2的语义,比如添加仅V2存在的校验字段。

示例代码:

use serde::{de::Visitor, Deserialize, Deserializer, Serialize};
use std::fmt;

#[derive(Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV2 {
    // 区分明确版本与默认场景:None表示使用默认,Some表示明确指定
    version: Option<String>,
    // 仅V2存在的标识字段,序列化时隐藏
    #[serde(skip_serializing)]
    is_v2: bool,
}

impl<'de> Deserialize<'de> for RubyVersionV2 {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct RubyVersionV2Visitor;

        impl<'de> Visitor<'de> for RubyVersionV2Visitor {
            type Value = RubyVersionV2;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a RubyVersionV2 struct")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::MapAccess<'de>,
            {
                let mut version: Option<String> = None;
                let mut has_v2_flag = false;

                while let Some((key, value)) = map.next_entry()? {
                    match key {
                        "version" => version = Some(value),
                        "_v2_flag" => has_v2_flag = true,
                        _ => return Err(serde::de::Error::unknown_field(key, &["version", "_v2_flag"])),
                    }
                }

                // 强制要求存在_v2_flag字段,否则报错
                if !has_v2_flag {
                    return Err(serde::de::Error::missing_field("_v2_flag"));
                }

                Ok(RubyVersionV2 {
                    version,
                    is_v2: true,
                })
            }
        }

        deserializer.deserialize_map(RubyVersionV2Visitor)
    }
}

// 原始V1结构体保持不变
#[derive(Deserialize, Serialize, Debug)]
#[serde(deny_unknown_fields)]
struct RubyVersionV1 {
    // 记录要安装的明确版本或默认版本
    version: String,
}

// 统一的 trait 定义程序所需的行为
trait RubyVersion {
    fn version_to_install(&self) -> String;
}

impl RubyVersion for RubyVersionV1 {
    fn version_to_install(&self) -> String {
        self.version.clone()
    }
}

const DEFAULT_VERSION: &str = "3.2.2";

impl RubyVersion for RubyVersionV2 {
    fn version_to_install(&self) -> String {
        self.version
            .clone()
            .unwrap_or_else(|| DEFAULT_VERSION.to_string())
    }
}

这种方式通过添加仅V2才有的_v2_flag校验字段,反序列化时强制要求该字段存在,从而阻止V1数据(无该字段)被解析为V2。


内容的提问来源于stack exchange,提问作者Schneems

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 12:05:13