Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 737
Matteo NNZ Avatar asked Oct 19 '25 08:10

Matteo NNZ


2 Answers

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.

like image 81
Laksitha Ranasingha Avatar answered Oct 21 '25 21:10

Laksitha Ranasingha


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());
like image 27
sprinter Avatar answered Oct 21 '25 20:10

sprinter



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!