3 votes

Rust serde deserialize dynamic trait

J'ai une structure de données récursive dans mon projet :

(il s'agit d'un exemple simplifié)

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

et je veux sérialiser et dé-sérialiser le trait de condition (en utilisant la fonction 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();
}

Parce que serde ne peut pas désérialiser les traits dyn en sortie de boîte, j'ai marqué le balisage sérialisé, par exemple :

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

et essayer de mettre en œuvre un Deserializer et un Vistor para Box<dyn Condition>

Étant donné que je suis nouveau dans Rust et que l'implémentation d'un Deserializer et d'un Visitor n'est pas si simple avec la documentation donnée, je me demande si quelqu'un a une idée pour résoudre mon problème avec une approche plus facile ?

J'ai parcouru la documentation de Serde et j'ai cherché une solution sur les sites/forums techniques. J'ai essayé étiquette d'identification mais il ne prend pas en charge les types génériques

UPDATE :

Pour être plus précis : la sérialisation fonctionne bien, c'est-à-dire que serde peut sérialiser n'importe quel objet concret du trait Condition, mais pour désérialiser une Condition, l'information de type concret doit être fournie. Mais cette information de type n'est pas disponible au moment de la compilation. Je suis en train d'écrire un service web où les clients peuvent télécharger des règles pour la correspondance de contexte (c'est-à-dire des conditions), de sorte que le contrôleur du service web ne connaît pas le type lorsque la condition doit être désérialisée. Par exemple, un client peut poster :

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

ou

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

ou plus complexe avec n'importe quel combinateur ('et', 'ou', 'pas')

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

et donc je dois désérialiser vers un trait (dyn Condition) en utilisant les balises de type dans le balisage sérialisé...

1voto

Da_Niel Points 31

J'ai supprimé les génériques des conditions du combinateur, de sorte que je peux maintenant utiliser étiquette d'identification comme @EvilTak l'a suggéré :

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

(en revanche, j'ai dû supprimer les macros dérivées PartialEq et Debug).

Fait latéral intéressant : je dois garder les #[serde(tag="type")] sur le And Struct parce que sinon le typetag sera omis dans la sérialisation (pour les consitions primitives il n'est pas nécessaire) UPDATE : typetag ajoute la balise de type seulement pour les objets trait donc la balise #[serde(tag="type")] n'est pas nécessaire...

0voto

BallpointBen Points 3265

Je dirais que la façon "classique" de résoudre ce problème est de désérialiser dans un enum avec une variante par type "réel" potentiel dans lequel vous allez désérialiser. Malheureusement, And est générique, ce qui signifie que ces paramètres génériques doivent également exister dans l'enum, et que vous devez donc les spécifier lors de la désérialisation.

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

Imprimés And(And { left: Equal { ref_val: "goofy" }, right: Equal { ref_val: "goofy" } })

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X