rust 如何支持Serde枚举的未知值或其他值?

wkftcu5l  于 11个月前  发布在  其他
关注(0)|答案(5)|浏览(137)

我有一个JSON API,它返回一个看起来像这样的对象:

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

字符串
为了捕获这个,我有一个枚举和一个结构:

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


目前,这个API只支持PortType中列出的三个协议,但让我们假设将来会添加对DCCP的支持。我不希望API的客户端仅仅因为配置选项中的未知字符串而开始失败。
为了解决这个问题,我添加了一个Unknown变量,用String来表示值:

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


这里的目标是在传入一个未知值时,最终得到一个稍微不方便的PortType::Unknown("dccp")值。当然,这并不像我希望的那样开箱即用--传入未知的"dccp"值将导致:

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


是否有一个Serde配置来做我想要的事情,或者我应该手动编写PortTypeDeserializeSerialize实现?

n8ghc7c1

n8ghc7c11#

尝试使用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),
}

字符串

2ekbmq32

2ekbmq322#

这是一个问题,虽然它已经开放了3年,到目前为止还没有完全解决。
在这篇文章发表的时候,目前似乎已经实现了#[serde(other)](尽管没有文档)。它只能应用于单元枚举字段,这限制了它的实用性:

#[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());
}

字符串
除此之外,在撰写本文时,唯一的其他方法就是编写自己的Deserialize实现。

pokxtpni

pokxtpni3#

我用serde(from=“String”)来做

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

impl From<String> for PortType {
    fn from(s: String)->Self {
        use PortType::*;

        return match s.as_str() {
            "sctp" => Sctp,
            "tcp" => Tcp,
            "udp" => Udp,
            _ => Unknown(s)
        }
    }
}

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

字符串

cld4siwp

cld4siwp4#

简单的情况下应该是罚款与此:

use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::from_str;

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

#[derive(Clone, Eq, PartialEq, Serialize, Debug)]
pub enum PortType {
    Sctp,
    Tcp,
    Udp,
    Unknown(String),
}

const PORT_TYPE: &'static [(&'static str, PortType)] = &[
    ("sctp", PortType::Sctp),
    ("tcp", PortType::Tcp),
    ("udp", PortType::Udp),
];

impl From<String> for PortType {
    fn from(variant: String) -> Self {
        PORT_TYPE
            .iter()
            .find(|(id, _)| *id == &*variant)
            .map(|(_, port_type)| port_type.clone())
            .unwrap_or(PortType::Unknown(variant))
    }
}

impl<'a> From<&'a str> for PortType {
    fn from(variant: &'a str) -> Self {
        PORT_TYPE
            .iter()
            .find(|(id, _)| *id == &*variant)
            .map(|(_, port_type)| port_type.clone())
            .unwrap_or_else(|| PortType::Unknown(variant.to_string()))
    }
}

impl<'de> Deserialize<'de> for PortType {
    fn deserialize<D>(de: D) -> Result<PortType, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct PortTypeVisitor {}

        impl<'de> Visitor<'de> for PortTypeVisitor {
            type Value = PortType;

            fn expecting(
                &self,
                fmt: &mut std::fmt::Formatter<'_>,
            ) -> std::result::Result<(), std::fmt::Error> {
                fmt.write_str("We expected a string")
            }

            fn visit_str<E>(self, variant: &str) -> Result<Self::Value, E> {
                Ok(variant.into())
            }

            fn visit_string<E>(self, variant: String) -> Result<Self::Value, E> {
                Ok(variant.into())
            }
        }

        de.deserialize_string(PortTypeVisitor {})
    }
}

fn main() {
    let input = r#"
    {
      "PrivatePort": 2222,
      "PublicPort": 3333,
      "Type": "dccp"
    }
    "#;

    let result: Result<PortMapping, _> = from_str(input);

    println!("{:#?}", result);
}

字符串
我不认为有一个习惯的方式来做到这一点,这可以包括在未来。

pftdvrlh

pftdvrlh5#

您现在可以在变量级别上使用#[serde(untagged)]属性来捕获未知变量。

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

字符串
不要与枚举级别上的未标记属性混淆。
我找不到这个属性的任何文档。
感谢https://github.com/serde-rs/serde/issues/912#issuecomment-1868785603

相关问题