7
votes

I have a data structure Document, which I would like to serialize other Rust structs to. It is basically a HashMap for the fields internally, however it interacts with a database API, so I will definitely want to convert other types into those Documents.

For example this struct

struct Entry {
    id: String,
    user: String,
    duration: u32,
    location: (f64, f64),
}

I already have a conversion to the Document type using the From trait, however this is an extra place I have to modify when the Entry struct changes. The implementation uses a DocumentBuilder and looks like this:

impl From<Entry> for Document {
    fn from(entry: Entry) -> Self {
        Document::builder()
            .name(&entry.id)           // set the name of the document
            .field("user", entry.user) // add fields ...
            .field("duration", entry.duration)
            .field("location", entry.location)
            .build()                   // build a Document

    }
}

The field method can assign any value which can be converted to a FieldValue to a key. So the signature of field is:

impl DocumentBuilder {
    // ...
    pub fn field<T: Into<FieldValue>>(mut self, key: &str, value: T) -> Self { ... }
    // ...
}

I would like to use serde and its derive feature to automatically serialize the struct and its fields into a Document. How would I go about doing this? I looked at the wiki for Implementing a Serializer but the example shown writes to a string and I would like to know how I can serialize to a data structure using the builder pattern.

1
I swear this has been asked before; I'm positive I misread that question and suggested serde-transcode before understanding the goal. I believe my suggestion there was just to use From, as you have.Shepmaster

1 Answers

6
votes

The simplest way to do this would be to use serde_json::from_value (applicable even if you're not using JSON, but requires all fields to be valid JSON [e.g. no non-string keys in hashmaps]):

let entry = Entry {
    a: 24,
    b: 42,
    c: "nice".to_string()
};
let v = serde_json::to_value(&entry).unwrap();
let document: Document = serde_json::from_value(v).unwrap();

Caveat: the value type of Document will have to implement Deserialize, and in a way that can deserialize any value into the correct argument. This can be done by using #[serde(untagged)], but may be prone to certain types being wrong, such as u8 being converted to u64.

Full playground example

A more sophisticated method that doesn't involve any unnecessary copies will require you to write a custom (De)serializer, and a good one to look at would be serde_transcode::transcode, which does the inverse of the thing you want - it converts between two data formats.