Jackson: impostare BigDecimal come tipo per la deserializzazione JSON di numeri decimali

Nella deserializzazione JSON di un numero decimale, Jackson genera un oggetto del tipo concreto esatto con cui il campo è definito nella classe. Qualora il campo sia definito da un tipo generico o da un’astrazione, di default Jackson deserializza il valore come Double.
Nell’esempio seguente definiamo nella nostra classe un campo di tipo Number, settiamo il suo valore ad un numero decimale e poi eseguiamo serializzazione e deserializzazione JSON.

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());
    }

}

Il risultato ottenuto eseguendo questo programma di test è il seguente, in cui possiamo vedere come il tipo reale del campo value (definito come Number) sia proprio Double.

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

JSON to JAVA: Test= 0.165352

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

Occorre notare che il valore decimale deserializzato è di tipo Double non perchè questo sia il tipo di default di Java per questo tipo di valori, ma perchè esso è il tipo di default usato da Jackson. Possiamo facilmente verificare questa cosa, mantenendo la reference del campo della classe di tipo Number ma settando il suo valore utilizzando esplicitamente un Float, anzichè il default Double.
Nel codice dell’esempio precedente modifichiamo l’istruzione:

i.setValue(0.165352);

in

i.setValue(0.165352f);  // indichiamo esplicitamente un float

Eseguendo nuovamente il programma otteniamo lo stesso risultato di prima, mentre ci si poteva aspettare di ottenere Float come tipo del campo “value”, essendo il suo tipo reale nell’oggetto originale.

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

JSON to JAVA: Test= 0.165352

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

Questo comportamento può essere modificato in modo da ottenere, in fase di deserializzazione, un tipo BigDecimal invece di un Double. Per fare questo occorre abilitare tale feature sull’oggetto ObjectMapper nel modo seguente:

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

Modifichiamo il codice dell’esempio precedente aggiungendo tale direttiva:

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();
        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());
    }

}

Eseguendo nuovamente il programma di test otteniamo il seguente risultato:

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

JSON to JAVA: Test= 0.165352

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

Come possiamo vedere, questa volta, il tipo reale del campo “value” dell’oggetto creato come risultato della deserializzazione JSON è BigDecimal e non più Double. Come detto in apertura di articolo però, questo meccanismo funziona solo quando il tipo atteso è un tipo generico o un’astrazione come il Number del nostro esempio. Se il tipo di destinazione è un tipo concreto, allora la direttiva sull’utilizzo di BigDecimal viene ignorato.
Facciamo un ulteriore esempio in cui questa volta definiamo la variabile d’istanza di un tipo concreto Double invece che del tipo astratto Number e lasciamo comunque abilitata la feature di deserializzare i numeri decimali in BigDecimal.

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());
    }

}

Come possiamo vedere dal risultato seguente, il valore viene deserializzato in base al tipo concreto di destinazione.

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

JSON to JAVA: Test= 0.165352

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

Se invece il tipo concreto della variabile d’istanza “value” fosse Float, pur lasciando abilitato il DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, otterremmo come risultato:

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

JSON to JAVA: Test= 0.165352

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

2 thoughts on “Jackson: impostare BigDecimal come tipo per la deserializzazione JSON di numeri decimali

  1. Ciao, articolo interessante, grazie per la condivisione.

    Seguo da poco il tuo blog e voglio darti qualche consiglio:

    – Introduzione alla libreria: sarebbe opportuno spendere una frase su cosa la libreria faccia, solo durante la lettura dell’articolo ho capito cosa fa, è utile per catturare l’attenzione del lettore.

    – Il blog del tema su mobile non è fruibile, ho dovuto usare readability per leggere il contenuto.

    Al prossimo articolo :)

  2. Ciao,
    sono contento che hai trovato interessante l’articolo e grazie per i consigli.
    Per quanto riguarda Jackson, ho scritto diversi articoli al riguardo ed avevo effettivamente fatto un’introduzione e descritto come incorporarlo in un progetto nel primo di questi; in quelli successivi ho pensato di non ripetere ogni volta la descrizione di cosa sia e a cosa serva, ma di concentrarmi sulle funzionalità che volevo descrivere. Inserendo comunque sempre sterilizzazione/deserializzazione JSON nel titolo degli articolo pensavo fosse piuttosto contestualizzato l’ambito.
    Per quanto riguarda il discorso del template su mobile è un problema che avevo notato con il nuovo template che avevo scelto e stavo personalizzando; non avendo molto tempo per metterci mano ho deciso di optare per la soluzione più rapida di tornare al vecchio template che su mobile rendeva molto bene.
    Grazie ancora per la visita ed i suggerimenti.

    A presto,
    Davis

Leave a Reply to unnikked Cancel reply

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