jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

`WriteValueAsString` produces different result in comparison with putting BigDecimal to `ObjectNode`

Open MothF opened this issue 2 years ago • 4 comments

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

MothF avatar May 18 '22 21:05 MothF

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.

cowtowncoder avatar May 19 '22 00:05 cowtowncoder

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?

MothF avatar May 19 '22 09:05 MothF

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.

cowtowncoder avatar May 19 '22 17:05 cowtowncoder

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: @.***>

jeruelle avatar May 19 '22 17:05 jeruelle