Rust serde 反序列化 动态 trait

3

我在我的宠物项目中有一个递归数据结构:

(这是一个简化的例子)

pub trait Condition {
        fn validate(&self, s: &str) -> bool;
}

pub struct Equal {
    ref_val: String,
}
impl Condition for Equal {
    fn validate(&self, s: &str) -> bool { self.ref_val == s }
}

pub struct And<A, B> where A: Condition + ?Sized, B: Condition + ?Sized {
    left: Box<A>,
    right: Box<B>,
}

impl<A, B> Condition for And<A, B> where A: Condition + ?Sized, B: Condition + ?Sized {
    fn validate(&self, s: &str) -> bool { self.left.validate(s) && self.right.validate(s) }
}

我想对条件特性进行序列化和反序列化(使用serde)。例如:
fn main() {

    let c = And {
        left: Box::new(Equal{ ref_val: "goofy".to_string() }),
        right: Box::new(Equal{ ref_val: "goofy".to_string() }),
    };

    let s = serde_json::to_string(&c).unwrap();

    let d: Box<dyn Condition> = serde_json::from_string(&s).unwrap();
}

由于serde无法直接反序列化动态特征,因此我标记了序列化的标记,例如:

#[derive(PartialEq, Debug, Serialize)]
#[serde(tag="type")]
pub struct Equal {
    ref_val: String,
}

尝试实现DeserializerVistor以处理Box<dyn Condition>

由于我是Rust的新手,而且根据给定的文档实现Deserializer和Visitor并不那么直接,我想知道是否有人有更简单的方法来解决我的问题?

我已经阅读了serde文档,并在技术网站/论坛上寻找解决方案。我尝试了typetag,但它不支持泛型类型。

更新:

更准确地说:序列化工作正常,即serde可以序列化Condition trait的任何具体对象,但为了反序列化Condition,需要提供具体类型信息。但是这种类型信息在编译时不可用。我正在编写一个Web服务,客户可以上传用于上下文匹配(即条件)的规则,因此当需要反序列化条件时,Web服务的控制器不知道类型。例如,客户可以发布:

{"type":"Equal","ref_val":"goofy"}

或者

{"type":"Greater","ref_val":"Pluto"}

或者更复杂,包含任何组合器(“and”,“or”,“not”)

{"type":"And","left":{"type":"Greater","ref_val":"Gamma"},"right":{"type":"Equal","ref_val":"Delta"}}

因此,我需要使用序列化标记中的类型标签将其反序列化为一个特质(dyn Condition)。

@Stargateur,我更新了我的帖子,以使问题更加清晰明了。 - Da_Niel
typetag 这个 crate 是由 dtolnay 开发的,它可以让你实现这一点。 - EvilTak
@EvilTak 理论上是可以的,但不幸的是 typetag 不支持泛型:暂时不支持通用实现的反序列化;请使用 #[typetag::serialize] 生成仅序列化的代码... - Da_Niel
2个回答

1

我从组合条件中删除了泛型,因此现在可以像@EvilTak建议的那样使用typetag:

#[derive(Serialize, Deserialize)]
#[serde(tag="type")]
pub struct And {
    left: Box<dyn Condition>,
    right: Box<dyn Condition>,
}
#[typetag::serde]
impl Condition for And {
    fn validate(&self, s: &str) -> bool { self.left.validate(s) && self.right.validate(s) }
}

缺点是,我不得不删除派生宏 PartialEq 和 Debug。

有趣的事实是:我必须在 And 结构体上保留 #[serde(tag="type")],否则在序列化时类型标记将被省略(对于原始条件而言不需要)更新:typetag仅为特定对象添加类型标记,因此不需要 #[serde(tag="type")]...


0
我认为解决这个问题的“经典”方式是将其反序列化为一个枚举,每个潜在的“真实”类型都有一个变体。不幸的是,And是通用的,这意味着这些通用参数也必须存在于枚举中,因此您必须在反序列化时指定它们。
use serde::{Deserialize, Serialize};
use serde_json; // 1.0.91 // 1.0.152

pub trait Condition {
    fn validate(&self, s: &str) -> bool;
}

#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct Equal {
    ref_val: String,
}
impl Condition for Equal {
    fn validate(&self, s: &str) -> bool {
        self.ref_val == s
    }
}

#[derive(PartialEq, Debug, Serialize, Deserialize)]
pub struct And<A, B>
where
    A: Condition + ?Sized,
    B: Condition + ?Sized,
{
    left: Box<A>,
    right: Box<B>,
}

impl<A, B> Condition for And<A, B>
where
    A: Condition + ?Sized,
    B: Condition + ?Sized,
{
    fn validate(&self, s: &str) -> bool {
        self.left.validate(s) && self.right.validate(s)
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
enum Expr<A, B>
where
    A: Condition + ?Sized,
    B: Condition + ?Sized,
{
    Equal(Equal),
    And(And<A, B>),
}

fn main() {
    let c = And {
        left: Box::new(Equal {
            ref_val: "goofy".to_string(),
        }),
        right: Box::new(Equal {
            ref_val: "goofy".to_string(),
        }),
    };

    let s = serde_json::to_string(&c).unwrap();

    let d: Expr<Equal, Equal> = serde_json::from_str(&s).unwrap();
    println!("{d:?}");
}

打印 And(And { left: Equal { ref_val: "goofy" }, right: Equal { ref_val: "goofy" } })


谢谢@BallpointBen,使用枚举的想法很有趣,但是它仍然需要在编译时进行反序列化的类型信息。(我更新了我的问题以更清楚地指定问题案例) - Da_Niel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接