简体   繁体   中英

Serde JSON deserializing enums

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?

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

Assuming there is no information left from OP. Using string replace functionality before you deserialze is an option.

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize,Debug,Clone)]
enum Action {
    Join,
    Leave,
}
#[derive(Serialize, Deserialize,Debug,Clone)]
struct Message {
    action: Action,
}

fn main(){
    let json_message= "{\"action\":0}";
    let refactored = json_message.to_string().replace("0","\"Join\"");
    let msg: Message = serde_json::from_str(&refactored).unwrap();
    dbg!("{:?}",msg.clone());

    let test=serde_json::to_string(&msg).unwrap();
        dbg!("{:?}",test);

}

You can replace 0 to \"Join\" and then deserialize it. [Permalink Plaground]

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM