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:
3
in the actual node is a LongNode
(which I understand since the original map contains a 3L
)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.
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.