跨Crate的Serde反序列化兼容:旧Crate数据转新Crate对象
问题:旧Crate序列化字符串反序列化为新Crate对象的解决方案
问题背景
我定义了两个模块crate_old和crate_new,代码如下:
/* [dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1" typetag = "*" */ pub mod crate_old { use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MyStruct( #[serde()] pub usize ); #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MyStructAppend( pub usize, pub usize ); #[typetag::serde(tag = "MyTrait", content = "value")] pub trait MyTrait {} #[typetag::serde] impl MyTrait for MyStruct {} #[typetag::serde(tag = "RealStruct", content = "value")] pub trait RealStruct {} #[typetag::serde] impl RealStruct for MyStruct {} #[typetag::serde] impl RealStruct for MyStructAppend {} } pub mod crate_new { use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum RealStruct { MyStruct(usize), MyStructAppend(usize, usize), } #[typetag::serde(tag = "MyTrait", content = "value")] pub trait MyTrait {} #[typetag::serde] impl MyTrait for RealStruct {} } fn main() { let crate1_box: Box<dyn crate_old::MyTrait> = Box::new(crate_old::MyStruct(10)); let crate1_box_str = serde_json::to_string(&crate1_box).unwrap(); assert_eq!(crate1_box_str, r#"{"MyTrait":"MyStruct","value":10}"#); let crate2_box: Box<dyn crate_new::MyTrait> = Box::new(crate_new::RealStruct::MyStruct(10)); let crate2_box_str = serde_json::to_string(&crate2_box).unwrap(); }
旧Crate中MyStruct是独立结构体,新Crate中它是枚举RealStruct的一个变体,数据结构完全一致。两者序列化后的字符串存在差异:
- 旧Crate序列化结果:
{"MyTrait":"MyStruct","value":10} - 新Crate对应对象序列化结果:
{"MyTrait":"RealStruct","value":10}
需要将旧Crate生成的序列化字符串,反序列化为新Crate中与crate2_box一致的对象,推测需要自定义typetag的反序列化逻辑,但不清楚具体实现方式。
解决方案
核心思路是拦截typetag的标签解析流程,将旧标签映射到新标签对应的枚举变体,同时兼容新旧格式的反序列化。具体实现如下:
1. 修改crate_new模块的反序列化逻辑
为Box<dyn MyTrait>实现自定义反序列化,识别旧标签并映射到对应的RealStruct变体:
pub mod crate_new { use serde::{de::Deserializer, Deserialize, Serialize}; use typetag::serde::Deserialize; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum RealStruct { MyStruct(usize), MyStructAppend(usize, usize), } #[typetag::serde(tag = "MyTrait", content = "value")] pub trait MyTrait {} #[typetag::serde] impl MyTrait for RealStruct {} // 自定义反序列化,兼容旧标签格式 impl<'de> Deserialize<'de> for Box<dyn MyTrait> { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { // 先解析为包含标签和内容的中间结构 #[derive(Deserialize)] struct Intermediate { #[serde(rename = "MyTrait")] tag: String, value: serde_json::Value, } let intermediate = Intermediate::deserialize(deserializer)?; // 根据标签映射到对应的RealStruct变体 let real_struct = match intermediate.tag.as_str() { // 处理旧Crate的标签 "MyStruct" => { let val: usize = serde_json::from_value(intermediate.value)?; RealStruct::MyStruct(val) } "MyStructAppend" => { let (a, b): (usize, usize) = serde_json::from_value(intermediate.value)?; RealStruct::MyStructAppend(a, b) } // 处理新Crate的标签 "RealStruct" => serde_json::from_value(intermediate.value)?, // 未知标签报错 other => return Err(serde::de::Error::custom(format!("未知MyTrait标签: {}", other))), }; Ok(Box::new(real_struct)) } } }
2. 验证反序列化效果
在main函数中添加验证代码,确认旧格式字符串能正确反序列化为新Crate对象,且重新序列化后与新格式一致:
fn main() { let crate1_box: Box<dyn crate_old::MyTrait> = Box::new(crate_old::MyStruct(10)); let crate1_box_str = serde_json::to_string(&crate1_box).unwrap(); assert_eq!(crate1_box_str, r#"{"MyTrait":"MyStruct","value":10}"#); let crate2_box: Box<dyn crate_new::MyTrait> = Box::new(crate_new::RealStruct::MyStruct(10)); let crate2_box_str = serde_json::to_string(&crate2_box).unwrap(); assert_eq!(crate2_box_str, r#"{"MyTrait":"RealStruct","value":10}"#); // 测试旧格式字符串反序列化 let deserialized: Box<dyn crate_new::MyTrait> = serde_json::from_str(&crate1_box_str).unwrap(); let reserialized = serde_json::to_string(&deserialized).unwrap(); // 验证反序列化后重新序列化的结果与新格式一致 assert_eq!(reserialized, crate2_box_str); }
原理说明
- 通过自定义
Box<dyn MyTrait>的反序列化逻辑,先将JSON解析为包含tag和value的中间结构体,绕开默认的typetag解析流程。 - 根据旧标签(
MyStruct/MyStructAppend)直接将内容解析为对应的RealStruct变体;对于新标签RealStruct,则直接解析为完整的RealStruct枚举。 - 既实现了旧格式的兼容反序列化,又保留了新格式的正常序列化和反序列化能力。
内容的提问来源于stack exchange,提问作者dfadfdff
相关产品推荐
相关产品推荐

