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

Default serialisation for BigInteger should be as a JSON String

Open Woodz opened this issue 6 years ago • 16 comments

I am aware of the ability to configure Jackson to serialize BigInteger as a JSON String (https://github.com/FasterXML/jackson-databind/issues/1911) but I believe that it should be the default behaviour, with serialisation as a JSON Number configurable if required. The reason I believe this is that JSON Number does not have arbitrary length, which will lead to loss of precision during serialisation.

Supporting evidence: https://stackoverflow.com/a/39681707/323177 https://stackoverflow.com/a/38357877/323177 https://www.avioconsulting.com/blog/overcoming-javascript-numeric-precision-issues

Woodz avatar Oct 23 '19 08:10 Woodz

I disagree: I think number should be treated as number, unless configured to be serialized as JSON String (either globally or on per-field basis; both of which are possible). I think same is true for BigDecimal as well. This both from conceptual (what I think is Right) and practical (change would be major backwards incompatible breakage leading to dozens of bug reports that I would then need to address). In fact, I do not think there is much anything I can think of to suggest that such a change would be good thing.

But if you do want to convince me otherwise, your best bet would be to bring this up on mailing list, either jackson-dev (most likely) -- https://groups.google.com/forum/#!forum/jackson-dev -- or possible user list (https://groups.google.com/forum/#!forum/jackson-user).

cowtowncoder avatar Oct 23 '19 17:10 cowtowncoder

(I've removed my comment here as it was mistakenly added to an issue surrounding BigInteger and not BigDecimal. I've opened #5193.)

garretwilson avatar Jun 16 '25 14:06 garretwilson

  • we can't change behaviour without affecting existing users - like it or not, some of them might rely on the jackson works today
  • we could introduce a config (eg a SerializationFeature) that could be set be users if they want BigInteger (this issue) or BigDecimal (not this issue) to be serialized as a JSON string.
  • Jackson 3 could change the default but the risk is that Jackson 3 could be released before any changes for this issue are done.

pjfanning avatar Jun 16 '25 14:06 pjfanning

There is already com.fasterxml.jackson.core.json.JsonWriteFeature#WRITE_NUMBERS_AS_STRINGS

This is possibly too broad as it affects all numbers that are written to the JSON, not just BigInteger/BigDecimal.

So the option would be to add JsonWriteFeature#WRITE_BIG_INTEGERS_AS_STRINGS and JsonWriteFeature#WRITE_BIG_DECMALS_AS_STRINGS or possibly a single new feature (but what to call it).

pjfanning avatar Jun 16 '25 14:06 pjfanning

Oops, I'm sorry. The bug reporter form asked if I had searched for existing issues, and so I searched and accidentally found this issue, which was referring to BigInteger and not BigDecimal. I've opened a separate ticket #5193.

garretwilson avatar Jun 16 '25 14:06 garretwilson

This appears in some of our tests. So this is one option.

        mapper.configOverride(BigInteger.class)
            .setFormat(JsonFormat.Value.forShape(JsonFormat.Shape.STRING));

I'm adding this for users who run into this issue. I'm aware that some other users want us to change the default.

You can also annotate fields with @JsonFormat(shape=Shape.STRING).

pjfanning avatar Jun 16 '25 16:06 pjfanning

Plain String is controlled by this feature

https://github.com/FasterXML/jackson-core/blob/373f108a16e3f315e9df9eaecb482e43f9953621/src/main/java/com/fasterxml/jackson/core/JsonGenerator.java#L190-L204

pjfanning avatar Jun 16 '25 16:06 pjfanning

What @pjfanning show is THE recommended way to do configuration.

I am 100% against change to serialize Numbers (BigInteger or otherwise) as JSON Strings and consider it unlikely default will ever be changed for Jackson.

Will leave open only for sake of visibility.

cowtowncoder avatar Jun 16 '25 16:06 cowtowncoder

Plain String is controlled by this feature …

@pjfanning , could help me out here? I'm actually setting up read-only JSON readers and writers. First I construct an ObjectMapper using a builder:

return JsonMapper.builder().serializationInclusion(JsonInclude.Include.NON_ABSENT) //
    .withConfigOverride(BigDecimal.class, configOverride -> configOverride.setFormat(JsonFormat.Value.forShape(Shape.STRING))) //
    .enable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN) //

But here SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN is deprecated. How do I set up the reader and writer to use WRITE_BIGDECIMAL_AS_PLAIN if I'm not working in terms of a generator?

garretwilson avatar Jun 16 '25 17:06 garretwilson

Deprecated doesn't mean it shouldn't work - it's just a warning that support is likely to be removed in future. JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN is checked in jackson-databind serialization code too. You could try playing around with the settings to see what works. JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN is a better bet if it works for you since this is not deprecated.

pjfanning avatar Jun 16 '25 17:06 pjfanning

But I don't expose a generator. My design is to use the modern ObjectReader and ObjectWriter to provide a preconfigured way to read and write JSON; e.g.:

  static {
    OBJECT_MAPPER = createJsonObjectMapper();
    JSON_READER = OBJECT_MAPPER.reader();
    JSON_WRITER = OBJECT_MAPPER.writer();
  }

I guess I'll just go back to the way I was doing it before:

return JsonMapper.builder().serializationInclusion(JsonInclude.Include.NON_ABSENT) //
    .addModule(new SimpleModule() //
        .addSerializer(BigDecimal.class, new JsonSerializer<BigDecimal>() {
          @Override
          public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeString(value.toPlainString());
          }
        }) //
        .addKeySerializer(BigDecimal.class, new JsonSerializer<BigDecimal>() {
          @Override
          public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            gen.writeFieldName(value.toPlainString());
          }
        })
  …

Eventually I'll put these in a reusable module.

Thanks anyway for the tips.

garretwilson avatar Jun 16 '25 19:06 garretwilson

This appears to compile for me:

JsonMapper.builder()
                .enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN)
                .build();

pjfanning avatar Jun 16 '25 19:06 pjfanning

Ah, thanks, @pjfanning ! I had a SerializationFeature in my code for something else (disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)), so I was thinking I had to use the SerializationFeature here as well. It looks like enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN) works! 👏

garretwilson avatar Jun 16 '25 19:06 garretwilson

On deprecation of JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN -- while this will not be removed from 2.x ever, there is replacement (since this is format-agnostic setting):

StreamWriteFeature.WRITE_BIGDECIMAL_AS_PLAIN

using which removed deprecation warnings.

And since it is Streaming-level feature, it will change all writes with BigDecimal to use Plain notation, regardless of databind-level logical type (probably not much actual difference come to think of it now).

cowtowncoder avatar Jun 16 '25 21:06 cowtowncoder

using which removed deprecation warnings.

I think what was giving me deprecation warnings was SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN, not JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, but what you indicate seems an even better flag, so I'll use it. Thanks.

garretwilson avatar Jun 17 '25 01:06 garretwilson

Ah. It is possible JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN not yet marked as deprecated. But it will be removed from 3.0, whereas StreamWriteFeature counterpart remains.

cowtowncoder avatar Jun 17 '25 01:06 cowtowncoder