Jackson: set BigDecimal as type for JSON deserialization of decimal numbers

In the JSON serialization of a decimal number, Jackson creates an object of the exact actual type used when the field is defined in the class. If the field is defined by a generic type or by an abstraction, Jackson as default deserializes the value as Double.
In the following example we define in our class a Number field, we set its value to a decimal number and then we perform JSON serialization and deserialization.

import java.io.IOException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

class MyItem {

    private String desc;
    private Number value;

    public String getDesc() {
        return this.desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Number getValue() {
        return this.value;
    }

    public void setValue(Number value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return this.desc + "= " + this.value;
    }
}

public class JSONBigDecExample {

    public static void main(String[] args) {

        ObjectMapper mapper = new ObjectMapper();
        MyItem i = new MyItem();
        i.setDesc("Test");
        i.setValue(0.165352);

        String s = null;

        try {
            s = mapper.writeValueAsString(i);
        }
        catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        System.out.println("JAVA to JSON: " + s);

        MyItem i2 = null;
        try {
            i2 = mapper.readValue(s, MyItem.class);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("\nJSON to JAVA: " + i2);
        System.out.println("\n\"value\" field type: : " + i2.getValue().getClass());
    }

}

The result obtained by running this test program is the following, where we can see that the actual type of the “value” field (defined as Number) is Double.

JAVA to JSON: {"desc":"Test","value":0.165352}

JSON to JAVA: Test= 0.165352

"value" field type: : class java.lang.Double

It should be noted that the deserialized decimal value is a Double not because this is the default Java type for this type of values, but because it is the default type used by Jackson. We can easily check this keeping the reference of the class field as Number, but setting its value explicitly to a Float, instead the default Double.
In the code of the above example we modify the instruction:

i.setValue(0.165352);

as

i.setValue(0.165352f);  // we explicitly assign a float

By running the program again we get the same result as before, while we could expect to get a Float as type for the “value” field, being that its real type in the original object.

JAVA to JSON: {"desc":"Test","value":0.165352}

JSON to JAVA: Test= 0.165352

"value" field type: : class java.lang.Double

This behavior can be modified to obtain, during deserialization, a BigDecimal object instead of a Double. To do this we have to enable the feature on the ObjectMapper object in the following way:

ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);

We can change the code of the previous example by adding the instruction to enable the feature:

import java.io.IOException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

class MyItem {

    private String desc;
    private Number value;

    public String getDesc() {
        return this.desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Number getValue() {
        return this.value;
    }

    public void setValue(Number value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return this.desc + "= " + this.value;
    }
}

public class JSONBigDecExample {

    public static void main(String[] args) {

        ObjectMapper mapper = new ObjectMapper();

        // here we set BigDecimal as type for floating numbers
        mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);

        MyItem i = new MyItem();
        i.setDesc("Test");
        i.setValue(0.165352);

        String s = null;

        try {
            s = mapper.writeValueAsString(i);
        }
        catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        System.out.println("JAVA to JSON: " + s);

        MyItem i2 = null;
        try {
            i2 = mapper.readValue(s, MyItem.class);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("\nJSON to JAVA: " + i2);
        System.out.println("\n\"Value\" field type: : " + i2.getValue().getClass());
    }

}

Rerunning the test program we get the following result:

JAVA to JSON: {"desc":"Test","value":0.165352}

JSON to JAVA: Test= 0.165352

"Value" field type: : class java.math.BigDecimal

As we can see, this time the actual type of the “value” field of the object created as a result of JSON deserialization is BigDecimal and no longer Double.
As mentioned at the article beginning, however, this mechanism only works when the expected type is a generic type or an abstraction like Number in our example. If the destination type is a concrete type, then the directive about using BigDecimal is ignored.

Let’s take a further example where this time we define the instance variable of a concrete type Double instead of the abstract type Number and we keep anyway the feature to deserialize decimal numbers in BigDecimal enabled.

import java.io.IOException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

class MyItem {

    private String desc;
    private Double value;

    public String getDesc() {
        return this.desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public Double getValue() {
        return this.value;
    }

    public void setValue(Double value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return this.desc + "= " + this.value;
    }
}

public class JSONBigDecExample {

    public static void main(String[] args) {

        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
        MyItem i = new MyItem();
        i.setDesc("Test");
        i.setValue(0.165352);

        String s = null;

        try {
            s = mapper.writeValueAsString(i);
        }
        catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        System.out.println("JAVA to JSON: " + s);

        MyItem i2 = null;
        try {
            i2 = mapper.readValue(s, MyItem.class);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("\nJSON to JAVA: " + i2);
        System.out.println("\n\"Value\" field type: : " + i2.getValue().getClass());
    }

}

As we can see from the following result, the value is serialized according to the actual destination type.

JAVA to JSON: {"desc":"Test","value":0.165352}

JSON to JAVA: Test= 0.165352

"Value" field type: : class java.lang.Double

If the “value” instance variable concrete type was Float, even leaving enabled DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, we would get as a result:

JAVA to JSON: {"desc":"Test","value":0.165352}

JSON to JAVA: Test= 0.165352

"Value" field type: : class java.lang.Float

Leave a Reply

Your email address will not be published. Required fields are marked *