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>>
}
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