Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optionally skip serializing a field with Serde?

Tags:

rust

serde

I have two structs:

#[derive(Serialize)]
struct Post {
    title: String,
    // ...more fields...,
    comments: Vec<Comment>,
}

#[derive(Serialize)]
struct Comment {
    body: String,
    // ...more fields...,
}

I want to generate 2 kinds of JSON files:

  1. A JSON index of a Vec<Post> which should include all fields except comments.
  2. A JSON of a Post which includes all fields.

Is it possible to achieve this with the Serialize derive attribute? I found skip_serializing_if attribute in Serde's documentation but as far as I can see, it's not useful for me because I want to skip not based on the value of the field but based on which JSON file I'm generating.

Right now I'm generating the index using the json! macro which requires manually listing all the fields of Post but I'm hoping there's a better way to do this.

like image 529
Dogbert Avatar asked Jan 05 '18 06:01

Dogbert


People also ask

Is Serde fast?

Serde. Serde, the incumbent serialization/deserialization library, is elegant, flexible, fast to run, and slow to compile.

What is Serde serialization?

Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things.

Is Serde slow?

Serde is really designed to be fast, by allowing fully statically dispatched operations without runtime reflections so formats and types are decoupled at code but transparent to the optimization. It's serde_json which cares all the weirdnesses of the JSON format.

What is Serde JSON?

Serde JSON JSON is a ubiquitous open-standard format that uses human-readable text to transmit data objects consisting of key-value pairs.


1 Answers

I want to generate 2 kinds of JSON files

I read that as "2 types of JSON files", so I turn towards that as a solution. I'd create wrapper types custom-fit to each context. These can take references to the original type to avoid too much memory overhead:

#[derive(Serialize)]
struct LightweightPost<'a> {
    title: &'a String,
}

impl<'a> From<&'a Post> for LightweightPost<'a> {
    fn from(other: &'a Post) -> Self {
        LightweightPost {
            title: &other.title,
        }
    }
}

fn main() {
    let posts = vec![
        Post {
            title: "title".into(),
            comments: vec![Comment { body: "comment".into() }],
        },
    ];

    let listing: Vec<_> = posts.iter().map(LightweightPost::from).collect();

    println!("{}", serde_json::to_string(&listing).unwrap());
    // [{"title":"title"}]

    println!("{}", serde_json::to_string(&posts[0]).unwrap());
    // {"title":"title","comments":[{"body":"comment"}]}
}

playground


Editorially, I've found this type of multiple-type structure very useful when writing web apps in Ruby, using the roar gem. These new types allow for places to hang behavior specific to certain contexts such as validation or persistence.

like image 173
Shepmaster Avatar answered Oct 16 '22 01:10

Shepmaster