Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I support an unknown or other value for a Serde enum?

Tags:

rust

serde

I have a JSON API that returns an object that looks like this:

{
  "PrivatePort": 2222,
  "PublicPort": 3333,
  "Type": "tcp"
}

To capture this, I have an enum and a struct:

#[derive(Eq, PartialEq, Deserialize, Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PortType {
    Sctp,
    Tcp,
    Udp,
}

#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct PortMapping {
    pub private_port: u16,
    pub public_port: u16,
    #[serde(rename = "Type")]
    pub port_type: PortType,
}

Right now, this API only supports the three protocols listed in PortType, but let's assume that support for DCCP is added in the future. I do not want clients of the API to start failing simply because of an unknown string in a configuration option they might not be looking at.

To address this, I've added an Unknown variant with a String to represent the value:

#[derive(Eq, PartialEq, Deserialize, Serialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PortType {
    Sctp,
    Tcp,
    Udp,
    Unknown(String),
}

The goal here is to end up with the slightly-inconvenient PortType::Unknown("dccp") value when an unknown value is passed in. Of course, this does not do what I would like out-of-box -- passing the unknown "dccp" value will result in:

Error("unknown variant `dccp`, expected one of `sctp`, `tcp`, `udp`, `unknown`", line: 1, column: 55)

Is there a Serde configuration for doing what I want or should I resort to manually writing Deserialize and Serialize implementations for PortType?

like image 1000
Travis Gockel Avatar asked Aug 12 '19 23:08

Travis Gockel


People also ask

How does SerDe work rust?

Serde is a framework for serializing and deserializing Rust data structures efficiently and generically. The Serde ecosystem consists of data structures that know how to serialize and deserialize themselves along with data formats that know how to serialize and deserialize other things.

What is SerDe JSON?

The Hive JSON SerDe is commonly used to process JSON data like events. These events are represented as single-line strings of JSON-encoded text separated by a new line. The Hive JSON SerDe does not allow duplicate keys in map or struct key names.

What is serialization and Deserialization in Rust?

Rustc has to serialize and deserialize various data during compilation. Specifically: "Crate metadata", mainly query outputs, are serialized in a binary format into rlib and rmeta files that are output when compiling a library crate, these are then deserialized by crates that depend on that library.


2 Answers

Try use serde-enum-str

#[derive(serde_enum_str::Deserialize_enum_str, serde_enum_str::Serialize_enum_str)]
#[serde(rename_all = "snake_case")]
pub enum PortType {
    Sctp,
    Tcp,
    Udp,
    #[serde(other)]
    Unknown(String),
}
like image 182
vkill Avatar answered Oct 05 '22 23:10

vkill


There's an issue for this, though it's been open for 3 years with no full resolution so far. Serde #912.

What seems to be currently implemented (though undocumented) at the time of this post is #[serde(other)]. It can only be applied to unit enum fields, which limits its usefulness:

#[derive(Deserialize, PartialEq)]
#[serde(tag = "tag")]
enum Target {
   A(()),
   B(()),
   #[serde(other)]
   Others
}

fn main() {
    assert_eq!(Target::Others, from_str::<Target>(r#"{ "tag": "blablah" }"#).unwrap());
}

Other than that, the only other method as of this writing is writing your own Deserialize implementation.

like image 45
Colonel Thirty Two Avatar answered Oct 05 '22 23:10

Colonel Thirty Two