Jackson: usare le annotations @JsonSerialize/@JsonDeserialize per registrare un serializzatore/deserializzatore custom

In un post precedente abbiamo visto come creare con Jackson un serializzatore JSON personalizzato (e anche un deserializzatore) e come registrarlo utilizzando la classe SimpleModule. Tale approccio, possibile solo a partire dalla versione 1.7, è definito dalla relativa pagina wiki di Jackson come il “metodo consigliato per registrare un serializzatore personalizzato”. Questo però presuppone di avere accesso all’oggetto ObjectMapper utilizzato per serializzare e deserializzare e di poter quindi registrare su di esso il SimpleModule su cui abbiamo aggiunto il nostro serializzatore. In alcuni casi questo non è possibile, perchè l’ObjectMapper, configurato con le opportune features, è magari centralizzato e fuori dal nostro controllo e le nostre classi sono soltanto delle entità che esso si aspetta di poter opportunamente gestire. In questo caso dobbiamo utilizzare un metodo alternativo per registrare il nostro serializzatore ed indicare che deve essere utilizzato per serializzare gli oggetti di una determinata classe. Questo metodo consiste nell’utilizzo dell’annotazione @JsonSerialize e della valorizzazione della sua proprietà “using”. Più precisamente, occorre:

  • annotare con @JsonSerialize la classe per la quale vogliamo definire un serializzatore personalizzato
  • valorizzare la proprietà “using” dell’annotation con la classe che rappresenta il serializzatore

Il discorso è analogo per registrare un deserializzatore custom, per il quale si utilizza allo stesso modo l’annotazione @JsonDeserialize.

Vediamo un esempio, andando a riprendere le classi utilizzate nel precedente articolo. La classe per la quale volevamo registrare un serializzatore ed un deserializzatore JSON personalizzati era la seguente:

class SWEngineer {

    private long id;
    private String name;
    private String[] languages;

    public SWEngineer(long id, String name, String[] languages) {
        this.id = id;
        this.name = name;
        this.languages = languages;
    }

    // GETTERS E SETTERS OMESSI

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("ID: ").append(this.id).append("\nNome: ")
                .append(this.name).append("\nLinguaggi:");

        for (String s: this.languages) {
            sb.append(" ").append(s);
        }
        return sb.toString();
    }
}

Avevamo quindi creato le classi CustomSerializer e CustomDeserializer e le avevamo utilizzate in una classe di test per serializzare e deserializzare un oggetto di tipo SWEngineer, dopo averle aggiunte ad un modulo e registrate con l’ObjectMapper nel modo seguente:

SimpleModule mod = new SimpleModule("MyModule");
mod.addSerializer(new CustomSerializer(SWEngineer.class));
mod.addDeserializer(SWEngineer.class, new CustomDeserializer(SWEngineer.class));
mapper.registerModule(mod);

Ora, come abbiamo detto, supponiamo di non poter più utilizzare questo metodo di registrazione dei nostri oggetti custom sul mapper, per cui modifichiamo la classe di test commentando le quattro righe di codice appena descritte. Sulla classe di test non effettuiamo nessuna altra modifica ed essa alla fine si presenta come segue:

public class CustomSerDeserExample {

    public static void main (String[] args) {

        SWEngineer swe1 = new SWEngineer(1, "Mark", new String[]{"Java", "Python", "C++", "Scala"});

        ObjectMapper mapper = new ObjectMapper();

//	NON UTILIZZIAMO PIU' QUESTO METODO
//      SimpleModule mod = new SimpleModule("MyModule");
//      mod.addSerializer(new CustomSerializer(SWEngineer.class));
//      mod.addDeserializer(SWEngineer.class, new CustomDeserializer(SWEngineer.class));
//      mapper.registerModule(mod);

        System.out.println("--- OGGETTO ORIGINALE ---\n" + swe1);

        String s = null;

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

        System.out.println("\n--- JAVA to JSON (Custom) ---\n" + s);

        SWEngineer sweOut = null;
        try {
            sweOut = mapper.readValue(s, SWEngineer.class);

        }
        catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("\n--- JSON to JAVA ---\n" + sweOut);
    }
}

Per fornire ugualmente l’indicazione di utilizzare il serializzatore e deserializzatore custom per il processamento di oggetti della classe SWEngineer, come abbiamo visto in apertura, possiamo utilizzare le annotations @JsonSerialize e @JsonDeserialize di Jackson. Annotiamo quindi la classe SWEngineer specificando, tramite la proprietà “using” di entrambe le annotazioni, quali sono le classi da utilizzare per serializzare e deserializzare le sue istanze.

@JsonSerialize(using=CustomSerializer.class)
@JsonDeserialize(using=CustomDeserializer.class)
class SWEngineer {

    private long id;
    private String name;
    private String[] languages;

    public SWEngineer(long id, String name, String[] languages) {
        this.id = id;
        this.name = name;
        this.languages = languages;
    }

    // GETTERS E SETTERS OMESSI

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("ID: ").append(this.id).append("\nNome: ")
                .append(this.name).append("\nLinguaggi:");

        for (String s: this.languages) {
            sb.append(" ").append(s);
        }
        return sb.toString();
    }
}

Tornando alla classe di test e provando ad eseguirla otteniamo il risultato seguente:

--- OGGETTO ORIGINALE ---
ID: 1
Nome: Mark
Linguaggi: Java Python C++ Scala

--- JAVA to JSON (Custom) ---
{"id":1,"name":"Mark","languages":"Java;Python;C++;Scala;"}

--- JSON to JAVA ---
ID: 1
Nome: Mark
Linguaggi: Java Python C++ Scala

Il risultato è lo stesso che avevamo ottenuto nel post precedente, quando avevamo registrato i nostri serializzatore e deserializzatore personalizzati utilizzando un SimpleModule e l’ObjectMapper.
Con questo metodo però, non abbiamo avuto bisogno di effettuare nessuna operazione su nessun altro oggetto, ma ci siamo limitati ad aggiungere le annotations alla nostra classe SWEngineer.

Il codice completo dell’esempio è scaricabile qui:

Leave a Reply

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