如何使用Serde在反序列化期间转换字段?

49

我正在使用 Serde 对一个包含十六进制值 0x400 的 XML 文件进行反序列化,我需要将它转换为值为 1024u32 类型。

我是否需要实现 Visitor 特性来分离 0x 并将 400 从 16 进制转换为 10 进制?如果是这样,我该如何做到在此过程中保留对于 10 进制整数的反序列化操作?

1个回答

73

deserialize_with属性

最简单的解决方案是使用Serde字段属性 deserialize_with,为您的字段设置自定义序列化函数。然后,您可以获得原始字符串,并且根据需要进行转换

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: &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)
    }
}

演练场

该方法使您可以添加或删除字段,因为“内部”反序列化类型可以做任何想做的事情。

fromtry_from属性

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

#[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)
    }
}

另请参见:


1
感谢 @Shepmaster。这正是我要找的东西。唯一的问题是关于字符串引用的,因为我得到了这个 panic 消息:无效类型:字符串 "0x400",应该是一个借用字符串。 - phodina
@phodina 我的两个示例都成功运行了,所以显然你已经改变了一些东西。我不是心灵读者,所以我不知道那个差异是什么。如果 XML 解码器无法提供字符串切片,你可以尝试使用 let s: String = ... - Shepmaster
我在playground和我的电脑上都尝试了你的两个示例,它们都可以工作。问题出现在我将底层格式从json更改为xml时。这是代码(playground缺少crate serde_xml_rs) let raw = r#"<EtheriumTransaction><account>0xDEADBEEF</account><amount>100</amount></EtheriumTransaction>"#; let transaction: EtheriumTransaction = serde_xml_rs::deserialize(raw.as_bytes()).unwrap(); - phodina
@phodina 是的,看起来切换到 let s: String(就像我之前建议的那样)是有效的。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接