-1

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>>
}
  • What's your question? "I get a wall of compiler errors, how to [fix](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b904ca7ec6a6f011180e9b5627918829)"? Or how to implement converting an u32 into a Vec of flags (I think you need to make your own trait for that.)? – Caesar Nov 11 '22 at 00:08

1 Answers1

0

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

Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
  • Thanks for your reply :) Is there a way that ˋdeserializeˋ returns a generic value so I can use the module for other structures that have other flags? Or would I need to create an own module for each structure I have? – LimpidCrypto Nov 12 '22 at 14:24
  • You can make it a little more general by introducing a `Flag` trait, then making serialize/deserialize work off those, see https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ad1a46f54a5f9281282ebd96d4b9f71b . You could switch away from using`u32` by adding a type to the `Flag` trait, but it would make the serialize/deserialize pair more complicated. (Probably easier to just create Flag32 and Flag64 etc for types with different flag length requirements) – Michael Anderson Nov 14 '22 at 00:23
  • Unfortunately I cannot move away from using u32 because it's extern data I'm receiving. The solution you suggested seems really nice. Thank you – LimpidCrypto Nov 14 '22 at 16:10