从平面结构中功能性地创建嵌套对象

Functionally creating a nested object from a flat structure

我正在尝试转动如下平面结构:

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<String, Hashmap<String, NewType>>

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

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

如果您需要对 flatten/merge 您的 Foo 结构做一些自定义,您可以使用以下内容将它变成 json rust 代码中的值:

   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