Evgenij Reznik
Evgenij Reznik

Reputation: 18594

How to Flatten a HashMap?

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?

Upvotes: 2

Views: 16898

Answers (3)

jbx
jbx

Reputation: 22128

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

Upvotes: 8

Lino
Lino

Reputation: 19926

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.

Upvotes: 1

HPCS
HPCS

Reputation: 1454

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

Upvotes: 0

Related Questions