Recursive merge of Maps with generics

generics, java, recursion

Solution

Re making assumptions about the `Set` and `Map` implementations you're using: you can at least avoid that if you always create new maps and sets -- which has the nice side benefit that if somebody modifies the original set it's not going to screw up your merged version.

As for your main point, there's no way to do this in Java without casting. Generics can't help you because at run time, the compiler doesn't know any more than

Map<String, Object> merge ( Map<String, ?> map1, Map<String, ?> map2 )

You don't actually know in your code whether you have a `Map<String, Set<String>>`, a `Map<String, Map<String, Set<String>>`, or a `Map<String, Object>` where some values are `Set<String>` and other values are `Map<String, Set<String>>` -- in which third case it does still work, so long as for each key, both maps have the same value type.

Paradoxically, the best way to get rid of the warnings is to get rid of the generics, at which point, using only information available at run time (`Map` or `Set`, of what, we don't care), all casts are safe:

public Map<Object, Object> merge ( Map<?, ?> map1, Map<?, ?> map2 )
{
    Map<Object, Object> merged = new HashMap<Object, Object>();
    if ( map1 == null || map2 == null )
    {
        if ( map1 != null )
        {
            merged.putAll( map1 );
        }
        if ( map2 != null )
        {
            merged.putAll( map2 );
        }
        return merged;
    }

    Set<Object> allKeys = new HashSet<Object>();
    allKeys.addAll( map1.keySet() );
    allKeys.addAll( map2.keySet() );

    for ( Object key : allKeys )
    {
        Object v1 = map1.get( key );
        Object v2 = map2.get( key );
        if ( v1 instanceof Set || v2 instanceof Set )
        {
            Set<Object> newSet = new HashSet<Object>();
            if ( v1 instanceof Set )
            {
                newSet.addAll( (Set) v1 );
            }
            if ( v2 instanceof Set )
            {
                newSet.addAll( (Set) v2 );
            }
            merged.put( key, newSet );
        }
        else if ( v1 instanceof Map || v2 instanceof Map )
        {
            Map<?, ?> m1 = v1 instanceof Map ? (Map<?, ?>) v1 : null;
            Map<?, ?> m2 = v2 instanceof Map ? (Map<?, ?>) v2 : null;
            merged.put( key, merge( m1, m2 ) );
        }

    }
    return merged;
}

Problem

I'm trying to build a method to merge the content of two maps. I've looked around on here for a while and couldn't see a way to make this generic. I want to avoid the `@SuppressWarnings("unchecked")` annotation if at all possible. I have a nested map structure where keys are strings and values are maps of more stuff, with the 'leaf' nodes in this structure always being sets. So in most cases I have two maps with a structure like: ``` Map<String,Map<String,Set<String>>> ``` and I'll want to merge both maps so that I end up with a union of the two and any common keys in those two maps are represented in the resultant map with a value that is a merge of the two values from both maps. In code, this is what I have thus far: ``` @SuppressWarnings("unchecked") public Map<String,Object> merge(final Map<String, Object> map1, final Map<String, Object> map2) { final Map<String,Object> merged = new HashMap<String,Object>(map1); for (final Map.Entry<String,Object> entry : merged.entrySet()) { final String key = entry.getKey(); final Object value = entry.getValue(); if (map2.containsKey(key)) { final Object value2 = map2.get(key); if ((value instanceof Map) && (value2 instanceof Map)) { merged.put(key, merge((Map<String, Object>) value, (Map<String, Object>) value2)); } else if ((value instanceof Set) && (value2 instanceof Set)) { final Set<Object> set = new HashSet<Object>((Set<Object>)value); set.addAll((Set<Object>) value2); merged.put(key, set); } else { // throw up, should only ever be a map or a set } } } for (final String key : map2.keySet()) { if (!merged.containsKey(key)) { merged.put(key, map2.get(key)); } } return merged; } ``` It does the job but I'm unhappy with it as to use it you're casting a whole bunch of stuff and it also makes assumptions about the Set and Map implementations you're using. Given that I know I'm always dealing with maps of string to something, where something is either a set of string or another map of string to something, I'm trying to figure out how to spec this out using generics. I fiddled with similar but not quite the same approaches shows in other answers around here, for example a method signature like: ``` public <T extends Map<String,T>> Map<String,T> merge(final T map1, final T map2) ``` but that didn't work out as the recursion call didn't like my attempt to input arguments that were a `Map<String,Set<String>>`. I admit to not ever having the need to go this deep with generics before. Any guidance is much appreciated.

Original source