Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional field determines which variant #1028

Closed
dtolnay opened this issue Aug 28, 2017 · 3 comments
Closed

Optional field determines which variant #1028

dtolnay opened this issue Aug 28, 2017 · 3 comments
Labels

Comments

@dtolnay
Copy link
Member

dtolnay commented Aug 28, 2017

Moved from #119 (comment):


I'm deserializing JSON-RPCs, which alongside method and params fields, can optionally have an id field. The presence of the id field indicates that the RPC is a request, which requires a response. So in JSON, it might look like this, with the id field optional:

{ 
    "id": 42, // optional
    "method": "item_at_index",
    "params": { 
        "index": 5,
    }
}

Notifications and Requests are represented as distinct types:

#[derive(Serialize, Deserialize)]
#[serde(tag = "method", content = "params")]
#[serde(rename_all = "snake_case")]
enum MyRequests {
    ItemAtIndex { index: usize },
    Length,
}

#[derive(Serialize, Deserialize)]
#[serde(tag = "method", content = "params")]
#[serde(rename_all = "snake_case")]
enum MyNotifications {
    InsertItem { item: Value, index: usize },
    ShrinkToFit,
}

And what I'd like to do is to deserialize based on the presence of the id field, maybe using something like this:

enum JsonRpc<N, R> {
    Request(usize, R),
    Notification(N),
}

Does this fit within the scope of this issue? Is this something I could currently do with a custom deserialize implementation? (It isn't clear to me that I can.)

@dtolnay
Copy link
Member Author

dtolnay commented Aug 28, 2017

This is something you could do with a custom deserialize implementation. Here is one possible approach.

extern crate serde;

#[macro_use]
extern crate serde_derive;

#[macro_use]
extern crate serde_json;

use serde::ser::{self, Serialize, Serializer};
use serde::de::{self, Deserialize, Deserializer};
use serde_json::Value;

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "method", content = "params")]
#[serde(rename_all = "snake_case")]
enum MyRequests {
    ItemAtIndex { index: usize },
    Length,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "method", content = "params")]
#[serde(rename_all = "snake_case")]
enum MyNotifications {
    InsertItem { item: Value, index: usize },
    ShrinkToFit,
}

#[derive(Debug)]
enum JsonRpc<N, R> {
    Request(usize, R),
    Notification(N),
}

impl<N, R> Serialize for JsonRpc<N, R>
    where N: Serialize,
          R: Serialize
{
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: Serializer
    {
        match *self {
            JsonRpc::Request(id, ref r) => {
                let mut v = serde_json::to_value(r).map_err(ser::Error::custom)?;
                v["id"] = json!(id);
                v.serialize(serializer)
            }
            JsonRpc::Notification(ref n) => n.serialize(serializer),
        }
    }
}

impl<'de, N, R> Deserialize<'de> for JsonRpc<N, R>
    where N: Deserialize<'de>,
          R: Deserialize<'de>
{
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where D: Deserializer<'de>
    {
        #[derive(Deserialize)]
        struct IdHelper {
            id: Option<usize>,
        }
        
        let v = Value::deserialize(deserializer)?;
        let helper = IdHelper::deserialize(&v).map_err(de::Error::custom)?;
        match helper.id {
            Some(id) => {
                let r = R::deserialize(v).map_err(de::Error::custom)?;
                Ok(JsonRpc::Request(id, r))
            }
            None => {
                let n = N::deserialize(v).map_err(de::Error::custom)?;
                Ok(JsonRpc::Notification(n))
            }
        }
    }
}

fn main() {
    let j = r#"{
        "id": 42,
        "method": "item_at_index",
        "params": {
            "index": 5
        }
    }"#;
    
    type T = JsonRpc<MyNotifications, MyRequests>;
    println!("{:?}", serde_json::from_str::<T>(j).unwrap());
}

@dtolnay dtolnay mentioned this issue Aug 28, 2017
@cmyr
Copy link

cmyr commented Aug 28, 2017

@dtolnay Looks good, thank you for taking the time to write out the example. Is there any way to do this that lets me use borrowing?

@dtolnay
Copy link
Member Author

dtolnay commented Aug 29, 2017

You would need to use a different intermediate type (instead of serde_json::Value) that has a lifetime parameter but other than that, the code would be the same. I filed arcnmx/serde-value#15 to support this in serde-value.

@dtolnay dtolnay closed this as completed Sep 25, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants