Da_Niel
Da_Niel

Reputation: 41

Rust serde deserialize dynamic trait

I have a recursive data structure in my pet-project:

(this is a simplified example)

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) }
}

and i want to serialize and de-serialize the condition trait (using serde) eg.:

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();
}

Because serde cannot deserialize dyn traits out-of-the box, i tagged the serialized markup, eg:

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

and try to implement a Deserializer and a Vistor for Box<dyn Condition>

Since i am new to Rust and because the implementation of a Deserializer and a Visitor is not that straightforward with the given documentation, i wonder if someone has an idea how to solve my issue with an easier approach?

I went through the serde documentation and searched for solution on tech sites/forums. i tried out typetag but it does not support generic types

UPDATE:

To be more precise: the serialization works fine, ie. serde can serialize any concrete object of the Condition trait, but in order to deserialize a Condition the concrete type information needs to be provided. But this type info is not available at compile time. I am writing a web service where customers can upload rules for context matching (ie. Conditions) so the controller of the web service does not know the type when the condition needs to be deserialized. eg. a Customer can post:

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

or

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

or more complex with any combinator ('and', 'or', 'not')

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

and therefore i need to deserialze to a trait (dyn Condition) using the type tags in the serialized markup...

Upvotes: 3

Views: 1745

Answers (2)

Da_Niel
Da_Niel

Reputation: 41

I removed the generics from the combinator conditions, so i can now use typetag like @EvilTak suggested:

#[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) }
}

(on the downside, i had to remove the derive macros PartialEq, and Debug)

Interesting side fact: i have to keep the #[serde(tag="type")] on the And Struct because otherwise the typetag will be omitted in the serialization (for the primitive consitions it is not needed) UPDATE: typetag adds the type tag only for trait objects so the #[serde(tag="type")] is not needed...

Upvotes: 1

BallpointBen
BallpointBen

Reputation: 13867

I would say the “classic” way to solve this problem is by deserializing into an enum with one variant per potential “real” type you're going to deserialize into. Unfortunately, And is generic, which means those generic parameters have to exist on the enum as well, so you have to specify them where you deserialize.

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:?}");
}

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

Upvotes: 0

Related Questions