Home > Net >  Serde JSON deserializing enums
Serde JSON deserializing enums

Time:09-19

I have an enum:

#[derive(Serialize, Deserialize)]
enum Action {
    Join,
    Leave,
}

and a struct:

#[derive(Serialize, Deserialize)]
struct Message {
    action: Action,
}

and I pass a JSON string:

"{\"action\":0}" // `json_string` var

but when I try deserialzing this like this:

let msg: Message = serde_json::from_str(json_string)?;

I get the error expected value at line 1 column 11.

In the JSON if I were to replace the number 0 with the string "Join" it works, but I want the number to correspond to the Action enum's values (0 is Action::Join, 1 is Action::Leave) since its coming from a TypeScript request. Is there a simple way to achieve this?

CodePudding user response:

You want serde_repr!

Here's example code from the library's README:

use serde_repr::{Serialize_repr, Deserialize_repr};

#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)]
#[repr(u8)]
enum SmallPrime {
    Two = 2,
    Three = 3,
    Five = 5,
    Seven = 7,
}

fn main() -> serde_json::Result<()> {
    let j = serde_json::to_string(&SmallPrime::Seven)?;
    assert_eq!(j, "7");

    let p: SmallPrime = serde_json::from_str("2")?;
    assert_eq!(p, SmallPrime::Two);

    Ok(())
}

For your case:

use serde_repr::{Serialize_repr, Deserialize_repr};

#[derive(Serialize_repr, Deserialize_repr)]
#[repr(u8)]
enum Action {
    Join = 0,
    Leave = 1,
}

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Message {
    action: Action,
}

CodePudding user response:

Without adding any extra dependencies, the least verbose way is possibly to use "my favourite serde trick", the try_from and into container attributes. But in this case I feel that custom implementations of Deserialize and Serialize are more appropriate:

impl<'de> Deserialize<'de> for Action {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let i = i8::deserialize(deserializer)?;
        match i {
            0 => Ok(Action::Join),
            1 => Ok(Action::Leave),
            _ => Err(serde::de::Error::custom("Expected 0 or 1 for action")),
        }
    }
}
impl Serialize for Action {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self {
            Action::Join => serializer.serialize_i8(0),
            Action::Leave => serializer.serialize_i8(1),
        }
    }
}

The custom implementations only redirect to serializing/deserializing i8. Playground

  • Related