如何为rust中带有serde的内部标记的枚举创建自定义反序列化程序

zzoitvuj  于 2023-06-23  发布在  其他
关注(0)|答案(2)|浏览(108)

我有一堆来自DynamoDB表的实体。每个实体都有一个type字段,标识它是什么类型的实体。类型字段是一个单项列表,其中包含一个带有类型名称的字符串。这是一个奇怪的格式,但这是我必须处理的。
我使用Serde成功地实现了一个自定义的反序列化器,将该类型转换为枚举。我目前的方法包括首先反序列化为一个带有type字段的简化结构体,然后匹配该结构体类型,并基于该类型使用适当的反序列化器。
我看到Serde支持内部标记的枚举来反序列化属于枚举中类型的其余字段,但我不知道如何调整我当前的反序列化器以选择此功能。
作为参考,这是我目前拥有的反序列化器:

use serde::{Deserialize, Deserializer, Serialize};
use types::*;

#[derive( Debug)]
enum Type {
    Story,
    Layer,
    Unknown(String),
}

impl<'de> Deserialize<'de> for Type {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = <Vec<String>>::deserialize(deserializer)?;
        let k: &str = s[0].as_str();
        Ok(match k {
            "Story" => Type::Story,
            "Layer" => Type::Layer,
            v => Type::Unknown(v.to_string()),
        })
    }
}

#[derive(Deserialize, Serialize, Debug)]
pub struct SimpleItem {
    #[serde(rename = "type")]
    item_type: Type,
}

我的理想场景是这样的:

use serde::{Deserialize, Deserializer, Serialize};
use types::*;

#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Type {
    Story { id: String, name: string, duration: u32 }
    Layer { id: string, layout: string },
    Unknown,
}

然后使用它直接反序列化正确的类型。
这就是我的数据可能看起来的样子:{ type: ["Story"], ...otherFields}

3bygqnnd

3bygqnnd1#

Deserialize的自定义实现通常是这种“复杂”反序列化的最佳解决方案:

impl<'de> Deserialize<'de> for Type {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<D, D::Error> {
        deserializer.deserialize_map(DeVisitor)
    }
}

struct DeVisitor;
impl<'de> de::Visitor<'de> for DeVisitor {
    type Value = Type;
    fn expecting(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_str("a type")
    }
    fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
        // could use another custom Deserialize to avoid
        // allocation and improve error messages
        let type_key = map.next_key::<String>()?;
        if type_key.as_deref() != Some("type") {
            return Err(de::Error::missing_field("type"));
        }
        // You can also use a Vec<String> here so you avoid TypeField, at
        // the gain of less boilerplate but at cost of worse error messages
        let r#type = map.next_value::<TypeField>()?;
        match r#type {
            Type::Story => {
                #[derive(Deserialize)]
                struct Story {
                    id: String,
                    name: string,
                    duration: u32,
                }
                let story = Story::deserialize(MapAccessDeserializer::new(map))?;
                // Construct your Type
            } // etc
        }
    }
}

enum TypeField {
    Story,
    Layer,
    Unknown(String),
}

// The manual Deserialize implementation you had above
// goes here

上述方法的一个很大的限制是它要求"type"字段是第一个字段。如果你想避免这种情况,最简单的方法取决于数据格式。例如,使用JSON,它可能看起来像这样:

let mut type_field = None;
let mut data = <Vec<(String, Box<serde_json::value::RawValue)>>::new();
while let Some(key) = map.next_key()? {
    if key == "type" {
        if type_field.is_some() {
            return Err(de::Error::duplicate_field("type"));
        }
        type_field = Some(map.next_value()?);
    } else {
        data.push((key, map.next_value()?));
    }
}
// Now you can match on type_field and use MapDeserializer
// to deserialize from `data`
zed5wv10

zed5wv102#

所提供的答案非常完整,而且正确。但是,永远不要低估使用默认的东西和一点前一步的方便性。在这种特殊情况下(我并不是说这可以应用于所有类似的情况),在反序列化之前对数据进行规范化可能是最简单的事情。源类型几乎就在那里了,唯一的小缺点是它被 Package 在一个无用的列表中(这只是来自不同编程语言的序列化工件)。所以,我重复一遍,在这个特定的例子中,处理这个小细节比编写一个完整的新反序列化器要容易得多,特别是因为这种格式有点复杂。
所以这就是我现在要做的,展开类型,然后让正常的serde反序列化发生。

// Normalize the data from the DB so the "type" is just a string rather than a list with a
// single string in it.
let items = items
.iter()
.map(|item| {
    let mut item = item.to_owned();
    item.entry("type".to_owned()).and_modify(|v| {
        // if it is already a plain string, no need to unwrap
        if !v.is_s() {
            *v = v
                .as_l()
                .map_err(|_err| {
                    GetError::DataError(
                        "Expected db item 'type' field to be a list or a string",
                    )
                })
                .unwrap()
                .get(0)
                .unwrap()
                .to_owned();
        }
    });
    item
})
.collect::<Vec<_>>();

现在,您可以正常地反序列化,而无需为内部标记的枚举定义任何自定义反序列化:

let stories: Vec<StoryPart> = from_items(items.clone())?

相关问题