是否有可以"diff"两个对象的 Java 库?



是否有一个类似于Unix程序diff的Java实用程序库,但用于对象?我正在寻找可以比较相同类型的两个对象并生成表示它们之间差异的数据结构的东西(并且可以递归地比较实例变量的差异)。我是不是寻找一个文本diff的Java实现。我也不是寻找如何使用反射来做到这一点的帮助。

我所维护的应用程序对这个功能有一个脆弱的实现,它有一些糟糕的设计选择,需要重写,但如果我们可以使用现成的东西,那就更好了。

这里有一个我正在寻找的例子:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();
a.setProp1("A");
a.setProp2("X");
b.setProp1("B");
b.setProp2("X");
DiffDataStructure diff = OffTheShelfUtility.diff(a, b);  // magical recursive comparison happens here

经过比较,实用程序会告诉我两个对象之间的"prop1"不同,而"prop2"相同。我认为这是最自然的DiffDataStructure是一个树,但我不会挑剔,如果代码是可靠的。

可能有点晚了,但我和您的情况一样,最终为您的用例创建了自己的库。由于我被迫自己想出一个解决方案,我决定将其发布在Github上,以避免其他人的辛苦工作。你可以在这里找到:https://github.com/SQiShER/java-object-diff

编辑

下面是一个基于OPs代码的小用法示例:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();
a.setProp1("A");
a.setProp2("X");
b.setProp1("B");
b.setProp2("X");
DiffNode diff = ObjectDifferBuilder.buildDefault().compare(a, b);
assert diff.hasChanges();
assert diff.childCount() == 1;
assert diff.getChild('prop1').getState() == DiffNode.State.CHANGED;

http://javers.org是一个库,它完全满足了您的需要:有像compare(Object leftGraph, Object rightGraph)这样的方法返回Diff对象。Diff包含一个更改列表(ReferenceChange, ValueChange, PropertyChange),例如

given:
DummyUser user =  dummyUser("id").withSex(FEMALE).build();
DummyUser user2 = dummyUser("id").withSex(MALE).build();
Javers javers = JaversTestBuilder.newInstance()
when:
Diff diff = javers.compare(user, user2)
then:
diff.changes.size() == 1
ValueChange change = diff.changes[0]
change.leftValue == FEMALE
change.rightValue == MALE

它可以处理图中的循环。

此外,您可以获得任何图形对象的快照。java有JSON序列化器和反序列化器来快照和更改,因此您可以轻松地将它们保存在数据库中。有了这个库,您可以轻松地实现一个审计模块。

您还可以看看Apache的解决方案。由于它是commons-lang的一部分,大多数项目已经在它们的类路径中包含了它。

检查特定字段的差异:
http://commons.apache.org/proper/commons lang/javadocs/api - 3.9 -/- org/apache/commons/lang3/builder/diffbuilder.html

使用反射检查差异:
http://commons.apache.org/proper/commons lang/javadocs/api - 3.9 -/- org/apache/commons/lang3/builder/reflectiondiffbuilder.html

所有的Java库都只支持Java 7,我处于一种情况,因为我想将其用于Java 6项目,所以我碰巧采用了源代码并以适合Java 6的方式进行了更改,下面是github代码。

https://github.com/sand3sh/javers-forJava6

Jar链接:https://github.com/sand3sh/javers-forJava6/blob/master/build/javers-forjava6.jar

我只将Java 7支持的'<>'固有的强制转换更改为Java 6支持我不能保证所有的功能都能工作,因为我已经注释了一些不必要的代码,它适用于所有自定义对象比较。

这可能会有所帮助,这取决于您在何处使用此代码,它可能是有用的或有问题的。测试这段代码。

    /**
 * @param firstInstance
 * @param secondInstance
 */
