4

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?

2
  • Try this play.rust-lang.org/… Commented Sep 19, 2022 at 3:51
  • You can also add an match statement if needed so Commented Sep 19, 2022 at 3:52

2 Answers 2

5

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, } 
Sign up to request clarification or add additional context in comments.

Comments

5

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>, { match i8::deserialize(deserializer)? { 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, { serializer.serialize_i8(match self { Action::Join => 0, Action::Leave => 1, }) } } 

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

3 Comments

Sweet that's a great option thanks!
Doesn't deserialize go into recursive call?
@Voilin No: <Action as Deserialize>::deserialize calls <i8 as Deserialize>::deserialize. Accordingly, the playground terminates. (I'm not sure why I didn't do it consistently between serializer and deserializer, I could have just as well used 0i8.serialize(serializer) (on the other hand, deserializer.deserialize_i8() would have required implementing a visitor. Nope.))

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.