【问题标题】:Serde MsgPack versioningSerde MsgPack 版本控制
【发布时间】:2021-12-15 06:25:18
【问题描述】:

我需要将一些数据序列化为文件。为了内存效率,我想使用 MessagePack (MsgPack) 的默认 compact 序列化程序,因为它只序列化不带名称的字段值。我还希望能够在未来的版本中对数据结构进行更改,这显然不能在不存储一些元/版本信息的情况下完成。我想最有效的方法是为此目的简单地使用一些“标题”字段。这是一个例子:

pub struct Data {
    pub version: u8,
    pub items: Vec<Item>,
}
pub struct Item {
    pub field_a: i32,
    pub field_b: String,
    pub field_c: i16,  // Added in version 3
}

我可以在rmp-serde(或者其他一些箱子吗?)中做类似的事情 - 以某种方式注释某个结构字段应该只考虑特定文件版本?

【问题讨论】:

  • 您的意思是,您希望以前版本的应用程序在 Item 增长时不会在解码消息时失败,仅基于 Data 中的 version
  • @DenysSéguret 是的,完全正确。在我的示例中,我想field_c 应该在version &lt; 3 时使用某种默认值进行初始化。
  • 如果你真的需要这样的大小增益,你可能应该在反序列化之前考虑分支,以便你反序列化成不同的结构集。因为您当前的方法似乎很难处理,尤其是添加字段只是可能的更改之一

标签: rust serde msgpack


【解决方案1】:

您可以通过编写这样的自定义反序列化器来实现此目的:

use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Serialize)]
pub struct Data {
    pub version: u8,
    pub items: Vec<Item>,
}

#[derive(Serialize)]
pub struct Item {
    pub field_a: i32,
    pub field_b: String,
    pub field_c: i16, // Added in version 3
}

impl<'de> Deserialize<'de> for Data {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        // Inner structs, used for deserializing only
        #[derive(Deserialize)]
        pub struct InnerData {
            version: u8,
            items: Vec<InnerItem>,
        }
        #[derive(Deserialize)]
        pub struct InnerItem {
            field_a: i32,
            field_b: String,
            field_c: Option<i16>, // Added in version 3 - note that this field is optional
        }

        // Deserializer the inner structs
        let inner_data = InnerData::deserialize(deserializer)?;        
        
        // Get the version so we can add custom logic based on the version later on
        let version = inner_data.version;

        // Map the InnerData/InnerItem structs to Data/Item using our version based logic
        Ok(Data {
            version,
            items: inner_data
                .items
                .into_iter()
                .map(|item| {
                    Ok(Item {
                        field_a: item.field_a,
                        field_b: item.field_b,
                        field_c: if version < 3 {
                            42 // Default value
                        } else {
                            // Get the value of field_c
                            // If it's missing return an error, since it's required since version 3
                            // Otherwise return the value
                            item.field_c
                                .map_or(Err(D::Error::missing_field("field_c")), Ok)?
                        },
                    })
                })
                .collect::<Result<_, _>>()?,
        })
    }
}

反序列化器如何工作的简短说明:

  • 我们创建了一个“哑”内部结构,它是您的结构的副本,但“新”字段是可选的
  • 我们反序列化为新的内部结构
  • 我们使用基于版本的逻辑从内部结构映射到外部结构
    • 如果新版本中缺少某个新字段,我们将返回 D::Error::missing_field 错误

【讨论】:

    猜你喜欢
    • 2018-02-01
    • 2010-10-01
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 2011-05-12
    • 2010-10-30
    • 2014-08-15
    • 1970-01-01
    相关资源
    最近更新 更多