从平面结构中功能性地创建嵌套对象
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}));
}
};
我正在尝试转动如下平面结构:
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}));
}
};