简体   繁体   English

Serde MsgPack 版本控制

[英]Serde MsgPack versioning

I need to serialize some data into files.我需要将一些数据序列化为文件。 For the sake of memory efficiency, I want to use the default compact serializer of MessagePack (MsgPack), as it only serializes field values w/o their names.为了 memory 效率,我想使用 MessagePack (MsgPack) 的默认紧凑序列化程序,因为它只序列化不带名称的字段值。 I also want to be able to make changes to the data structure in future versions, which obviously can't be done w/o also storing some meta/versioning information.我还希望能够在未来版本中对数据结构进行更改,这显然无法在不存储一些元/版本信息的情况下完成。 I imagine the most efficient way to do it is to simply use some "header" field for that purpose.我想最有效的方法是为此目的简单地使用一些“标题”字段。 Here is an example:这是一个例子:

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
}

Can I do something like that in rmp-serde (or maybe some other crate?) - to somehow annotate that a certain struct field should only be taken into account for specific file versions?我可以在rmp-serde (或者其他一些 crate 吗?)中做类似的事情 - 以某种方式注释某个结构字段应该只考虑特定文件版本?

You can achieve this by writing a custom deserializer like this:您可以通过编写这样的自定义反序列化器来实现这一点:

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<_, _>>()?,
        })
    }
}

Short explanation how the deserializer works:解串器如何工作的简短说明:

  • We create a "dumb" inner struct which is a copy of your structs but the "new" fields are optional我们创建一个“哑”内部结构,它是您的结构的副本,但“新”字段是可选的
  • We deserialize to the new inner structs我们反序列化到新的内部结构
  • We map from our inner to our outer structs using version-based logic我们使用基于版本的逻辑从内部结构到外部结构 map
    • If one of the new fields is missing in a new version we return a D::Error::missing_field error如果新版本中缺少其中一个新字段,我们将返回D::Error::missing_field错误

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM