2014-09-10 40 views
3

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

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

{ name: "bob", emails: { home: "[email protected]", work : "[email protected]" } } 

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

{ emails: { home2: "[email protected]" } } 

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

{ name: "bob", emails: { home: "[email protected]", work : "[email protected]", home2: "[email protected] } } 

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

+0

发生什么情况,如果有冲突?你想如何处理?顺便问一下好问题。 – 2014-09-10 20:22:50

+0

对于我自己,它是从左到右的地图合并,右图(新地图)覆盖旧地图,如果密钥存在.... – adrian 2014-09-10 20:25:06

回答

-1

我没有深入的合并方法,但我很清楚,但这可能是设计。一般来说,嵌套Map是一种反模式,因为它们很快变得难以管理。例如,假设你递了一段代码,看起来像这样:

Map<String, Map<String, Map<String, Object>>> someNonsensicalMap = someObject.getNonsensicalMap(); 

好运制作的是什么地图包含不耗时的逆向工程的努力太大的意义。这种事情应该避免。

解决这个问题的常用方式是使用类而不是嵌套地图来为内容提供一些有意义的上下文。使用DOM解析XML时,一个真实世界的例子就会发挥作用。看看这个SO帖子的答案:parsing Xml with NodeList and DocumentBuilder。你可以看到,代替嵌套地图,有一个对象,其中包含Element,它本身可以包含NodeList等,这更容易遵循并允许实际上无限嵌套。

因此,我强烈建议重新访问您的设计并避免嵌套地图。当使用类,而不是一个嵌套的地图,那么你可以添加合并一种或多种方法,以你的类来执行深合并:

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

以上是怎样的一个简单的例子,基于以上的JSON,但可以很容易扩展为递归地合并类所包含的任何字段。

+0

我会同意你,除了有一个缺陷如果你需要重建数据嵌套的地图形式。这可能是一个问题。 – adrian 2014-09-10 20:59:47

+0

你能举一个例子说明你为什么需要嵌套地图形式吗?你的意思是说地图需要被序列化/反序列化为JSON或XML?因为这也可以通过向类添加toJSON()和/或toXML()方法来处理,而不是使用嵌套的映射反模式。 – paulk23 2014-09-10 21:14:38

10

的改进版本:this gist

这是一种深合并的Java地图:

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

作品嵌套贴图,物体和物体的名单。请享用。

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

+0

你能解释一下它是如何工作的吗? @ wonderkid2 – 2018-01-13 12:50:12

2

我真的很喜欢上面wonderkid2的做法。

我修改了它多一点:

  1. 略微更高的性能(避免一些。得到(_)通过输入)
  2. 递归任何收集调用,而不是仅仅列出
  3. 一些更严格的验证

这不是一般的美妙的做法,但我看它如何被需要有时。

(PS我用番石榴,但这些电话(Objects.equals,Preconditions.checkArgument)是容易的,如果需要更换。)

@SuppressWarnings({"rawtypes", "unchecked"}) 
static void deepMerge(
     Map original, 
     Map newMap) { 

    for (Entry e : (Set<Entry>) newMap.entrySet()) { 
     Object 
      key = e.getKey(), 
      value = e.getValue(); 

     // unfortunately, if null-values are allowed, 
     // we suffer the performance hit of double-lookup 
     if (original.containsKey(key)) { 
      Object originalValue = original.get(key); 

      if (Objects.equal(originalValue, value)) 
       return; 

      if (originalValue instanceof Collection) { 
       // this could be relaxed to simply to simply add instead of addAll 
       // IF it's not a collection (still addAll if it is), 
       // this would be a useful approach, but uncomfortably inconsistent, algebraically 
       Preconditions.checkArgument(value instanceof Collection, 
         "a non-collection collided with a collection: %s%n\t%s", 
         value, originalValue); 

       ((Collection) originalValue).addAll((Collection) value); 

       return; 
      } 

      if (originalValue instanceof Map) { 
       Preconditions.checkArgument(value instanceof Map, 
         "a non-map collided with a map: %s%n\t%s", 
         value, originalValue); 

       deepMerge((Map) originalValue, (Map) value); 

       return; 
      } 

      throw new IllegalArgumentException(String.format(
        "collision detected: %s%n%\torig:%s", 
        value, originalValue)); 

     } else 
      original.put(key, value); 
    } 
} 
+2

而不是每个'if'语句中的'return',它应该是'continue'。如果您想覆盖'original'映射中的现有键(而不是抛出'IllegalArgumentException',则将'IllegalArgumentException'替换为'original.put(key,value);'。 – 2016-07-31 10:31:44

3

我使用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; 
} 

例如: 基本配置:

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 

测试ENV配置:

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

books: 
    technical: 
    - f sharp 
    novels: [2666] 
    poetry: 
    - parra 

合并配置:

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"