【问题标题】:Deserialize map of remote structs using serde_json使用 serde_json 反序列化远程结构的映射
【发布时间】:2020-12-18 05:24:03
【问题描述】:

我有一个用例需要将 JSON 反序列化为“远程”(在另一个 crate 中定义)结构的映射。我在这方面遇到了可笑的困难时期,所以我一定遗漏了一些明显的东西。

以下基本上是所需的最终状态:

use hyper::Uri;
use serde_json;
use std::collections::HashMap;

fn main() {
    let data = r#"
        {
            "/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
            "/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
            "/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
        }"#;

    let map: HashMap<String, Uri> = serde_json::from_str(data).unwrap();

    println!("{:?}", map);
}

失败是因为:

the trait bound `Uri: serde::de::Deserialize<'_>` is not satisfied required because of the requirements
on the impl of `serde::de::Deserialize<'_>` for `HashMap<std::string::String, Uri>`

虽然 serde 文档 describe a pretty nasty but potentially viable workaround 用于在远程结构上派生 Deserialize,但它需要在任何引用容器类型上使用 #[serde(with = "LocalStructRedefinition")],这在创建 HashMap 时似乎是不可能的。

直观地说,这必须是一个常见的用例......有没有办法解决这个不涉及:

  1. 将数据反序列化为HashMap&lt;String, String&gt;
  2. 遍历地图,将值解析为 HashMap&lt;String, Uri&gt;

【问题讨论】:

  • 您能否澄清一下为什么列出的两种方法是不可接受的?您可以创建自己的struct MyUri(Uri) 并在其上实现Deserialize 以获得HashMap&lt;String, MyUri&gt;
  • 这是我得到的,我相信它可以改进以适应2(no iter) --> play.rust-lang.org/…
  • 您可以按照@kmdreko 的建议避免进入并使用 MyUri
  • 谢谢你们的帮助! @kmdreko 这不一定是不可接受的;将每个值加载到内存中两次(尤其是当真实世界的 JSON 对象包含数十万个条目时)只是感觉不太理想,我认为必须有更聪明的方法来做到这一点。

标签: rust serde hyper


【解决方案1】:

通过混合使用Intodeserialize_withflatten,您可以实现您想要的:

use serde_json;
use std::collections::HashMap;
use hyper::Uri;
use serde::{de::Error, Deserialize, Deserializer};

#[derive(Debug, Deserialize)]
struct MyUri(#[serde(deserialize_with = "from_uri")] Uri);

#[derive(Debug, Deserialize)]
struct MyUriMap {
    #[serde(flatten)]
    inner: HashMap<String, MyUri>
}

impl Into<HashMap<String, Uri>> for MyUriMap {
    fn into(self) -> HashMap<String, Uri> {
        self.inner.into_iter().map(|x| (x.0, x.1.0)).collect()
    }
}


fn from_uri<'de, D>(deserializer: D) -> Result<Uri, D::Error>
where
    D: Deserializer<'de>,
{
    let s: &str = Deserialize::deserialize(deserializer)?;
    s.parse().map_err(D::Error::custom)
}


fn main() {
    let data = r#"
        {
            "/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
            "/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
            "/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
        }"#;

    let map: MyUriMap = serde_json::from_str(data).unwrap();

    // let map: HashMap<String, Uri> = map.into();
    // I think to get HashMap<String, Uri> you have to do an iter as seen in the Into implementation
    println!("{:?}", map);
}

Playground

PS。在我的回答中,要获得 HashMap 您必须执行 Into 实现中所见的迭代

【讨论】:

  • 谢谢! MyUri 绝对是我所缺少的。只是想在这里学习:Into 实现是零拷贝(或被优化为零拷贝),这样地图实际上只存储在内存中一次?
  • 其实我觉得你不需要使用 into。这应该可以正常工作吗? play.rust-lang.org/…
  • 我不认为它是零拷贝。似乎建议使用From 而不是Into doc.rust-lang.org/std/convert/trait.Into.html
  • 这实际上解决了我的实际用例,我可以在其中使用HashMap&lt;String, MyUri&gt; 并在查找时访问内部Uri - 我认为我以错误的方式看待这个问题。我将把它标记为已解决,但我认为另一半是 没有 一个明确定义的方法来实现带有 serde 的 HashMap&lt;String, RemoteStruct&gt; 的零拷贝反序列化,如果这确实是必要的。
【解决方案2】:

通过使用serde_with crate,大部分反序列化逻辑可以简化为一个属性。 它的优点是避免了除反序列化之外的任何包装器类型。

#[serde_with::serde_as]
#[derive(serde::Deserialize)]
struct Wrapper(
    #[serde_as(as = "HashMap<_, serde_with::DisplayFromStr>")]
    HashMap<String, hyper::Uri>
);

fn main() {
    let data = r#"
        {
            "/a": "http://example.com/86f7e437faa5a7fce15d1ddcb9eaeaea377667b8",
            "/b": "http://example.com/e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98",
            "/c": "http://example.com/84a516841ba77a5b4648de2cd0dfcb30ea46dbb4"
        }"#;

    let map: Wrapper = serde_json::from_str(data).unwrap();
    println!("{:?}", map.0);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-16
    • 2019-06-24
    • 1970-01-01
    • 2018-01-07
    • 1970-01-01
    • 2013-01-10
    • 1970-01-01
    相关资源
    最近更新 更多