jackson-databind
jackson-databind copied to clipboard
`WriteValueAsString` produces different result in comparison with putting BigDecimal to `ObjectNode`
Describe the bug The way jackson writes value as String differs from output of ObjectNode.toString. There is a case when I need to write ObjectNode to String and return this value. While writing unit-tests I figured out that they fail due to behaviour mismatching.
In tests I create expected response from my controller by manually constructing ObjectNode and adding values I need. The thing is that actual response from my controller differs from what is built manually because from controller I return string produced by WriteValueAsString. This case may be inappropriate, but what I expected was that values are the same
Below is a brief test describing my concern, which should fail in my opinion
Version information jackson-databind: 2.13.0 java: temurin-11
To Reproduce `@Test void test() throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper().configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
BigDecimal value = new BigDecimal(20);
assertThat(value.toString()).isEqualTo("20");
ObjectNode objectNode = mapper.createObjectNode();
objectNode.put("value", value);
assertThat(objectNode.toString()).isEqualTo("{\"value\":2E+1}");
String afterWrite = mapper.writeValueAsString(objectNode);
assertThat(afterWrite).isEqualTo("{\"value\":20}");
}`
Additional context Mapper is configured with WRITE_BIGDECIMAL_AS_PLAIN as true
This is probably not a bug: JsonNode.toString()
cannot use anything other than ObjectMapper
with default configuration -- it has no other context. If you want differently configured output, you have to use ObjectMapper.writeValueAsString()
, toString()
method will always just use defaults.
Note that this is true regardless of how ObjectNode
is constructed: it will NOT retain a reference to mapper that created it. This is intentional and will not (be) change(d).
I recommend not using toString()
method for anything other than quick diagnostics: mapper.writeValueAsString()
(and other methods) are superior in about every way.
Oh, I see. Thanks for your response. One more question if you don't mind
I faced the same issue with pure ObjectMapper
without any additional configuration (or with configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, false)
)
My question is -- if ObjectNode.put(BigDecimal) mutates scale of the number (by invocation of stripTrailingZeros
), why do we get different output during writeValueAsString
?
I don't think ObjectNode itself does; JsonNodeDeserializer
just uses JsonParser
like so:
JsonParser.NumberType nt = p.getNumberType();
if (nt == JsonParser.NumberType.BIG_DECIMAL) {
return nodeFactory.numberNode(p.getDecimalValue());
}
nodeFactory
is of type JsonNodeFactory
and it has setting "big decimal exact", defaulting to false
(for backwards-compatibility). I think that means that normalization is used by default.
You can configure ObjectMapper
with JsonNodeFactory
; and to get different instance can use
JsonNodeFactory jnf = JsonNodeFactory.withExactBigDecimals(true);
So I think that explains it; by default ObjectNode
is constructed with normalized BigDecimal
but can be reconfigured in a bit of round about way.
IMO-This is a great solution answer and fix for coders. It will make the system run smooth and properly but hide the programmers intent agenda. It should stay in compliance with the zoom.us/trust for unilateralism concern
Get Outlook for Androidhttps://aka.ms/AAb9ysg
From: Tatu Saloranta @.>
Sent: Thursday, May 19, 2022 1:13:26 PM
To: FasterXML/jackson-databind @.>
Cc: Subscribed @.***>
Subject: Re: [FasterXML/jackson-databind] WriteValueAsString
produces different result in comparison with putting BigDecimal to ObjectNode
(Issue #3491)
I don't think ObjectNode itself does; JsonNodeDeserializer just uses JsonParser like so:
JsonParser.NumberType nt = p.getNumberType();
if (nt == JsonParser.NumberType.BIG_DECIMAL) {
return nodeFactory.numberNode(p.getDecimalValue());
}
nodeFactory is of type JsonNodeFactory and it has setting "big decimal exact", defaulting to false (for backwards-compatibility). I think that means that normalization is used by default.
You can configure ObjectMapper with JsonNodeFactory; and to get different instance can use
JsonNodeFactory jnf = JsonNodeFactory.withExactBigDecimals(true);
So I think that explains it; by default ObjectNode is constructed with normalized BigDecimal but can be reconfigured in a bit of round about way.
— Reply to this email directly, view it on GitHubhttps://github.com/FasterXML/jackson-databind/issues/3491#issuecomment-1131973870, or unsubscribehttps://github.com/notifications/unsubscribe-auth/APHYEWJXL35SDOSRGTG3ALLVKZZDNANCNFSM5WJ2ZBAQ. You are receiving this because you are subscribed to this thread.Message ID: @.***>