Search code examples
rustpolymorphismserde

How to implement `serde::Serialize` for a boxed trait object?


I ran into a problem trying to create a generic vector for a struct. This was my first attempt:

#[derive(Serialize)]
struct Card {
    sections: Vec<Section<dyn WidgetTrait>>
}

#[derive(Serialize)]
struct Section<T: WidgetTrait> {
    header: String,
    widgets: Vec<T>
}

This has brought me to an error that Sized is not implemented and WidgetTrait size is not known at compile time.

My next attempt was to use Box<dyn WidgetTrait> like so:

#[derive(Serialize)]
struct Section {
    header: String,
    widgets: Vec<Box<dyn WidgetTrait>>
}

Playground

This has led me to an error:

error[E0277]: the trait bound `WidgetTrait: serde::Serialize` is not satisfied
  --> src/main.rs:11:10
   |
11 | #[derive(Serialize)]
   |          ^^^^^^^^^ the trait `serde::Serialize` is not implemented for `WidgetTrait`
   |
   = note: required because of the requirements on the impl of `serde::Serialize` for `std::boxed::Box<dyn WidgetTrait>`
   = note: required because of the requirements on the impl of `serde::Serialize` for `std::vec::Vec<std::boxed::Box<dyn WidgetTrait>>`
   = note: required by `serde::ser::SerializeStruct::serialize_field`

My goal is for the widgets vector in Section struct to be able to accept different types of widgets that implement WidgetTrait trait, just like you would with an interface.


Solution

  • For serializing Serde trait objects you should use erased-serde.

    // [dependencies]
    // erased-serde = "0.3"
    // serde = { version = "1", features = ["derive"] }
    // serde_json = "1"
    
    use erased_serde::serialize_trait_object;
    use serde::Serialize;
    
    #[derive(Serialize)]
    struct Card {
        sections: Vec<Section>,
    }
    
    #[derive(Serialize)]
    struct Section {
        header: String,
        widgets: Vec<Box<dyn WidgetTrait>>,
    }
    
    #[derive(Serialize)]
    struct Image {
        image_url: String,
    }
    
    #[derive(Serialize)]
    struct KeyValue {
        top_label: String,
        content: String,
    }
    
    trait WidgetTrait: erased_serde::Serialize {}
    impl WidgetTrait for Image {}
    impl WidgetTrait for KeyValue {}
    
    serialize_trait_object!(WidgetTrait);
    
    fn main() {
        let card = Card {
            sections: vec![
                Section {
                    header: "text".to_owned(),
                    widgets: vec![
                        Box::new(Image {
                            image_url: "img".to_owned(),
                        }),
                        Box::new(KeyValue {
                            top_label: "text".to_owned(),
                            content: "text".to_owned(),
                        }),
                    ],
                },
            ],
        };
    
        println!("{}", serde_json::to_string_pretty(&card).unwrap());
    }