dtolnay / request-for-implementation

Crates that don't exist, but should
610 stars 6 forks source link

Derive macros to serialize and deserialize struct as an array of values #3

Closed dtolnay closed 5 years ago

dtolnay commented 5 years ago

By default, Serde will serialize a struct to JSON as a JSON map like {"s":"","i":0,"b":true}. In some cases it can be preferable to serialize as a JSON array like ["",0,true]. Serde serializes tuple structs like this, but then we lose the field names when working with the type in Rust code.

I would like there to be derive macros Serialize_tuple and Deserialize_tuple that let me write an ordinary braced struct with named fields but serialize it as if it were a tuple struct.

#[derive(Serialize_tuple, Deserialize_tuple)]
struct T {
    s: String,
    i: i32,
    b: bool,
}

See serde_repr as an existing example of an independent special-purpose derive macro for Serde traits.

// generated code

impl serde::Serialize for T {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        #[derive(serde::Serialize)]
        #[serde(rename = "T")]
        struct Helper<'a>(&'a String, &'a i32, &'a bool);

        let helper = Helper(&self.s, &self.i, &self.b);
        serde::Serialize::serialize(&helper, serializer)
    }
}

impl<'de> serde::Deserialize<'de> for T {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        #[derive(serde::Deserialize)]
        #[serde(rename = "T")]
        struct Helper(String, i32, bool);

        let helper: Helper = serde::Deserialize::deserialize(deserializer)?;
        Ok(T {
            s: helper.0,
            i: helper.1,
            b: helper.2,
        })
    }
}
kardeiz commented 5 years ago

I found this issue from https://github.com/serde-rs/serde/issues/637 while working on a JSONRPC related mechanism for deserializing a struct by name or position.

I've made a first attempt at this: https://github.com/kardeiz/serde_tuple, though it is still a little rough.

My implementation also lets you specify output position (e.g., if you want to change the order of values in the output array). It also offers a DeserializeMaybeTuple to deserialize from either the tuple or named style.

It is currently limited to no generics. I think this could be pretty easily lifted to just "no reference fields". It might be possible to be open to any serde-capable type, but I'm not sure if that is really feasible.

Question: is there a reason to prefer Serialize_tuple over SerializeTuple? I've chosen the latter as I think it looks nicer, but I'm open to changing it.

dtolnay commented 5 years ago

Thanks for taking a look at this. Some notes on your implementation:

kardeiz commented 5 years ago

Thanks for the comments!

I've never really thought about struct field position as meaningful, but that makes sense: the position attribute has to be in the struct definition, so you might as well go ahead and change the field order there.

I've been using serde for years, and I had no idea it would deserialize a tuple to a struct with named fields!

I've updated the names. I've also made everything generic (hopefully in a way that covers all cases).

https://github.com/kardeiz/serde_tuple/blob/master/src/lib.rs

dtolnay commented 5 years ago

Nicely done. Let me know once the crate is documented and published to crates.io and I'll close out this issue.

kardeiz commented 5 years ago

Thanks! See https://crates.io/crates/serde_tuple.

dtolnay commented 5 years ago

Fantastic. I marked off the idea in the readme with a link to your crate.