【问题标题】:Functionally creating a nested object from a flat structure从平面结构功能性地创建嵌套对象
【发布时间】:2019-09-16 07:53:55
【问题描述】:

我正在尝试转换如下扁平结构:

let flat = vec![
    Foo {
        a: "abc1".to_owned(),
        b: "efg1".to_owned(),
        c: "yyyy".to_owned(),
        d: "aaaa".to_owned(),
    },
    Foo {
        a: "abc1".to_owned(),
        b: "efg2".to_owned(),
        c: "zzzz".to_owned(),
        d: "bbbb".to_owned(),
    }];

通过serde_json 进入一个嵌套的 JSON 对象,看起来像:

{
    "abc1": {
        "efg1": {
            "c": "hij1",
            "d": "aaaa", 
        },
        "efg2": {
            "c": "zzzz",
            "d": "bbbb", 
        },
    }
}

b 的值保证在数组中是唯一的)

如果我只需要一层,我会这样做:

let map = flat.into_iter().map(|input| (input.a, NewType {
    b: input.b,
    c: input.c,
    d: input.d,
})).collect::<Hashmap<String, NewType>>();

let out = serde_json::to_string(map).unwrap();

但是,这似乎无法扩展到多层(即(String, (String, NewType)) 无法收集到Hashmap&lt;String, Hashmap&lt;String, NewType&gt;&gt;

有没有比在将条目转换为 json 之前手动循环并将条目插入哈希映射更好的方法?

【问题讨论】:

  • 你说 a 和 b 保证是唯一的,但然后使用 a 就好像它不是唯一的(并且它不在示例数据中)。
  • 遗憾的是,您为此使用了具有固定数量字段的自定义 struct。如果你想要更多,你将不得不做出不同的变体;这是一个浪费的机会,对于包含分支路径的 Vec 来说,这将是一个完美的用例
  • @JussiKukkonen 我的错,我的意思是b 是独一无二的,而不是
  • @SébastienRenauld 你能用分支路径解释一下Vec 的意思吗?我特别将 serde_json 作为上下文,因为我不确定完成此操作的最佳方法是什么,谢谢。
  • 如果不是你的平面结构定义(使用Foo {...}),你有一个指示要走的路径的向量(即vec!['abc1', 'efg1', ...])你就可以迭代地走这条路并生成你的结构体。遗憾的是,如果没有实现像 Into&lt;Vec&lt;String&gt;&gt; 这样能够递归的东西,我认为你将不得不在程序上为每个元素沿着树走下去

标签: rust serde serde-json


【解决方案1】:

map 将保留数据的形状。那不是你想要的;转换后数据的基数发生了变化。所以仅仅map 是不够的。

相反,fold 可以:您从一个空的HashMap 开始,并在您遍历集合时填充它。但在这种情况下,它几乎不比循环更具可读性。我发现multimap 在这里非常有用:

use multimap::MultiMap;
use std::collections::HashMap;

struct Foo {
    a: String,
    b: String,
    c: String,
    d: String,
}

#[derive(Debug)]
struct NewFoo {
    c: String,
    d: String,
}

fn main() {
    let flat = vec![
        Foo {
            a: "abc1".to_owned(),
            b: "efg1".to_owned(),
            c: "yyyy".to_owned(),
            d: "aaaa".to_owned(),
        },
        Foo {
            a: "abc1".to_owned(),
            b: "efg2".to_owned(),
            c: "zzzz".to_owned(),
            d: "bbbb".to_owned(),
        },
    ];
    let map = flat
        .into_iter()
        .map(|e| (e.a, (e.b, NewFoo { c: e.c, d: e.d })))
        .collect::<MultiMap<_, _>>()
        .into_iter()
        .map(|e| (e.0, e.1.into_iter().collect::<HashMap<_, _>>()))
        .collect::<HashMap<_, _>>();
    println!("{:#?}", map);
}

【讨论】:

    【解决方案2】:

    如果你需要做一些自定义来扁平化/合并你的 Foo 结构,你可以在你的 rust 代码中使用以下方法将其转换为 json 值:

       let mut root: Map<String, Value> = Map::new();
       for foo in flat.into_iter() {
           let b = json!({ "c": foo.c, "d": foo.d });
           if let Some(a) = root.get_mut(&foo.a) {
               if let Value::Object(map) = a {
                    map.insert(foo.b, b);
               }
           } else {
               root.insert(foo.a, json!({foo.b: b}));
           }
       };
    

    link to playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-24
      相关资源
      最近更新 更多