serde-rs / serde

Serialization framework for Rust
https://serde.rs/
Apache License 2.0
8.81k stars 747 forks source link

Q: Best way to deserialize a primitive type in containers #2756

Open 0vercl0k opened 2 weeks ago

0vercl0k commented 2 weeks ago

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:

  1. Is this what you would recommend for me to do?
  2. 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)?

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