admin管理员组

文章数量:1344241

I'm really impressed with how elegantly and easily serde / serde_json can parse messages:

{"msg_type": "asauce", "aaa": 3, "bbb": 14}
{"msg_type": "csyrup", "ccc": 10, "ddd": 20}
// this works!
#[derive(serde::Deserialize, PartialEq, Debug)]
struct AppleSauce {
    aaa: u8,
    bbb: u8,
}

#[derive(serde::Deserialize, PartialEq, Debug)]
struct ChocolateSyrup {
    ccc: u8,
    ddd: u8,
}

#[derive(serde::Deserialize, PartialEq, Debug)]
#[serde(tag = "msg_type")]
enum Sauce {
    #[serde(rename = "asauce")]
    AppleSauce(AppleSauce),
    #[serde(rename = "csyrup")]
    ChocolateSyrup(ChocolateSyrup),
}

#[test]
fn deserialise_inner_applesauce() {
    let json = r#"{"msg_type": "asauce", "aaa": 3, "bbb": 14}"#;
    let expected = AppleSauce { aaa: 3, bbb: 14 };
    let sauce: Sauce = serde_json::from_str(json).unwrap();
    assert_eq!(sauce, Sauce::AppleSauce(expected));
}

However my messages have a parent key, which I'd like to be rid of:

{"boilerplate": {"msg_type": "asauce", "aaa": 3, "bbb": 14}}
{"boilerplate": {"msg_type": "csyrup", "ccc": 10, "ddd": 20}}

For the moment I've implemented a from_str method:

impl Sauce {
    pub fn from_str(msg: &str) -> Option<Sauce> {
        let outer: serde_json::Value = serde_json::from_str(msg).ok()?;
        let inner = &outer["boilerplate"];
        serde_json::from_value(inner.clone()).ok()
    }
}

#[test]
fn deserialise_boilerplate_applesauce_with_helper_method() {
    let json = r#"{"boilerplate": {"msg_type": "asauce", "aaa": 3, "bbb": 14}}"#;
    let expected = AppleSauce { aaa: 3, bbb: 14 };
    let sauce = Sauce::from_str(json).unwrap(); // not serde_json::from_str :(
    assert_eq!(sauce, Sauce::AppleSauce(expected));
}

Is there a way to elide the parent key with a serde attribute, and eliminate the custom from_str method?

I only need to deserialise these messages (but from what I see of serde, it would probably serialise too for free).

I'm really impressed with how elegantly and easily serde / serde_json can parse messages:

{"msg_type": "asauce", "aaa": 3, "bbb": 14}
{"msg_type": "csyrup", "ccc": 10, "ddd": 20}
// this works!
#[derive(serde::Deserialize, PartialEq, Debug)]
struct AppleSauce {
    aaa: u8,
    bbb: u8,
}

#[derive(serde::Deserialize, PartialEq, Debug)]
struct ChocolateSyrup {
    ccc: u8,
    ddd: u8,
}

#[derive(serde::Deserialize, PartialEq, Debug)]
#[serde(tag = "msg_type")]
enum Sauce {
    #[serde(rename = "asauce")]
    AppleSauce(AppleSauce),
    #[serde(rename = "csyrup")]
    ChocolateSyrup(ChocolateSyrup),
}

#[test]
fn deserialise_inner_applesauce() {
    let json = r#"{"msg_type": "asauce", "aaa": 3, "bbb": 14}"#;
    let expected = AppleSauce { aaa: 3, bbb: 14 };
    let sauce: Sauce = serde_json::from_str(json).unwrap();
    assert_eq!(sauce, Sauce::AppleSauce(expected));
}

However my messages have a parent key, which I'd like to be rid of:

{"boilerplate": {"msg_type": "asauce", "aaa": 3, "bbb": 14}}
{"boilerplate": {"msg_type": "csyrup", "ccc": 10, "ddd": 20}}

For the moment I've implemented a from_str method:

impl Sauce {
    pub fn from_str(msg: &str) -> Option<Sauce> {
        let outer: serde_json::Value = serde_json::from_str(msg).ok()?;
        let inner = &outer["boilerplate"];
        serde_json::from_value(inner.clone()).ok()
    }
}

#[test]
fn deserialise_boilerplate_applesauce_with_helper_method() {
    let json = r#"{"boilerplate": {"msg_type": "asauce", "aaa": 3, "bbb": 14}}"#;
    let expected = AppleSauce { aaa: 3, bbb: 14 };
    let sauce = Sauce::from_str(json).unwrap(); // not serde_json::from_str :(
    assert_eq!(sauce, Sauce::AppleSauce(expected));
}

Is there a way to elide the parent key with a serde attribute, and eliminate the custom from_str method?

I only need to deserialise these messages (but from what I see of serde, it would probably serialise too for free).

Share Improve this question asked 8 hours ago Jack DeethJack Deeth 3,3674 gold badges28 silver badges44 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 2

Is there a way to elide the parent key with a serde attribute

No, there is no attribute that will do this. You would need to define another type, like:

#[derive(serde::Deserialize)]
struct OuterSauce {
    boilerplate: Sauce,
}

And then deserialize that instead. Alternatively, you would need a custom Deserialize implementation, but that gets unwieldy really quickly, even moreso than having another type layer.

You can make structs (SauceSerde and SauceWrapper) that reflect the JSON, and then impl From<SauceWrapper> for Sauce to handle the conversion. Then, you can add #[serde(from = "SauceWrapper")] so that deserializing Sauce uses SauceWrapper's deserialization logic.

#[derive(serde::Deserialize, PartialEq, Debug)]
pub struct AppleSauce {
    aaa: u8,
    bbb: u8,
}

#[derive(serde::Deserialize, PartialEq, Debug)]
pub struct ChocolateSyrup {
    ccc: u8,
    ddd: u8,
}

#[derive(serde::Deserialize, PartialEq, Debug)]
#[serde(tag = "msg_type")]
// Rename this and keep it private
enum SauceSerde {
    #[serde(rename = "asauce")]
    AppleSauce(AppleSauce),
    #[serde(rename = "csyrup")]
    ChocolateSyrup(ChocolateSyrup),
}

// Make a copy and use `from = "SauceWrapper"`
// Also put `into = "SauceWrapper"` if serializing is needed
#[derive(serde::Deserialize, PartialEq, Debug)]
#[serde(from = "SauceWrapper")]
pub enum Sauce {
    AppleSauce(AppleSauce),
    ChocolateSyrup(ChocolateSyrup),
}

// Make a private wrapper to match the JSON
#[derive(serde::Deserialize, PartialEq, Debug)]
struct SauceWrapper {
    boilerplate: SauceSerde,
}

impl From<SauceWrapper> for Sauce {
    fn from(value: SauceWrapper) -> Self {
        match value.boilerplate {
            SauceSerde::AppleSauce(apple_sauce) => Sauce::AppleSauce(apple_sauce),
            SauceSerde::ChocolateSyrup(chocolate_syrup) => Sauce::ChocolateSyrup(chocolate_syrup),
        }
    }
}

#[test]
fn deserialise_inner_applesauce() {
    let json = r#"{"boilerplate": {"msg_type": "asauce", "aaa": 3, "bbb": 14}}"#;
    let expected = AppleSauce { aaa: 3, bbb: 14 };
    let sauce: Sauce = serde_json::from_str(json).unwrap();
    assert_eq!(sauce, Sauce::AppleSauce(expected));
}

本文标签: rustEliding boilerplate parent JSON key with serdeStack Overflow