I'm parsing an INI-style file that uses integers for enumerators.
#[derive(Debug, Deserialize, Serialize)]
pub enum MyThing {
First = 0,
Second = 1,
Third = 2,
}
In the file, the value will get serialized like so:
thing=0
However, Serde by default matches against the variant name rather than the discriminant. Is custom-implementing Deserialize
the cleanest method?
The Serde website has an entire example on how to serialize an enum as a number:
extern crate serde;
extern crate serde_json;
use std::fmt;
macro_rules! enum_number {
($name:ident { $($variant:ident = $value:expr, )* }) => {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum $name {
$($variant = $value,)*
}
impl ::serde::Serialize for $name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: ::serde::Serializer
{
// Serialize the enum as a u64.
serializer.serialize_u64(*self as u64)
}
}
impl<'de> ::serde::Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: ::serde::Deserializer<'de>
{
struct Visitor;
impl<'de> ::serde::de::Visitor<'de> for Visitor {
type Value = $name;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("positive integer")
}
fn visit_u64<E>(self, value: u64) -> Result<$name, E>
where E: ::serde::de::Error
{
// Rust does not come with a simple way of converting a
// number to an enum, so use a big `match`.
match value {
$( $value => Ok($name::$variant), )*
_ => Err(E::custom(
format!("unknown {} value: {}",
stringify!($name), value))),
}
}
}
// Deserialize the enum from a u64.
deserializer.deserialize_u64(Visitor)
}
}
}
}
enum_number!(SmallNumber {
Zero = 0,
One = 1,
Two = 2,
Three = 3,
});
fn main() {
use SmallNumber::*;
let nums = vec![Zero, One, Two, Three];
// Prints [0,1,2,3]
println!("{}", serde_json::to_string(&nums).unwrap());
assert_eq!(Two, serde_json::from_str("2").unwrap());
}
I would believe that this is the best way to do it as it's recommended by the crate authors themselves.