Search code examples
javajacksonassertj

AssertJ: how can I make JsonNode comparison handle IntNode and LongNode as same?


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.


Solution

  • 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.