【问题标题】:Rust custom deserialize implementationRust 自定义反序列化实现
【发布时间】:2021-05-14 00:47:09
【问题描述】:

我很难理解如何使用 Rust 的 serde 为自定义映射实现反序列化。如果有人能帮我做这个例子,我会很高兴:

我有以下结构:

#[derive(Debug, Clone, PartialEq)]
pub struct ConnectorTopics {
    pub name: String,
    pub topics: Vec<String>,
}

JSON 数据采用以下格式:

{
  "test-name": {
    "topics": [
      "topic1",
      "topic2"
    ]
  }
}

如您所见,name 字段是主题的包装,因此在我的情况下,它应该反序列化为:

let _ = ConnectorTopics {
    name: "test-name".into(),
    topics: vec!["topic1".into(), "topic2".into()]
}

我的第一次尝试是在 Deserialize 实现中使用自定义结构,但是,这不会编译并且似乎不是正确的方法。

impl<'de> Deserialize<'de> for ConnectorTopics {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Debug, Deserialize)]
        struct Inner {
            topics: Vec<String>,
        }

        let a = deserializer.deserialize_map(HashMap<String, Inner>).unwrap();

        

        let value = Deserialize::deserialize::<HashMap<String, Inner>>(deserializer)?;

        let (connector, inner) = value.iter().nth(0).ok_or("invalid")?.0;

        Ok(ConnectorTopics {
            name: connector,
            topics: vec![],
        })
    }
}

【问题讨论】:

  • 那个奇怪的 json 模式
  • 希望我能控制它。 :)

标签: rust serde


【解决方案1】:

您所做的是正确的方法,但您的 json 很奇怪:

use serde::de;
use serde::Deserialize;
use std::fmt;

#[derive(Debug, Clone, PartialEq)]
pub struct ConnectorTopics {
    pub name: String,
    pub topics: Vec<String>,
}

#[derive(Debug, Deserialize)]
struct Inner {
    topics: Vec<String>,
}

impl<'de> de::Deserialize<'de> for ConnectorTopics {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: de::Deserializer<'de>,
    {
        struct ConnectorTopicsVisitor;

        impl<'de> de::Visitor<'de> for ConnectorTopicsVisitor {
            type Value = ConnectorTopics;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("ConnectorTopics")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
            where
                V: de::MapAccess<'de>,
            {
                if let Some(key) = map.next_key()? {
                    let value: Inner = map.next_value()?;
                    if let Some(_) = map.next_key::<&str>()? {
                        Err(de::Error::duplicate_field("name"))
                    } else {
                        Ok(Self::Value {
                            name: key,
                            topics: value.topics,
                        })
                    }
                } else {
                    Err(de::Error::missing_field("name"))
                }
            }
        }

        deserializer.deserialize_map(ConnectorTopicsVisitor {})
    }
}

fn main() {
    let input = r#"{
      "test-name": {
        "topics": [
          "topic1",
          "topic2"
        ]
      }
    }"#;

    let result: ConnectorTopics = serde_json::from_str(input).unwrap();

    let expected = ConnectorTopics {
        name: "test-name".into(),
        topics: vec!["topic1".into(), "topic2".into()],
    };

    assert_eq!(result, expected);
}

【讨论】:

  • 这看起来是一种更好的方法。这样的 JSON 来自 Kafka Connect。我正在尝试在此基础上编写一个 rust 客户端。 docs.confluent.io/platform/current/connect/references/…
  • @EvaldasBuinauskas 确实是很奇怪的数据格式
  • 是的,如果没有任何连接器名称参数并且可以为多个连接器带回主题,这将是有意义的。但我在这里 :) 再次感谢您!
【解决方案2】:

您可以为其使用自定义函数:

pub fn deserialize_connector_topics(data: &str) -> Result<ConnectorTopics> {
    let value: Value = serde_json::from_str(data)?;
    if let Some(object) = value.as_object() {
        let mut it = object.into_iter();
        if let Some((name, topics)) = it.next() {
            let topics: Vec<String> = serde_json::from_value(topics.get("topics").unwrap().clone())?;
            return Ok(ConnectorTopics {
                name: name.to_string(),
                topics: topics,
            });
        }
    };
    Err(Error::custom("Invalid ConnectorTopics data"))
}

请注意,它是非常临时的,它甚至在这里和那里都有一些展开和(可能是不必要的)克隆。您应该考虑根据需要对其进行修改。但是作为一个例子,应该就足够了。

Playground

【讨论】:

  • 我会将Stargateur 的答案标记为已接受,因为它避免了临时数据结构和不安全的展开。
猜你喜欢
  • 2021-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-23
相关资源
最近更新 更多