1

I am attempting to turn a flat structure like the following:

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(),
    }];

into a nested JSON object through serde_json that looks something like:

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

(The values b are guaranteed to be unique within the array)

If I had needed only one layer, I would do something like this:

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();

However, this doesn't seem to scale to multiple layers (i.e. (String, (String, NewType)) can't collect into Hashmap<String, Hashmap<String, NewType>>)

Is there a better way than manually looping and inserting entries into the hashmaps, before turning them into json?

8176135
  • 3,755
  • 3
  • 19
  • 42
  • 1
    you say a and b are guaranteed to be unique but then use a as if it's not unique (and it is not in the example data). – Jussi Kukkonen Sep 16 '19 at 08:35
  • Shame you're using a custom `struct` for this with a fixed number of fields. If you want more, you're going to have to make different variants; this is a wasted opportunity and would've been a perfect use case for a `Vec` containing the branch path – Sébastien Renauld Sep 16 '19 at 09:23
  • @JussiKukkonen My bad, I meant `b` is unique, not a – 8176135 Sep 16 '19 at 09:36
  • @SébastienRenauld can you explain what you mean by `Vec` with branch paths? I specifically put in serde_json as context as I'm not sure what the best way to accomplish this is, thanks. – 8176135 Sep 16 '19 at 09:38
  • 1
    If instead of your flat structure definition (using `Foo {...}`), you had a vector indicating the path to go (i.e. `vec!['abc1', 'efg1', ...]`) you would then be able to iteratively walk this path and generate your structure. As it is, sadly, short of implementing something like `Into>` to be able to recurse, you're going to be stuck with procedurally walking down the tree for each element, I think – Sébastien Renauld Sep 16 '19 at 09:41

2 Answers2

1

A map will preserve the shape of the data. That is not what you want; the cardinality of the data has been changed after the transformation. So a mere map won't be sufficient.

Instead, a fold will do: you start with an empty HashMap, and populate it as you iterate through the collection. But it is hardly any more readable than a loop in this case. I find a multimap is quite useful here:

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);
}
edwardw
  • 12,652
  • 3
  • 40
  • 51
0

If you need to do something custom to flatten/merge your Foo structure, you could turn it into json Values in your rust code using something this:

   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

Ultrasaurus
  • 3,031
  • 2
  • 33
  • 52