Looking for good patterns for de/serializing a vector of trait objects from json with serde


#1

I’m porting an image editor from ECMAScript at the moment, and started out by throwing together a de/serializer for the existing (mostly JSON) file format with serde. That came together reasonably smoothly, but left me with a Vec of enums, with different variants for the different layer types (pixmap, layer effects, etc).

But, you can’t impl methods on variants, or (to my knowledge) access the fields without matching, and I’ve got over half a dozen fields per variant. So at this point I’m considering translating to a vector of trait objects after I load the vector of enums using something like the code below, but that would then require defining every variant twice and keeping them in sync.

Is there a Better Way? Or at the very least are there ways of making this approach a bit more readable/maintainable?

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



trait Sing {
    fn sing(&self) -> ();
    fn curse(&self) -> Womble;
}

struct B32 {
    y: i32,
}
struct B16 {
    z: i16,
}

impl Sing for B32 {
    fn sing(&self) -> () {
        println!("Lala from B32{{ y:{} }}", self.y);
    }
    fn curse(&self) -> Womble {
        Womble::B32 { y: self.y }
    }
}
impl Sing for B16 {
    fn sing(&self) -> () {
        println!("Lala from B16{{ z:{} }}", self.z);
    }
    fn curse(&self) -> Womble {
        Womble::B16 { z: self.z }
    }
}



#[derive(Serialize, Deserialize)]
#[serde(tag = "wombletype")]
enum Womble {
    B32 { y: i32 },
    B16 { z: i16 },
}
impl Womble {
    fn bless(&self) -> Box<Sing> {
        match *self {
            Womble::B32 {y} => Box::new(B32{y:y}),
            Womble::B16 {z} => Box::new(B16{z:z}),
        }
    }
}


type BoxList = Vec<Box<Sing>>;
type EnumList = Vec<Womble>;

fn main() {
    let list:BoxList = vec![ Box::new(B16{z:160}), Box::new(B32{y:320}),];

    for j in &list {
        j.sing();
    }

    let partial_ser: EnumList = list.iter().map(|x| x.curse()).collect();
    let serialized = serde_json::to_string_pretty(&partial_ser).unwrap();

    println!("\nserialized = {}\n", serialized);

    let partial_deser:EnumList = serde_json::from_str(&serialized[..]).unwrap();
    let deser:BoxList = partial_deser.iter().map(|x| x.bless()).collect();

    for j in deser {
        j.sing();
    }
}

#2

I guess for a start I could wrap the contents of the enum variants, using something like

struct ImageLayer {...}
struct ModeFilter {...}
enum Layer {
  ImageLayer(ImageLayer),
  ModeFilter(ModeFilter),
}

It’s still feeling pretty unergonomic though…


#3

So what you actually want is to be able to deserialize directly into Box<Trait>. You can have a look at erased_serde, it should offer you ways to create serializeable/deserializeable trait objects. With some changes to your trait it should be doable.


#4

Isn’t erased_serde about boxing the de/serializer, rather than the deserialized data?


#5

yes, but then you can have a Serializer in your api without it needing to be generic. This way you can make YourTrait require a Serialize impl and then you can implement Serialize for Box<YourTrait>


#6

Ah! Ok, that sounds fairly sensible.

I am, however, somewhat daunted by the prospect, and meanwhile I got distracted working out how to get around my bodge above failing whenever there are non-Copy types involved. So I’ve been learning more about moving things out of vectors, dealing with lifetimes for vectors of references etc, and threw together the following:

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
const SAMPLE: &str = r#"
[
  {
    "layertype": "A",
    "x": 23,
    "b": "cat"
  },
  {
    "layertype": "B",
    "v":[1,2,3]
  }
]"#;



#[derive(Serialize,Deserialize)]
struct A {
    x: u32,
    b: String,
}
#[derive(Serialize,Deserialize)]
struct B {v:Vec<u8>}

#[derive(Deserialize)]
#[serde(tag = "layertype")]
enum E {
    A(A),
    B(B),
}

#[derive(Serialize)]
#[serde(tag = "layertype")]
enum Eref<'a> {
    A(&'a A),
    B(&'a B),
}



trait K {
    fn tag(&self) -> Eref;
}
impl K for A {
    fn tag(&self) -> Eref {
        Eref::A(self)
    }
}
impl K for B {
    fn tag(&self) -> Eref {
        Eref::B(self)
    }
}

fn un_enum(x: E) -> Box<K> {
    match x {
        E::A(v) => Box::new(v),
        E::B(v) => Box::new(v),
    }
}
fn ser(v: &Vec<Box<K>>) -> String {
    let q: Vec<Eref> = v.iter().map(|x| x.tag()).collect();
    let s = serde_json::to_string_pretty(&q).unwrap();
    s
}
fn deser(s:&str) -> Vec<Box<K>> {
        let mut a: Vec<E> = serde_json::from_str(s).unwrap();
        let v=a.drain(..).map(un_enum).collect();
        v
}

fn main() {
    let v =deser(SAMPLE);
    let s = ser(&v);
    println!("{}",s);
}