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?

5
  • 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). Commented Sep 16, 2019 at 8: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 Commented Sep 16, 2019 at 9:23
  • @JussiKukkonen My bad, I meant b is unique, not a Commented Sep 16, 2019 at 9: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. Commented Sep 16, 2019 at 9: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<Vec<String>> to be able to recurse, you're going to be stuck with procedurally walking down the tree for each element, I think Commented Sep 16, 2019 at 9:41

2 Answers 2

2

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);
}
Sign up to request clarification or add additional context in comments.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.