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?
deserialize_with
attributeThe 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
.
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.
from
and try_from
attributesYou can also place the custom conversion logic from above into a From
or TryFrom
implementation, then instruct Serde to make use of that via the from
or try_from
attributes:
#[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)
}
}
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With