Michael Burger
Michael Burger

Reputation: 701

Apache Commons DiffBuilder, deep compare

We try to use Appache commons DiffBuilder and also ReflectionDiffBuilder in version 3.7` to compare two complex objects each other.

For a simple object it is working really good, but I'm not able to get it work with properties which are an List of other Complex Objects which could contain again a complex object.

Let me explain better, if we have for Example this object (we will represent it for simplicity by an json object):

{
    "id": 1,
    "name": "Master1",
    "details": [{
        "id": 1,
        "name": "Master1.Detail1"
    }, {
        "id": 2,
        "name": "Master2.Detail2",
        "subDetail": [{
            "id1": 1,
            "name": "Master2.Detail2.SubDetail1"
        }]
    }]
}

In This Case if we change name in SubDetail1 and compare old with new object we need also to be notified that the property in the third level of 3 complex objects changed.

Is this possible with Apaches library? It seems not out of the box?

Upvotes: 3

Views: 2168

Answers (1)

debbeca
debbeca

Reputation: 11

In your parent diff method you should iterate through your collections and compare each sub entities and append all differences in your parent diff result object. source

for (int i = 0; i < first.getDetails().size(); i++) {
        diffBuilder.append("details[" + i + "].name", 
          first.getDetails().get(i).getName(), 
          second.getDetails().get(i).getName());
    }

You may need to have the Detail object implement Diffable as well if it is more complex

for (int i = 0; i < first.getDetails().size(); i++) {
        diffBuilder.append("details[" + i + "]", 
         first.getDetails().get(i).diff(second.getDetails().get(i)));
    }

Granted This all assumes that the items in the list remain a certain set, and we are merely comparing the items by index. If however the list changes via removals or additions the standard behavior of the ReflectionDiffBuilder can detect said changes to the List as a whole and you can interpret the results afterward.

original.diff(changed).getDiffs().stream()
        .flatMap(
            diff -> {
              if (diff.getLeft() instanceof Collection<?>) {
                return Stream.of(
                    "%s dropped %s"
                        .formatted(
                            diff.getFieldName(),
                            CollectionUtils.removeAll(
                                (Collection<?>) diff.getLeft(), (Collection<?>) diff.getRight())),
                    "%s added %s"
                        .formatted(
                            diff.getFieldName(),
                            CollectionUtils.removeAll(
                                (Collection<?>) diff.getRight(), (Collection<?>) diff.getLeft())));
              } else {
                return Stream.of(
                    "%s changed from %s to %s"
                        .formatted(
                            diff.getFieldName(), diff.getLeft().toString(), diff.getRight()));
              }
            })
        .toList();

Or finally Example 2 here makes creative use of the limitation that we could learn from

@lombok.Value
public class DiffableCollectionHolder<T> implements Diffable<Collection<T>> {
    
    Collection<T> collection;

    public DiffResult<Collection<T>> diff(Collection<T> obj) {
        return DiffBuilder.<Collection<T>>builder().setLeft(this.getCollection())
                .setRight(obj).build()
                .append("removals", Collections.emptyList(), CollectionUtils.removeAll(this.getCollection(), obj))
                .append("additions", Collections.emptyList(), CollectionUtils.removeAll(obj, this.getCollection())).build();

    }
}

Upvotes: -1

Related Questions