How to transform fields during deserialization usi

2020-01-23 04:58发布

I'm using Serde to deserialize an XML file which has the hex value 0x400 as a string and I need to convert it to the value 1024 as a u32.

Do I need to implement the Visitor trait so that I separate 0x and then decode 400 from base 16 to base 10? If so, how do I do that so that deserialization for base 10 integers remains intact?

标签: rust hex serde
1条回答
Anthone
2楼-- · 2020-01-23 05:45

The deserialize_with attribute

The easiest solution is to use the Serde field attribute deserialize_with to set a custom serialization function for your field. You then can get the raw string and 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

Note how this can use any other existing Serde implementation to decode. Here, we decode to a string slice (let s: &str = Deserialize::deserialize(deserializer)?). You can also create intermediate structs that map directly to your raw data, derive Deserialize on them, then deserialize to them inside your implementation of Deserialize.

Implement serde::Deserialize

From here, it's a tiny step to promoting it to your own type to allow reusing it:

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

This method allows you to also add or remove fields as the "inner" deserialized type can do basically whatever it wants.

See also:

查看更多
登录 后发表回答