Search code examples
javajsonjava-8jacksonjackson-databind

Big number deserialisation throws NumberFormatException


I wrote below program to convert Parameter to JsonNode. Getting NumberFormatException when I set 3.9E38. How to set BigDecimal in JsonNode?

public class JsonCheck {
    public static void main(String[] args) throws JsonProcessingException {

        ObjectMapper om = new ObjectMapper();
        Parameter p = new Parameter();
        p.setPrevValue(new BigDecimal("3.9E38"));
        JsonNode node =  om.convertValue(p,JsonNode.class);
        System.out.println(node);

    }
    public static class DefaultValueSerializer extends JsonSerializer<BigDecimal> {
    @Override
    public void serialize(BigDecimal o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeNumber(o.toPlainString());
    }
}

    public static class Parameter {

        @JsonSerialize(using = DefaultValueSerializer.class)
        private BigDecimal prevValue;

        public void setPrevValue(BigDecimal prevValue) {
            this.prevValue = prevValue;
        }


        public BigDecimal getPrevValue() {
            return prevValue;
        }
    }
}

Solution

  • It looks like this because com.fasterxml.jackson.core.JsonParser implementation used during deserialization (in your case conversion) process can not parse really big numbers. By default, number is treated as Long and value 3.9E38 exceeds it's range. Unfortunately, features USE_BIG_DECIMAL_FOR_FLOATS and USE_BIG_INTEGER_FOR_INTS are not handled in this case and we need to write customization here. See below example:

    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.util.JsonParserDelegate;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.fasterxml.jackson.databind.deser.std.JsonNodeDeserializer;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    
    import java.io.IOException;
    import java.math.BigDecimal;
    
    public class JsonApp {
    
        public static void main(String[] args) {
            Parameter parameter = new Parameter();
            parameter.setPrevValue(new BigDecimal("3.9E38"));
    
            SimpleModule module = new SimpleModule();
            module.addDeserializer(JsonNode.class, new BigDecimalFirstJsonNodeDeserializer());
    
            ObjectMapper mapper = new ObjectMapper();
            mapper.registerModule(module);
    
            JsonNode node = mapper.convertValue(parameter, JsonNode.class);
            System.out.println(node);
        }
    }
    
    class BigDecimalFirstJsonNodeDeserializer extends JsonNodeDeserializer {
    
        @Override
        public JsonNode deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            return super.deserialize(new BigDecimalJsonParser(p), ctxt);
        }
    }
    
    class BigDecimalJsonParser extends JsonParserDelegate {
    
        public BigDecimalJsonParser(JsonParser parser) {
            super(parser);
        }
    
        @Override
        public NumberType getNumberType() {
            return NumberType.BIG_DECIMAL;
        }
    
        @Override
        public BigDecimal getDecimalValue() throws IOException {
            String value = getText();
            return new BigDecimal(value);
        }
    }
    
    class BigDecimalPlainStringJsonSerializer extends JsonSerializer<BigDecimal> {
        @Override
        public void serialize(BigDecimal o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeNumber(o.toPlainString());
        }
    }
    
    class Parameter {
    
        @JsonSerialize(using = BigDecimalPlainStringJsonSerializer.class)
        private BigDecimal prevValue;
    
        public void setPrevValue(BigDecimal prevValue) {
            this.prevValue = prevValue;
        }
    
    
        public BigDecimal getPrevValue() {
            return prevValue;
        }
    }
    

    Above code prints:

    {"prevValue":3.9E+38}