【问题标题】:How can I support an unknown or other value for a Serde enum?如何支持 Serde 枚举的未知值或其他值?
【发布时间】:2019-08-12 23:47:16
【问题描述】:

我有一个 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 的客户端仅仅因为他们可能没有查看的配置选项中的未知字符串而开始失败。

为了解决这个问题,我添加了一个带有String 的Unknown 变体来表示值:

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

这里的目标是在传入未知值时以稍微不方便的PortType::Unknown("dccp") 值结束。当然,这并不能实现我想要的开箱即用——传递未知的@987654329 @值会导致:

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

是否有 Serde 配置可以做我想做的事,还是我应该求助于为 PortType 手动编写 Deserialize 和 Serialize 实现?

【问题讨论】:

    标签: rust serde


    【解决方案1】:

    这有一个问题,尽管它已经开放了 3 年,到目前为止还没有完全解决。 Serde #912.

    在发布这篇文章时,目前正在实施的(尽管没有记录)是#[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 实现。

    【讨论】:

      【解决方案2】:

      我使用 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,
      }
      

      【讨论】:

      • 这是一个巧妙的技巧,但请注意,它仅适用于“简单”枚举类型,即那些没有附加值的类型。
      【解决方案3】:

      简单的情况应该没问题:

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

      我认为没有一种惯用的方法可以做到这一点,将来可能会包括在内。

      【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-09-16
        • 2011-03-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多