如何防止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

