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:
Deriving Encodable trait
Manual implementation of ToJson trait
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:
- 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.
If I want manual serialization, which method should I choose and why?
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?Why does rust stdlib use method 3 even for primitives, while not using method 2 internally?
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.Yes.
ToJson
creates a nestedJson
enum of the whole object, whileEncodable
streams to aWriter
.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.