【问题标题】:How to transform fields during deserialization using Serde?如何使用 Serde 在反序列化期间转换字段?
【发布时间】:2018-03-27 00:12:58
【问题描述】:

我正在使用 Serde 反序列化具有十六进制值 0x400 作为字符串的 XML 文件,我需要将其转换为值 1024 作为 u32

我是否需要实现 Visitor 特征,以便分隔 0x,然后将 400 从基数 16 解码到基数 10?如果是这样,我该怎么做才能使以 10 为底的整数的反序列化保持不变?

【问题讨论】:

    标签: rust hex serde


    【解决方案1】:

    deserialize_with 属性

    最简单的解决方案是使用Serde field attributedeserialize_with 为您的字段设置自定义序列化函数。然后你可以得到原始字符串和convert it as appropriate:

    use serde::{de::Error, Deserialize, Deserializer}; // 1.0.94
    use serde_json; // 1.0.40
    
    #[derive(Debug, Deserialize)]
    struct EtheriumTransaction {
        #[serde(deserialize_with = "from_hex")]
        account: u64, // hex
        amount: u64, // decimal
    }
    
    fn from_hex<'de, D>(deserializer: D) -> Result<u64, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: &str = Deserialize::deserialize(deserializer)?;
        // do better hex decoding than this
        u64::from_str_radix(&s[2..], 16).map_err(D::Error::custom)
    }
    
    fn main() {
        let raw = r#"{"account": "0xDEADBEEF", "amount": 100}"#;
        let transaction: EtheriumTransaction =
            serde_json::from_str(raw).expect("Couldn't derserialize");
        assert_eq!(transaction.amount, 100);
        assert_eq!(transaction.account, 0xDEAD_BEEF);
    }
    

    playground

    注意这如何使用任何其他现有的 Serde 实现来解码。在这里,我们解码为字符串切片 (let s: &amp;str = Deserialize::deserialize(deserializer)?)。您还可以创建直接映射到原始数据的中间结构,在它们上派生 Deserialize,然后在 Deserialize 的实现中反序列化到它们。

    实现serde::Deserialize

    从这里开始,将其提升为您自己的类型以允许重用它只是一小步:

    #[derive(Debug, Deserialize)]
    struct EtheriumTransaction {
        account: Account, // hex
        amount: u64,      // decimal
    }
    
    #[derive(Debug, PartialEq)]
    struct Account(u64);
    
    impl<'de> Deserialize<'de> for Account {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            let s: &str = Deserialize::deserialize(deserializer)?;
            // do better hex decoding than this
            u64::from_str_radix(&s[2..], 16)
                .map(Account)
                .map_err(D::Error::custom)
        }
    }
    

    playground

    此方法还允许您添加或删除字段,因为“内部”反序列化类型基本上可以为所欲为。

    fromtry_from 属性

    您还可以将上面的自定义转换逻辑放入FromTryFrom 实现中,然后通过fromtry_from 属性指示Serde 使用它:

    #[derive(Debug, Deserialize)]
    struct EtheriumTransaction {
        account: Account, // hex
        amount: u64,      // decimal
    }
    
    #[derive(Debug, PartialEq, Deserialize)]
    #[serde(try_from = "IntermediateAccount")]
    struct Account(u64);
    
    #[derive(Deserialize)]
    struct IntermediateAccount<'a>(&'a str);
    
    impl<'a> TryFrom<IntermediateAccount<'a>> for Account {
        type Error = std::num::ParseIntError;
    
        fn try_from(other: IntermediateAccount<'a>) -> Result<Self, Self::Error> {
            // do better hex decoding than this
            u64::from_str_radix(&other.0[2..], 16).map(Self)
        }
    }
    

    另见:

    【讨论】:

    • 感谢@Shepmaster。这正是我正在寻找的。唯一的问题是当我得到这个恐慌消息时对字符串的引用:无效类型:字符串“0x400”,预期是借来的字符串。
    • @phodina 我的两个示例都运行成功,所以显然你已经改变了一些东西。我不是读心术,所以我不知道这种区别是什么。如果 XML 解码器无法提供字符串切片,您可以尝试 let s: String = ...
    • 我已经在操场和我的电脑上尝试了你的两个例子,它们都可以工作。当我将基础格式从 json 更改为 xml 时,问题就出现了。下面是代码(playground 缺少 crate serde_xml_rs) let raw = r#"0xDEADBEEF100"#;让交易:EtheriumTransaction = serde_xml_rs::deserialize(raw.as_bytes()).unwrap();
    • @phodina 是的,看来切换到 let s: String as I suggested earlier 有效。
    猜你喜欢
    • 2020-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-18
    • 2016-04-16
    相关资源
    最近更新 更多