N级映射的递归合并

adr*_*ian 5 java

有没有办法在Java中深入合并地图?我已经看过一些关于它的帖子,但大多数似乎解决方案似乎只处理一个级别的合并或者是乏味的.

我的数据结构(使用JSON字符串表示地图)看起来类似于:

{ name: "bob", emails: { home: "bob@home.com", work : "bob@work.com" } }
Run Code Online (Sandbox Code Playgroud)

理想情况下,如果我有另一张地图

{ emails: { home2: "bob@home2.com" } } 
Run Code Online (Sandbox Code Playgroud)

与第一张地图合并后会看起来像

{ name: "bob", emails: { home: "bob@home.com", work : "bob@work.com", home2: "bob@home2.com } }
Run Code Online (Sandbox Code Playgroud)

我可以保证我的所有地图都是<String, Object>.这是否有开箱即用的解决方案?我真的想避免自己为非常嵌套或大的对象编写一堆递归或迭代代码.

won*_*id2 15

改进版本:这个要点

这是一种深度合并Java Maps的方法:

// This is fancier than Map.putAll(Map)
private static Map deepMerge(Map original, Map newMap) {
    for (Object key : newMap.keySet()) {
        if (newMap.get(key) instanceof Map && original.get(key) instanceof Map) {
            Map originalChild = (Map) original.get(key);
            Map newChild = (Map) newMap.get(key);
            original.put(key, deepMerge(originalChild, newChild));
        } else if (newMap.get(key) instanceof List && original.get(key) instanceof List) {
            List originalChild = (List) original.get(key);
            List newChild = (List) newMap.get(key);
            for (Object each : newChild) {
                if (!originalChild.contains(each)) {
                    originalChild.add(each);
                }
            }
        } else {
            original.put(key, newMap.get(key));
        }
    }
    return original;
}
Run Code Online (Sandbox Code Playgroud)

适用于嵌套地图,对象和对象列表.请享用.

(免责声明:我不是Java开发人员!)


小智 5

我正在使用jackson加载 json 和yaml配置文件,每个环境都有一个基本配置文件和一个配置文件。我加载基本配置和特定于环境的配置。然后我深度合并两张地图。列表也被合并,删除重复项。值在 map1 上深度合并,map2 中的值会在发生冲突时覆盖 map1 中的值。

void deepMerge(Map<String, Object> map1, Map<String, Object> map2) {
    for(String key : map2.keySet()) {
        Object value2 = map2.get(key);
        if (map1.containsKey(key)) {
            Object value1 = map1.get(key);
            if (value1 instanceof Map && value2 instanceof Map) 
                deepMerge((Map<String, Object>) value1, (Map<String, Object>) value2);
            else if (value1 instanceof List && value2 instanceof List) 
                map1.put(key, merge((List) value1, (List) value2));
            else map1.put(key, value2);
        } else map1.put(key, value2);
    }
}

List merge(List list1, List list2) {
    list2.removeAll(list1);
    list1.addAll(list2);
    return list1;
}
Run Code Online (Sandbox Code Playgroud)

例如:基本配置:

electronics:
  computers:
    laptops:
      apple:
        macbook: 1000
        macbookpro: 2000
      windows:
        surface: 2000
    desktop:
      apple:
        imac: 1000
      windows:
        surface: 2000
  phones:
    android:
      samsung:
        motox: 300
    apple:
      iphone7: 500

books:
  technical:
    - java
    - perl
  novels:
    - guerra y paz
    - crimen y castigo
  poetry:
    - neruda
    - parra
Run Code Online (Sandbox Code Playgroud)

测试环境配置:

electronics:
  computers:
    laptops:
      windows:
        surface: 2500
    desktop: 100
  phones:
    windows:
      nokia: 800

books:
  technical:
    - f sharp
  novels: [2666]
  poetry:
    - parra
Run Code Online (Sandbox Code Playgroud)

合并配置:

electronics:
  computers:
    laptops:
      apple:
        macbook: 1000
        macbookpro: 2000
      windows:
        surface: 2500
    desktop: 100
  phones:
    android:
      samsung:
        motox: 300
    apple:
      iphone7: 500
    windows:
      nokia: 800
books:
  technical:
  - "java"
  - "perl"
  - "f sharp"
  novels:
  - "guerra y paz"
  - "crimen y castigo"
  - 2666
  poetry:
  - "neruda"
  - "parra"
Run Code Online (Sandbox Code Playgroud)


小智 5

我最近不得不做类似的事情,但我想要一个解决方案:

  • 使用 java8
  • 这不会修改原始地图,因此会返回一张新地图

merge 方法进行实际的合并,它从获取原始地图的副本开始,然后递归地合并嵌套的地图和集合。

如果存在键冲突并且关联的值不是 Map 或 Collection,则优先考虑来自 Map b 的值。

public static Map merge(Map a, Map b) {
    if (a == null && b == null)
        return null;
    if (a == null || a.size() == 0)
        return copy(b);
    if (b == null || b.size() == 0)
        return copy(a);
    Map copy = copy(a);
    copy.putAll(
        (Map) b
            .keySet()
            .stream()
            .collect(
                Collectors.toMap(
                    key -> key,
                    key -> {
                        Object original = copy.get(key);
                        Object value = b.get(key);
                        if (value == null && original == null)
                            return null;
                        if (value == null && original != null)
                            return original;
                        if (value != null && original == null)
                            return value;
                        if (value instanceof Map && original instanceof Map)
                            return merge((Map) original, (Map) value);
                        else if (value instanceof Collection
                            && original instanceof Collection) {
                            try {
                                Collection merge =
                                    newCollectionInstance(
                                        (Collection) original,
                                        (List) Lists
                                            .newArrayList(
                                                (Collection) original,
                                                (Collection) value)
                                            .stream()
                                            .flatMap(Collection::stream)
                                            .collect(Collectors.toList()));
                                return merge;
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }
                        }
                        return value;
                    })));
    return copy;
}
Run Code Online (Sandbox Code Playgroud)

这是复制地图的方法

public static Map copy(Map original) {
    return (Map) original
        .keySet()
        .stream()
        .collect(
            Collectors.toMap(
                key -> key,
                key -> {
                    Object value = original.get(key);
                    if (value instanceof Map)
                        return copy((Map) value);
                    if (value instanceof Collection)
                        return newCollectionInstance((Collection) value, (Collection) value);
                    return value;
                }));
}
Run Code Online (Sandbox Code Playgroud)

和一个帮助方法来复制具有相同类型的集合

public static Collection newCollectionInstance(Collection collection, Collection elements) {
    try {
        Collection newInstance = collection.getClass().newInstance();
        newInstance.addAll(elements);
        return newInstance;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 -2

据我所知,没有深度合并方法,但这可能是设计使然。一般来说,嵌套Map是一种反模式,因为它们很快就会变得难以管理。例如,假设您收到一段如下所示的代码:

Map<String, Map<String, Map<String, Object>>> someNonsensicalMap = someObject.getNonsensicalMap();
Run Code Online (Sandbox Code Playgroud)

祝你好运,无需进行耗时的逆向工程工作,就能充分理解该地图所包含的内容。这种事情应该避免。

解决这个问题的常见习惯是使用类而不是嵌套映射来为内容提供一些有意义的上下文。当使用 DOM 解析 XML 时,就会出现这样的一个现实示例。看看这篇 SO 帖子的答案: parsing Xml with NodeList and DocumentBuilder。您可以看到,不是嵌套映射,而是一个NodeList包含Elements 的对象,该对象本身也可以包含NodeLists 等。这更容易理解,并且允许几乎无限的嵌套。

因此,我强烈建议重新审视您的设计并避免嵌套地图。当使用类而不是嵌套映射时,您可以向类添加一个或多个合并方法来执行深度合并:

class Person {
  Set<String> names = new HashSet<String>();
  Set<String> emails = new HashSet<String>();

  public Set<String> getNames() { return names; }
  public Set<String> getEmails() { return emails; }

  public void mergeNames(HashSet<String> moreNames) {
    names.addAll(moreNames);
  }

  public void mergeEmails(HashSet<String> moreEmails) {
    emails.addAll(moreEmails);
  }

  public void mergeNames(HashSet<String> moreNames) {
    names.addAll(moreNames);
  }

  public void mergePerson(Person person) {
    mergeNames(person.getNames());
    mergeEmails(person.getEmails());
  }
} 
Run Code Online (Sandbox Code Playgroud)

上面是一个基于上面 JSON 的简单示例,但可以轻松扩展以递归合并类包含的任何字段。