2

I have a JsonNode which is built out of a Map<String, Object>:

Map<String, Object> actual = Map.of("test", 3L);
JsonNode actualNode = mapper.valueToTree(actual);

I would like to compare such node against an expected file, that I load as such:

String expected = "{\"test\": 3}";
JsonNode expectedNode = mapper.readTree(expected);

When I print these two nodes, I see that they are exactly the same:

>> System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(expectedNode));
>> System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(actualNode)); 

{
    "test": 3
}

However, when I compare the two nodes using assert-j, I get the following error:

Assertions.assertThat(actualNode).isEqualTo(expectedNode);

java.lang.AssertionError: 
Expecting:
 <"{"test":3} (ObjectNode@7ef2d7a6)">
to be equal to:
 <"{"test":3} (ObjectNode@5dcbb60)">
but was not.

If I debug the .isEqualTo of the assertion, I see that the failure happens because:

  • The 3 in the actual node is a LongNode (which I understand since the original map contains a 3L)
  • The 3 in the expected node though is an IntNode

So when the equality is tested, the IntNode is not even an instanceof LongNode and so the assertion fails.

However, I really don't control how the Map behind the actual node is built. What can I do to make the assertion work in this case?

Fully reproducible example:

Map<String, Object> actual = Map.of("test", 3L);
String expected = "{\"test\": 3}";
ObjectMapper mapper = new ObjectMapper();
JsonNode expectedNode = mapper.readTree(expected);
JsonNode actualNode = mapper.valueToTree(actual);
Assertions.assertThat(actualNode).isEqualTo(expectedNode);

P.s. I have currently fixed it by serializing and deserializing the node:

JsonNode newActualNode = mapper.readTree(mapper.writeValueAsString(actualNode));

... but I was looking for something cleaner.

Matteo NNZ
  • 11,930
  • 12
  • 52
  • 89

3 Answers3

2

The reason is because the jackson defaults to int when it can fit in the untyped value (i.e up to 32 bit). So the assertj (even the jupiter assertions) has correctly failed the assertion, because actual and expected are indeed two different types. This behaviour can be controlled by the DeserializationFeature.USE_LONG_FOR_INTS feature. The last sentence of the java doc of that property says this;

Feature is disabled by default, meaning that "untyped" integral numbers will by default be deserialized using Integer if value fits.

You can simply enable it when you create the ObjectMapper

mapper = new ObjectMapper().enable(DeserializationFeature.USE_LONG_FOR_INTS);

The downside is jackson will create values in larger long type unnecessarily.

Laksitha Ranasingha
  • 4,321
  • 1
  • 28
  • 33
1

As you have discovered, AssertJ is behaving correctly in reporting that the nodes are not equal as one has a long child and the other has an int.

You wrote:

When I print these two nodes, I see that they are exactly the same

Printing the nodes using toString proves that their toString methods produce the same result. That's not the same as them being exactly the same.

If what you are looking for is to validate the toString of the node then:

assertThat(actualNode).hasToString(expectedNode.toString());
sprinter
  • 27,148
  • 6
  • 47
  • 78
  • That could have been one solution indeed, even though I preferred the one configuring directly the object mapper, thanks! – Matteo NNZ Jun 19 '22 at 21:40
1

JsonUnit with AssertJ style is probably the best option:

assertThatJson(actualNode).isEqualTo(expectedNode); // passes

However, I would have expected the AssertJ recursive comparison to work as well:

assertThat(actualNode).usingRecursiveComparison().isEqualTo(expectedNode); // fails

but this is not the case.

I created #2672 to track this potential improvement.

Stefano Cordio
  • 1,687
  • 10
  • 20
  • Thanks, but I can't add another dependency unfortunately. Indeed, usingRecursiveComparison didn't work for me either but I think for the same reason (the instanceof check) – Matteo NNZ Jun 19 '22 at 21:41
  • 1
    The recursive comparison should have a lenient type check by default, but it doesn't work in this case as Jackson types are not known to AssertJ. I've raised https://github.com/assertj/assertj-core/issues/2672 to evaluate this use case. – Stefano Cordio Jun 19 '22 at 21:49