Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust Json serialization overlapping responsibilities

I'm learning Json serialization in Rust, in particular, how to serialize Rust objects to Json.

Currently I see 3 methods of converting an instance of a struct to Json:

  1. Deriving Encodable trait

  2. Manual implementation of ToJson trait

  3. Manual implementation of Encodable trait

Below code illustrates all 3 approaches:

extern crate serialize;

use serialize::{Encoder, Encodable, json};
use serialize::json::{Json, ToJson};
use std::collections::TreeMap;

fn main() {
  let document = Document::new();
  let word_document = WordDocument::new();
  println!("1. Deriving `Encodable`: {}", json::encode(&document));
  println!("2. Manually implementing `ToJson` trait: {}", document.to_json());
  println!("3. Manually implementing `Encodable` trait: {}", json::encode(&word_document));
}

#[deriving(Encodable)]
struct Document<'a> {
  metadata: Vec<(&'a str, &'a str)>
}

impl<'a> Document<'a> {
  fn new() -> Document<'a> {
    let metadata = vec!(("Title", "Untitled Document 1"));
    Document {metadata: metadata}
  }
}

impl<'a> ToJson for Document<'a> {
  fn to_json(&self) -> Json {
    let mut tm = TreeMap::new();
    for &(ref mk, ref mv) in self.metadata.iter() {
      tm.insert(mk.to_string(), mv.to_string().to_json());
    }
    json::Object(tm)
  }
}

struct WordDocument<'a> {
  metadata: Vec<(&'a str, &'a str)>
}

impl<'a> WordDocument<'a> {
  fn new() -> WordDocument<'a> {
    let metadata = vec!(("Title", "Untitled Word Document 1"));
    WordDocument {metadata: metadata}
  }
}

impl<'a, E, S: Encoder<E>> Encodable<S, E> for WordDocument<'a> {
  fn encode(&self, s: &mut S) -> Result<(), E> {
    s.emit_map(self.metadata.len(), |e| {
      let mut i = 0;
      for &(ref key,  ref val) in self.metadata.iter() {
        try!(e.emit_map_elt_key(i, |e| key.encode(e)));
        try!(e.emit_map_elt_val(i, |e| val.encode(e)));
        i += 1;
      }
      Ok(())
    })
  }
}

Rust playpen: http://is.gd/r7cYmE

Results:

1. Deriving `Encodable`: {"metadata":[["Title","Untitled Document 1"]]}
2. Manually implementing `ToJson` trait: {"Title":"Untitled Document 1"}
3. Manually implementing `Encodable` trait: {"Title":"Untitled Word Document 1"}

First method is fully automatic, but does not provide sufficient flexibility. Second and third achieve same level of flexibility by specifying the serialization process manually. In my case I want document metadata to be serialized as an object, not as an array (which is what deriving implementation gives me).

Questions:

  1. Why do methods 2 and 3 exist at all? I don't understand the reason for the overlap between them. I would expect there to exist only one automatic (deriving) method of serialization and one manual.
  2. If I want manual serialization, which method should I choose and why?

  3. Am I right in assuming that method 2 will build a Json enum in memory (besides the struct itself) and is a worse fit for huge documents (multi megabytes), while method 3 is streaming and safer for huge documents?

  4. Why does rust stdlib use method 3 even for primitives, while not using method 2 internally?

like image 726
Valentin V Avatar asked Aug 11 '14 07:08

Valentin V


Video Answer


1 Answers

Why do methods 2 and 3 exist at all? I don't understand the reason for the overlap between them. I would expect there to exist only one automatic (deriving) method of serialization and one manual.

Method 2 (the ToJson trait) is specific to encoding JSON. It returns JSON objects, instead of writing to a stream. One example of use is for mapping to custom representations - see this example in the documentation.

Method 3 (implementing Encodable) has to exist for method 1 to work.

Am I right in assuming that method 2 will build a Json enum in memory (besides the struct itself) and is a worse fit for huge documents (multi megabytes), while method 3 is streaming and safer for huge documents?

Yes. ToJson creates a nested Json enum of the whole object, while Encodable streams to a Writer.

If I want manual serialization, which method should I choose and why?

Why does rust stdlib use method 3 even for primitives, while not using method 2 internally?

You should use Encodable. It is not specific to the JSON format, does not use an intermediate representation (so can be streamed instead of stored in memory) and should continue to work with new formats added to the library.

like image 79
borntyping Avatar answered Oct 22 '22 05:10

borntyping