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

跨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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 11:37:10