我有一个JSON API,它返回一个如下所示的对象:

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

为了捕捉这一点,我有一个枚举和一个 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,
}

现在,这个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")值.当然,我不喜欢在这个框中传递值

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

是否有一个Serde配置来实现我想要的功能,或者我应该手动为PortType编写DeserializeSerialize实现?

推荐答案

简单的情况下就可以了:

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);
}

我不认为有一个惯用的方法可以做到这一点,这可以包括在future .

Rust相关问答推荐

程序退出后只写入指定管道的数据

为什么对不可复制数据的引用的取消引用没有O权限来避免Rust中的双重释放?

如何使用 list 在Rust for Windows中编译?

如果成员都实现特征,是否在多态集合上实现部分重叠的特征?

定义采用更高级类型泛型的性状

防止cargo test 中的竞争条件

有没有办法避免在While循环中多次borrow `*分支`

Tokio_Postgres行上未显示退回特性的生存期,且生命周期 不够长

try 创建随机数以常量

不能在Rust中使用OpenGL绘制三角形

这是什么:`impl Trait for T {}`?

当我编译 Rust 代码时,我是否缺少 AVX512 的目标功能?

‘&T as *const T as *mut T’ 在 ‘static mut’ 项目中合适吗?

如何在Rust中使用Serde创建一个自定义的反序列化器来处理带有内部标记的枚举

pyO3 和 Panics

Rust 中的方法调用有什么区别?

如何将参数传递给Rust 的线程?

当 T 不是副本时,为什么取消引用 Box 不会抱怨移出共享引用?

如何在 Rust 中创建最后一个元素是可变长度数组的 struct ?

我可以在不调用 .clone() 的情况下在类型转换期间重用 struct 字段吗?