I have a structure made of u64/ u32 / ... that I need to deserialize from a JSON file; the twist is those integers are actually hex encoded strings such as "0x1337".
I have been able to annotate fields with deserialize_with and my own function that looks like the below which sounds reasonable:
fn x<'de, D, T>(d: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: TryFrom<u128, Error: Display>,
{
struct MyVisitor<T>(PhantomData<T>);
impl<'de, T> Visitor<'de> for MyVisitor<T>
where
T: TryFrom<u128, Error: Display>,
{
type Value = T;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("expect an hexadecimal / decimal integer value as a string")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
let (s, radix) = if let Some(stripped) = v.strip_prefix("0x") {
(stripped, 16)
} else {
(v, 10)
};
let t = u128::from_str_radix(s, radix).map_err(E::custom)?;
t.try_into().map_err(E::custom)
}
}
d.deserialize_str(MyVisitor(PhantomData))
}
Now what's more annoying is when I have [u64; N] or any other containers containing those integer types; I need to define a different function for each of those; here's for example how I handle the array type:
fn ax<'de, D, T, const N: usize>(d: D) -> Result<[T; N], D::Error>
where
D: Deserializer<'de>,
T: TryFrom<u128, Error: Display> + Copy + Default + Deserialize<'de>,
{
struct MyVisitor<T, const N: usize>(PhantomData<[T; N]);
impl<'de, T, const N: usize> Visitor<'de> for MyVisitor<T, N>
where
T: TryFrom<u128, Error: Display> + Copy + Default + Deserialize<'de>,
{
type Value = [T; N];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("expect an hexadecimal / decimal integer value as a string")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut res = [Default::default(); N];
let mut idx = 0;
while let Some(v) = seq.next_element::<String>()? {
let (s, radix) = if let Some(stripped) = v.strip_prefix("0x") {
(stripped, 16)
} else {
(v.as_str(), 10)
};
let t = u128::from_str_radix(s, radix).map_err(A::Error::custom)?;
res[idx] = t.try_into().map_err(A::Error::custom)?;
idx += 1;
}
if idx != N {
Err(A::Error::custom(format!(
"expected {N} entries in the sequence but got {}",
idx
)))
} else {
Ok(res)
}
}
}
d.deserialize_seq(MyVisitor(PhantomData))
}
This seems a bit more involved and probably like I'm doing it wrong. My problem could also be solved by defining new struct types and replacing u64 etc. by those new types, but then it's a little bit awkward for the calling code that needs to handle those types so I didn't take that route.
At this point I have two questions:
Is this what you would recommend for me to do?
Would it make sense, if even possible, to have an attribute that document how you should deserialize contained elements in a containers (like key / values)?
I have a structure made of
u64
/u32
/ ... that I need to deserialize from a JSON file; the twist is those integers are actually hex encoded strings such as"0x1337"
.I have been able to annotate fields with
deserialize_with
and my own function that looks like the below which sounds reasonable:Now what's more annoying is when I have
[u64; N]
or any other containers containing those integer types; I need to define a different function for each of those; here's for example how I handle the array type:This seems a bit more involved and probably like I'm doing it wrong. My problem could also be solved by defining new struct types and replacing
u64
etc. by those new types, but then it's a little bit awkward for the calling code that needs to handle those types so I didn't take that route.At this point I have two questions:
Here is an example code that compiles and shows an example of what I'm trying to achieve: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ced1c8bc143848aa9e006311259523e1. I'm also a Rust/serde noob so maybe I'm also doing something fundamentally wrong 😅.
Thanks again!
Cheers