Search code examples
jsonruststructdeserializationserde

Rust deserialize Vec of u32 to Vec of enum


I'm looking for a good way to deserialize a Vec of u32 to a Vec of enum. So basically I'm receiving a json object like this:

{
  "Account": "r...",
  "Flags": 20,
  ...
}

The struct I'm trying to deserialize it into looks something like this:

#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all(serialize = "PascalCase", deserialize = "snake_case"))]
pub struct Foo<'a> {
  account: &'a str,
  flags: Option<Vec<FooFlag>>
}

FooFlag looks something like this:

#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize, Display, AsRefStr)]
pub enum FooFlag {
  Example1 = 5,
  Example2 = 15,
  ...
}

Now after I've received the json I want to derive the Vec<FooFlag> from that u32 Flags value. So for this example it should be vec![FooFlag::Example1, FooFlag::Example2] (5 + 15 = 20). How I decide that these are the two enums doesn't matter for now. I would like to have a deserialization function that I could use for multiple structs like Foo. I know there are crates like serde_repr for C-like enums but I didn't manage to put it together in a generic way.


So far I've written a mod:

mod flag_conversion {
    use alloc::vec::Vec;
    use serde::{Deserializer, Serializer, Deserialize};

    fn serialize<'a, T, S>(flags: &'a Option<Vec<T>>, s: S) -> Result<S::Ok, S::Error>
    where
        T: Into<u32>,
        S: Serializer,
    {
        s.serialize_option(
            {
                if let Some(flags) = &flags {
                    let transaction_flags: Vec<u32> = flags.into_iter().map(|&flag| {
                        let f = flag;
                        let n = f.into();
                        n
                    }).collect();
                    transaction_flags.iter().sum::<u32>()
                } else {
                    0
                }
            }
        )
    }

    fn deserialize<'de, D>(d: D) -> Result<D::Ok, D::Error>
    where
        D: Deserializer<'de>,
    {
        let specified_flags = u32::deserialize(d).unwrap();
        d.deserialize_u32(
    {
                if specified_flags == 0 {
                    None
                } else {
                    todo!()  // convert to `Vec` of `...Flag` enum
                }
            }
        )
    }
}

So I can use the module in the Foo struct like this:

#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all(serialize = "PascalCase", deserialize = "snake_case"))]
pub struct Foo<'a> {
  account: &'a str,
  #[serde(with = "flag_conversion")]
  flags: Option<Vec<FooFlag>>
}

Solution

  • I think you're quite close to getting this working.

    The key is adding the #[serde(with="some_module")] attribute to the flags field. you need to implement the serialization/deserialization of the Option<Vec<FooFlag>> in that module. (And you can remove Serialize/Deserialize from the FooFlag struct.)

    I'm not sure exactly how you want your ints to convert to flags, so I've hard coded that section of code. I'd usually be using power of 2 for the flags which you're not doing...

    use serde::Deserializer;
    use serde::Serializer;
    use serde::{Deserialize, Serialize}; // 1.0.147
    use serde_json; // 1.0.87
    
    use std::fmt::Display;
    
    #[derive(Debug, Serialize, Deserialize)]
    #[serde(rename_all(serialize = "PascalCase", deserialize = "PascalCase"))]
    pub struct Foo<'a> {
        account: &'a str,
    
        #[serde(with = "foo_flag_serde")]
        flags: Option<Vec<FooFlag>>,
    }
    
    #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
    pub enum FooFlag {
        Example1 = 5,
        Example2 = 15,
    }
    
    impl FooFlag {
        pub fn from_u32(d: u32) -> Vec<FooFlag> {
            vec![FooFlag::Example1, FooFlag::Example2]
        }
    
        pub fn to_u32(v: &[FooFlag]) -> u32 {
            123
        }
    }
    
    pub mod foo_flag_serde {
        use super::*;
    
        pub fn serialize<S>(flags: &Option<Vec<FooFlag>>, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let v: Option<u32> = flags.as_ref().map(|f| FooFlag::to_u32(&f));
            v.serialize(serializer)
        }
    
        pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<FooFlag>>, D::Error>
        where
            D: Deserializer<'de>,
        {
            let v: Option<u32> = Option::deserialize(deserializer)?;
            Ok(v.map(FooFlag::from_u32))
        }
    }
    
    pub fn main() {
        let data = r#"{"Account": "some_account","Flags": 20}"#;
    
        let deserialized: Foo = serde_json::from_str(&data).unwrap();
    
        println!("deserialized = {:?}", deserialized);
    
        let serialized = serde_json::to_string(&deserialized).unwrap();
    
        println!("serialized = {:?}", serialized);
    }
    

    Working on the rust playground