【问题标题】:How to deserialize BSON to a generic object?如何将 BSON 反序列化为通用对象?
【发布时间】:2019-01-04 19:13:58
【问题描述】:

我正在使用 Serde 将 BSON 对象反序列化为 Rust 结构实例。我可以将对象反序列化为具体的结构实例,但我如何才能一般地反序列化?

我在 MongoDB 中有“国家”和“城市”集合。在 Rust 程序中,我有一个 CountryCity 的结构。当我从 Mongo 中提取一个国家或城市时,我可以使用 Serde 将其反序列化为 CountryCity 结构。请参阅下面main() 中的第二行。

我想将 BSON 对象反序列化为通用 Location 对象。根据我在 Rust 书中读到的关于泛型的内容,我创建了一个特征 LocationTrait 并为 CountryCity 实现了它。 (参见main() 中的第 3 行)。它无法编译,说 dyn LocationTrait 类型的值的大小在编译时是未知的。

#[derive(Serialize, Deserialize)]
pub struct Country {
    pub name: String,
}

#[derive(Serialize, Deserialize)]
pub struct City {
    pub name: String,
}

pub trait LocationTrait {}
impl LocationTrait for Country {}
impl LocationTrait for City {}

fn main() {
    let item = mongo_coll
        .find_one(Some(doc! {"name": "usa"}), None)
        .unwrap()
        .unwrap();
    let country: Country = bson::from_bson(bson::Bson::Document(item)).unwrap();
    // fails -> let gen_location: LocationTrait = bson::from_bson(bson::Bson::Document(item)).unwrap();
}

最后,我想创建一个代表CountryCity 的通用对象。但是,我不确定起点——我需要专注于一个 trait 还是需要创建一个新的 trait-bound struct?

【问题讨论】:

    标签: serialization rust deserialization serde


    【解决方案1】:

    有两个问题阻止您的代码编译。

    你看到的第一个错误:the size for values of type dyn LocationTrait cannot be known at compilation time,是因为bson::from_bson需要按值返回反序列化的结果。编译器需要知道它需要在调用堆栈中分配多少空间才能返回它。

    不过,trait 是描述行为而非数据的抽象,因此可以为 u8(单个字节)或更大的结构实现。

    为了能够返回这样的值,您需要将其装箱(参见Trait Objects)。

    第二个问题是返回值必须实现Deserialize trait(而不是LocationTrait

    解决这些问题:

    最简单的方法是使用枚举而不是特征:

    #[derive(Serialize, Deserialize)]
    #[serde(tag = "type")]
    pub enum Location {
        Country(Country),
        City(City)
    }
    

    这适用于{"type" = "Country", name="usa"} 等文档。 更多选项请查看the Serde doc

    如果您真的想使用特征(例如,能够在此模块之外定义类型),您将需要盒装特征和自定义结构:

    // The same trait as defined earlier
    pub trait LocationTrait {}
    impl LocationTrait for Country {}
    impl LocationTrait for City {}
    
    // A custom struct on which you can implement the deserialize trait
    // Needed as both Deserialize and Box are defined outside this crate.
    struct DynLocation(Box<dyn LocationTrait>);
    
    impl<'de> Deserialize<'de> for DynLocation {
        fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            // Tricky part ommited here:
            // You will need to partially deserialize you object
            // in order to get a first discriminant before instanciating
            // and deserializing the proper type.
            unimplemented!()
        }
    }
    
    // The public method to hide the DynLocation wrapper
    pub fn deserialize(item: &str) -> Box<dyn LocationTrait> {
        let location: DynLocation = serde_json::from_str(item).expect("invalid json");
        location.0
    }
    

    可以在How can deserialization of polymorphic trait objects be added in Rust if at all? 中找到围绕同一主题的一些讨论。

    【讨论】:

    • 这很好解释。谢谢,特别是枚举的想法,这似乎对这种情况更有意义。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-11
    相关资源
    最近更新 更多