protected static void findMatchingValues(SomeClass firstInstance,
        SomeClass secondInstance) {
    try {
        Class firstClass = firstInstance.getClass();
        Method[] firstClassMethodsArr = firstClass.getMethods();
        Class secondClass = firstInstance.getClass();
        Method[] secondClassMethodsArr = secondClass.getMethods();

        for (int i = 0; i < firstClassMethodsArr.length; i++) {
            Method firstClassMethod = firstClassMethodsArr[i];
            // target getter methods.
            if(firstClassMethod.getName().startsWith("get") 
                    && ((firstClassMethod.getParameterTypes()).length == 0)
                    && (!(firstClassMethod.getName().equals("getClass")))
            ){
                Object firstValue;
                    firstValue = firstClassMethod.invoke(firstInstance, null);
                logger.info(" Value "+firstValue+" Method "+firstClassMethod.getName());
                for (int j = 0; j < secondClassMethodsArr.length; j++) {
                    Method secondClassMethod = secondClassMethodsArr[j];
                    if(secondClassMethod.getName().equals(firstClassMethod.getName())){
                        Object secondValue = secondClassMethod.invoke(secondInstance, null);
                        if(firstValue.equals(secondValue)){
                            logger.info(" Values do match! ");
                        }
                    }
                }
            }
        }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
}

深入比较对象的所有属性的好方法是将它们转换为java.util.Map。这样,java.util.Map#equals将深入比较对象,工作完成了!

唯一的问题是将对象转换为Map。一种方法是在org.codehaus.jackson.map.ObjectMapper中使用反射。

因此,com.google.common.collect.MapDifference中存在一个工具来描述这两个映射之间的差异。

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();
a.setProp1("A");
a.setProp2("X");
b.setProp1("B");
b.setProp2("X");
// Convert object to Map
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> aMap =  objectMapper.convertValue(a, Map.class);
Map<String, Object> bMap =  objectMapper.convertValue(b, Map.class);
aMap.equals(bMap); // --> false
// Show deeply all differences
MapDifference<String, Object> diff = Maps.difference(aMap, bMap);

首先,我们必须将对象转换为map:

    public Map<String, Object> objectToMap(Object object) throws JsonProcessingException {
    var mapper = new ObjectMapper();
    final var type = new TypeReference<HashMap<String, Object>>() {
    };
    ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
    return mapper.readValue(ow.writeValueAsString(object), type);
}

之后将map转换为flatten map:

    public Map<String, Object> flatten(Map<String, Object> map) {
    return map.entrySet().stream()
        .flatMap(this::flatten)
        .collect(LinkedHashMap::new, (m, e) -> m.put(camelToUnderScore("/" + e.getKey()), e.getValue()),
            LinkedHashMap::putAll);
}
public Stream<Map.Entry<String, Object>> flatten(Map.Entry<String, Object> entry) {
    if (entry == null) {
        return Stream.empty();
    }
    if (entry.getValue() instanceof Map<?, ?>) {
        return ((Map<?, ?>) entry.getValue()).entrySet().stream()
            .flatMap(e -> flatten(
                new AbstractMap.SimpleEntry<>(camelToUnderScore(entry.getKey() + "/" + e.getKey()),
                    e.getValue())));
    }
    if (entry.getValue() instanceof List<?>) {
        List<?> list = (List<?>) entry.getValue();
        return IntStream.range(0, list.size())
            .mapToObj(i -> new AbstractMap.SimpleEntry<String, Object>(
                camelToUnderScore(entry.getKey() + "/" + i), list.get(i)))
            .flatMap(this::flatten);
    }
    return Stream.of(entry);
}

,最后调用getDifferenceBetween2Maps来获取差异:

    public Map<String, Object> getDifferenceBetween2Maps(final Map<String, Object> leftFlatMap,
                                                     final Map<String, Object> rightFlatMap) {
  
    final MapDifference<String, Object> difference = Maps.difference(leftFlatMap, rightFlatMap);
    var differencesList = new HashMap<String, Object>();
  
    differencesList.putAll(difference.entriesOnlyOnLeft());
    differencesList.putAll(difference.entriesOnlyOnRight());
    return differencesList;
}

使用示例:

Map<String, Object> oldObjectFlatMap = flatten(objectToMap(oldObject));
Map<String, Object> newObjectFlatMap = flatten(objectToMap(newObject));
var differencesList = getDifferenceBetween2Maps(oldObjectFlatMap , newObjectFlatMap);

快速判断两个对象是否不同的一个更简单的方法是使用apache commons库

    BeanComparator lastNameComparator = new BeanComparator("lname");
    logger.info(" Match "+bc.compare(firstInstance, secondInstance));

最新更新