2

I have a nested HashMap in this form:

{key1=val1, key2=val2, 
    key3=[
            {key4=val4, key5=val5}, 
            {key6=val6, key7=val7} 
        ]
}

I now want to flatten that map, so that all entries are on the same level:

{key1=val1, key2=val2, key4=val4, key5=val5,key6=val6, key7=val7}

When I try

map.values().forEach(map.get("key3")::addAll);

as described in this post, I get the following error:

invalid method reference
  cannot find symbol
    symbol:   method addAll(T)
    location: class Object
  where T is a type-variable:
    T extends Object declared in interface Iterable

Is there any generic way to flatten any given Map?

Evgenij Reznik
  • 17,916
  • 39
  • 104
  • 181
  • 1
    a recursive function with an `instanceOf` check against `Map`, I assume – Eugene Jan 14 '19 at 14:52
  • 2
    you can try to use flatMap() from Java Stream API – i.merkurev Jan 14 '19 at 14:55
  • 3
    `map.get("key3")::addAll` makes no sense since you seem to have a `Map`. – f1sh Jan 14 '19 at 14:57
  • 4
    I would be really glad if you could share the type of your `Map` where the code that you've shared works. – Naman Jan 14 '19 at 14:58
  • 1
    What are the key value pairs in your hash map? key=>value, key=>array, key=>map? – Lino Jan 14 '19 at 14:58
  • Even for a map which allows that operation, what is it supposed to do when `map.values().forEach(map.get("key3")::addAll);` processes the entry for `"key3"`? – Holger Jan 14 '19 at 14:59
  • 4
    So what is the type of your `Map`? It can't be `Map` if some of the entries are `Entry>`. You need to include the code for us to understand what you actually have. – jbx Jan 14 '19 at 15:00
  • @user1170330 Then your question doesn't make any sense, and you aren't working with a nested map then. (doesn't seem right to me) – Lino Jan 14 '19 at 15:01
  • @Lino: Sorry, the "main" map is Map, all the contents within the this map are Strings. – Evgenij Reznik Jan 14 '19 at 15:02
  • 1
    @user1170330 No they are not. You are yourself saying some of them are a `Map`. – jbx Jan 14 '19 at 15:02
  • 1
    @nullpointer perhaps, but what I wanted to emphasizes, is that `map.values().forEach(…)` will process all entries, including the one the OP is adding to. So this will end up trying to add a list (or map) to itself. – Holger Jan 14 '19 at 15:03

3 Answers3

7

Not sure if I understood the question correctly, but something like this might work. Haven't checked all the syntax yet, so there might be some mistake somewhere.

Stream<Map.Entry<String, String>> flatten(Map<String, Object> map) {
  return map.entrySet()
            .stream()
            .flatMap(this::extractValue);         
}

Stream<Map.Entry<String, String>> extractValue(Map.Entry<String, Object> entry) {
   if (entry.getValue() instanceof String) {
      return Stream.of(new AbstractMap.SimpleEntry(entry.getKey(), (String) entry.getValue()));
   } else if (entry.getValue() instanceof Map) {
      return flatten((Map<String, Object>) entry.getValue());
   }
}

Then you could do:

Map<String, String> flattenedMap = flatten(yourmap)
   .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
jbx
  • 21,365
  • 18
  • 90
  • 144
1

You can make use of a recursive helper method:

static void forEachValue(Map<String, Object> source, BiConsumer<? super String, ? super Object> action) {
    for (final Map.Entry<String, Object> entry : source.entrySet()) {
        if (entry.getValue() instanceof Map) {
            forEachValue((Map<String, Object>) entry.getValue(), action);
        } else {
            action.accept(entry.getKey(), entry.getValue());
        }
    }
}

Which then can be called like this:

Map<String, Object> map = ...;

Map<String, Object> flattened = new HashMap<>();
forEachValue(map, map::put);

I've used this approach with the BiConsumer to not limit the method to only flatten the nested map into another map, but the caller may decide himself what he wants to do with every key-value pair.

Lino
  • 19,604
  • 6
  • 47
  • 65
0

You should try this:

Map<String, Object> flatenedMap = new HashMap<>();

    map.forEach((key, value) -> {
      if(value instanceof Map) {
        flatenedMap.putAll((Map) value);
      } else {
        flatenedMap.put(key, value);
      }
    });

If you have more than one level of nesting you can use recursive alg.

static Map<String, Object> flatMap(Map<String, Object> map) {
    Map<String, Object> flatenedMap = new HashMap<>();
    map.forEach((key, value) -> {
      if(value instanceof Map) {
        flatenedMap.putAll(flatMap((Map) value));
      } else {
        flatenedMap.put(key, value);
      }
    });

    return flatenedMap;
  }
HPCS
  • 1,434
  • 13
  • 22