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

3

21

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.

Evvy answered 25/4, 2018 at 12:5 Comment(9)
Please, can you provide a minimal reproducible example? Ideally, your code could be copy/pasted into the playground and compile or give the expected error.Montanez
Using generics as you have in the Section struct will not allow you to store different Widgets which implement WidgetTrait, because Rust monomorphises generics during compilation. Just FYI, the boxed trait is generally good way to go, or an Rc, depending on your use caseMaryrose
Added playground linkEvvy
You will need to implement Serialize manually for Section. Even if every WidgetTrait type implements Serialize, that doesn't mean that the type WidgeTrait itself does. In fact there's no way to automatically implement that.Ticker
Thanks, I think I'm getting somewhere. Added another playground link, how do I write an implementation for Box?Evvy
Did you check the official documentation? Did you try to implement Serialize for Box?Nyctaginaceous
Yes I did check the documentation, the problem is that I can't figure out a way to find out what type of struct comes from the Box<WidgetTrait> to be able to use the strongly typed serializerEvvy
Your latest edit does not solve your Problem, it's a complete different approach.Nyctaginaceous
Tim, my problem was finding a solution on how to implement Serialize trait for Box<of Trait> which is what the solution does. Not saying it's elegant.Evvy
S
17

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());
}
Scholastic answered 25/4, 2018 at 16:11 Comment(2)
For the folks that feel that this is not working, special attention to the serialize_trait_object!(WidgetTrait);. It makes all the difference.Projection
See my answer (https://mcmap.net/q/604404/-how-to-implement-serde-serialize-for-a-boxed-trait-object) if you also want to deserialize.Depict
D
5

erased-serde won't work if you also want to deserialize:

https://github.com/dtolnay/erased-serde/issues/25#issuecomment-605387945

The solution is to use:

https://github.com/dtolnay/typetag

All you need is #[typetag::serde(tag = "type")] on the trait, and #[typetag::serde] on the impl's.

Depict answered 11/8, 2023 at 18:46 Comment(0)
E
4

I got around the compiler errors:

#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate serde;

use serde::ser::{Serialize, Serializer, SerializeStruct};

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

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

#[derive(Serialize)]
struct Image {
    #[serde(rename = "imageUrl")]
    image_url: String
}

#[derive(Serialize)]
struct KeyValue {
    #[serde(rename = "topLabel")]
    top_label: String,

    content: String
}

trait WidgetTrait {}

impl Serialize for WidgetTrait {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer {
            let s = serializer.serialize_struct("???", 3)?;

            s.end()
        }
}

impl WidgetTrait for Image {}
impl WidgetTrait for KeyValue {}

fn main() {
    // let test = ResponseMessage { 
    //         text: None, 
    //         cards: Some(
    //             vec![Card { sections: vec![
    //                 Section { header: format!("text"), widgets: vec![ 
    //                     Box::new(Image { image_url: format!("img") }) 
    //                     ]},
    //                 Section { header: format!("text"), widgets: vec![
    //                      Box::new(KeyValue { top_label: format!("text"), content: format!("text") }),
    //                      Box::new(KeyValue { top_label: format!("text"), content: format!("text") })
    //                      ]}
    //                 ]}])
    //         }
}

Playground


Steps for a working solution.

  1. Write as_any() implementations for your structs that implement WidgetTrait as per How to get a reference to a concrete type from a trait object?.
  2. Add implementation for trait Serialize of type Box<dyn WidgetTrait>
  3. Downcast Box<Widget> to the struct so we know the type using as_any() and downcast_ref()
  4. Use documentation on how to serialize a strongly typed struct
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate serde;

use serde::ser::{Serialize, Serializer, SerializeStruct};
use std::any::Any;

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

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

#[derive(Serialize)]
struct Image {
    #[serde(rename = "imageUrl")]
    image_url: String
}

#[derive(Serialize)]
struct KeyValue {
    #[serde(rename = "topLabel")]
    top_label: String,

    content: String
}

trait WidgetTrait {
    fn as_any(&self) -> &Any;
}

impl Serialize for Box<dyn WidgetTrait> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 
        where S: Serializer {
            return match self.as_any().downcast_ref::<Image>() {
                Some(img) => {
                        let mut widget_serializer = serializer.serialize_struct("Image", 1)?;
                        widget_serializer.serialize_field("imageUrl", &img.image_url)?;

                        widget_serializer.end()  
                    },
                None => {
                    let key_value: &KeyValue = match self.as_any().downcast_ref::<KeyValue>() {
                        Some(k) => k,
                        None => panic!("Unknown type!")
                    };

                    let mut widget_serializer = serializer.serialize_struct("KeyValue", 2)?;
                    widget_serializer.serialize_field("topLabel", &key_value.top_label)?;
                    widget_serializer.serialize_field("content", &key_value.content)?;

                    widget_serializer.end()  
                }
            };                
        }
}

impl WidgetTrait for Image {
    fn as_any(&self) -> &Any {
        self
    }
}

impl WidgetTrait for KeyValue {
    fn as_any(&self) -> &Any {
        self
    }
}

fn main() {
    // let test = ResponseMessage { 
    //         text: None, 
    //         cards: Some(
    //             vec![Card { sections: vec![
    //                 Section { header: format!("text"), widgets: vec![ 
    //                     Box::new(Image { image_url: format!("img") }) 
    //                     ]},
    //                 Section { header: format!("text"), widgets: vec![
    //                      Box::new(KeyValue { top_label: format!("text"), content: format!("text") }),
    //                      Box::new(KeyValue { top_label: format!("text"), content: format!("text") })
    //                      ]}
    //                 ]}])
    //         }
}

Playground

Evvy answered 25/4, 2018 at 15:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.