Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
0vercl0k opened this issue Jun 17, 2024 · 0 comments
Open

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

0vercl0k opened this issue Jun 17, 2024 · 0 comments

Comments

@0vercl0k
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

1 participant