Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 288
snowbane Avatar asked Apr 25 '18 12:04

snowbane


2 Answers

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

#[macro_use]
extern crate serde_derive;

#[macro_use]
extern crate erased_serde;

extern crate serde;
extern crate serde_json;

#[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());
}
like image 68
dtolnay Avatar answered Nov 03 '22 03:11

dtolnay


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

like image 41
snowbane Avatar answered Nov 03 '22 02:11

snowbane