rust - Eliding boilerplate parent JSON key with serde - Stack Overflow

I'm really impressed with how elegantly and easily serdeserde_json can parse messages:{"m

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));
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1743745450a4499922.html

相关推荐

  • rust - Eliding boilerplate parent JSON key with serde - Stack Overflow

    I'm really impressed with how elegantly and easily serdeserde_json can parse messages:{"m

    6天前
    20

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信