2

I'm using java-object-diff to get differences between two objects parsed from xml by JAXB. In below example, I'm using the same string to test if I get no differences, however log.info("has changes: " + diff5.hasChanges()); logs true.

JAXBContext context1 = JAXBContext.newInstance(Item.class);
Unmarshaller m1 = context1.createUnmarshaller();
Item base = (Item) m1.unmarshal(new StringReader(s));
Item working = (Item) m1.unmarshal(new StringReader(s));
DiffNode diff5 = ObjectDifferBuilder
        .buildDefault()
        .compare(working, base);
log.info("has changes: " + diff5.hasChanges());
diff5.visit((node, visit) -> {
    final Object baseValue = node.canonicalGet(base);
    final Object workingValue = node.canonicalGet(working);
    final String message = node.getPath() + " changed from " +
            baseValue + " to " + workingValue;
    System.out.println(message);
});

The message I get from System.out.println is always the same, saying it has changed from null to <the actual value> This happens for every property. E.g.

content changed from null to Mit dem Wasserinonisator

I have verified that the both Items have the same content and none of the both actualy is not null, but the exact same content.

Item is a pojo with many subclasses (all getters and setters are present), e.g.

public class Item {

@XmlElement(name = "ASIN", required = true)
protected String asin;
@XmlElement(name = "ParentASIN")
protected String parentASIN;
@XmlElement(name = "Errors")
protected Errors errors;
@XmlElement(name = "DetailPageURL")
protected String detailPageURL;
@XmlElement(name = "ItemLinks")
protected ItemLinks itemLinks;
@XmlElement(name = "SalesRank")
protected String salesRank;
@XmlElement(name = "SmallImage")
protected Image smallImage;
}

Is there any way to make java-object-diff work, to make it compare the values correctly?

baao
  • 71,625
  • 17
  • 143
  • 203
  • Obviously `node.canonicalGet(base)` returns `null`. So what is the implementation of `DiffNode` and `ObjectDifferBuilder`? – Timothy Truckle Aug 09 '17 at 09:03
  • I have tried to reproduce your problem with your code, but I see a different behavior. When I run the program the `diff5.hasChanges() is false`. Please double-check that you show us the source-code as you run it. – Tobias Otto Aug 09 '17 at 13:16
  • Ja, das ist auf jeden Fall genau der Code den ich benutze. Bringt es dir was wenn ich die kompletten Klassen poste und xml dazu? @TobiasOtto – baao Aug 09 '17 at 14:02
  • Ich würde mir den gesamten Code ansehen und das Verhalten nachstellen können. – Tobias Otto Aug 09 '17 at 14:05
  • @Michael Could you post a link to your code so I can take a closer look? – SQiShER Aug 10 '17 at 19:35
  • @SQiShER thanks. Yes sure, here's a project demonstrating the issue https://github.com/baao/objectdiffdemo – baao Aug 11 '17 at 10:20

1 Answers1

1

After taking a closer look at your code I know what's wrong. The first problem is the fact, that JAXB doesn't generate equals methods. For the most part, that's not a problem, because the ObjectDiffer can establish the relationship between objects based on the hierarchy. Things get more complicated when ordered or unordered Collections are involved, because the ObjectDiffer needs some kind of way to establish the relationship between the collection items in the base and working instance. By default it relies on the lookup mechanism of the underlying collection (which typically involves on or more of the methods hashCode, equals or compareTo.)

In your case this relationship cannot be established, because none of your classes (but especially those contained in Lists and Sets) implement a proper equals method. This means that instances are only ever equal to themselves. This is further complicated by the fact, that the responsible classes represent value objects and don't have any hard identifier, that could be used to easily establish the relationship. Therefore the only option is to provide custom equals methods that simply compare all properties. The consequence is, that the slightest change on those objects will cause the ObjectDiffer to mark the base version as REMOVED and the working version as ADDED. But it will also not mark them as CHANGED, when they haven't actually changed. So that's something.

I'm not sure how easy it is to make JAXB generate custom equals methods, so here are some alternative solutions possible with java-object-diff:

  1. Implement your own de.danielbechler.diff.identity.IdentityStrategy for the problematic types and provide them to the ObjectDifferBuilder, like so (example uses Java 8 Lambdas):

    ObjectDifferBuilder
      .startBuilding()
      .identity()
      .ofCollectionItems(ItemLinks.class, "itemLink").via((working, base) -> {
        ItemLink workingItemLink = (ItemLink) working;
        ItemLink baseItemLink = (ItemLink) base;
        return StringUtils.equals(workingItemLink.getDescription(), baseItemLink.getDescription())
            && StringUtils.equals(workingItemLink.getURL(), baseItemLink.getURL());
      })
      // ...
      .and().build();
    
  2. Ignore problematic properties during comparison. Obviously this may not be what you want, but it's an easy solution in case you don't really care about the specific object.

    ObjectDifferBuilder
      .startBuilding()
      .inclusion()
      .exclude().type(Item.ImageSets.class)
      .and().build();
    

A solution that causes JAXB to generate custom equals methods would be my preferred way to go. I found another post that claims it's possible, so maybe you want to give this a try first, so you don't have to customize your ObjectDiffer.

I hope this helps!

SQiShER
  • 1,045
  • 7
  • 10
  • Thanks! So I will just let JAXB create proper equals methods (or my IDE if first fails) and it'll work. Nice project btw. ! – baao Aug 12 '17 at 14:50