Jackson contro Gson

1. Introduzione

In questo articolo, confronteremo le API Gson e Jackson per serializzare e deserializzare i dati JSON in oggetti Java e viceversa.

Gson e Jackson sono librerie complete che offrono supporto per l'associazione dati JSON per Java. Ognuno è un progetto open source sviluppato attivamente che offre la gestione di tipi di dati complessi e il supporto per Java Generics.

E nella maggior parte dei casi, entrambe le librerie possono deserializzare in un'entità senza modificare una classe di entità, il che è importante nei casi in cui uno sviluppatore non ha accesso al codice sorgente dell'entità.

2. Dipendenza da Gson Maven

 com.google.code.gson gson ${gson.version} 

Puoi ottenere l'ultima versione di Gson qui.

3. Serializzazione Gson

La serializzazione converte gli oggetti Java in output JSON. Considera le seguenti entità:

public class ActorGson { private String imdbId; private Date dateOfBirth; private List filmography; // getters and setters, default constructor and field constructor omitted } public class Movie { private String imdbId; private String director; private List actors; // getters and setters, default constructor and field constructor omitted }

3.1. Serializzazione semplice

Cominciamo con un esempio di serializzazione da Java a JSON:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); ActorGson rudyYoungblood = new ActorGson( "nm2199632", sdf.parse("21-09-1982"), Arrays.asList("Apocalypto", "Beatdown", "Wind Walkers") ); Movie movie = new Movie( "tt0472043", "Mel Gibson", Arrays.asList(rudyYoungblood)); String serializedMovie = new Gson().toJson(movie);

Ciò si tradurrà in:

{ "imdbId": "tt0472043", "director": "Mel Gibson", "actors": [{ "imdbId": "nm2199632", "dateOfBirth": "Sep 21, 1982 12:00:00 AM", "filmography": ["Apocalypto", "Beatdown", "Wind Walkers"] }] }

Per impostazione predefinita:

  • Tutte le proprietà vengono serializzate perché non hanno valori null
  • Il campo dateOfBirth è stato tradotto con il modello di data Gson predefinito
  • L'output non è formattato ei nomi delle proprietà JSON corrispondono alle entità Java

3.2. Serializzazione personalizzata

L'utilizzo di un serializzatore personalizzato ci consente di modificare il comportamento standard. Possiamo introdurre un formattatore di output con HTML, gestire valori nulli , escludere proprietà dall'output o aggiungere un nuovo output.

ActorGsonSerializer modifica la generazione del codice JSON per l' elemento ActorGson :

public class ActorGsonSerializer implements JsonSerializer { private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); @Override public JsonElement serialize(ActorGson actor, Type type, JsonSerializationContext jsonSerializationContext) { JsonObject actorJsonObj = new JsonObject(); actorJsonObj.addProperty("IMDB Code", actor.getImdbId()); actorJsonObj.addProperty("Date Of Birth", actor.getDateOfBirth() != null ? sdf.format(actor.getDateOfBirth()) : null); actorJsonObj.addProperty("N° Film: ", actor.getFilmography() != null ? actor.getFilmography().size() : null); actorJsonObj.addProperty("filmography", actor.getFilmography() != null ? convertFilmography(actor.getFilmography()) : null); return actorJsonObj; } private String convertFilmography(List filmography) { return filmography.stream() .collect(Collectors.joining("-")); } }

Per escludere la proprietà director , l' annotazione @Expose viene utilizzata per le proprietà che vogliamo considerare:

public class MovieWithNullValue { @Expose private String imdbId; private String director; @Expose private List actors; }

Ora possiamo procedere con la creazione di oggetti Gson utilizzando la classe GsonBuilder :

Gson gson = new GsonBuilder() .setPrettyPrinting() .excludeFieldsWithoutExposeAnnotation() .serializeNulls() .disableHtmlEscaping() .registerTypeAdapter(ActorGson.class, new ActorGsonSerializer()) .create(); SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); ActorGson rudyYoungblood = new ActorGson("nm2199632", sdf.parse("21-09-1982"), Arrays.asList("Apocalypto","Beatdown", "Wind Walkers")); MovieWithNullValue movieWithNullValue = new MovieWithNullValue(null, "Mel Gibson", Arrays.asList(rudyYoungblood)); String serializedMovie = gson.toJson(movieWithNullValue);

Il risultato è il seguente:

{ "imdbId": null, "actors": [ { "IMDB Code": "nm2199632", "Date Of Birth": "21-09-1982", "N° Film: ": 3, "filmography": "Apocalypto-Beatdown-Wind Walkers" } ] }

Notare che:

  • l'output è formattato
  • alcuni nomi di proprietà vengono modificati e contengono HTML
  • vengono inclusi valori null e il campo director viene omesso
  • Date è ora in gg-mm-aaaa formato
  • è presente una nuova proprietà - N ° Film
  • filmografia è una proprietà formattata, non l'elenco JSON predefinito

4. Deserializzazione Gson

4.1. Deserializzazione semplice

La deserializzazione converte l'input JSON in oggetti Java. Per illustrare l'output, implementiamo il metodo toString () in entrambe le classi di entità:

public class Movie { @Override public String toString() { return "Movie [imdbId=" + imdbId + ", director=" + director + ",actors=" + actors + "]"; } ... } public class ActorGson { @Override public String toString() { return "ActorGson [imdbId=" + imdbId + ", dateOfBirth=" + dateOfBirth + ",filmography=" + filmography + "]"; } ... }

Quindi utilizziamo il JSON serializzato e lo eseguiamo tramite la deserializzazione Gson standard:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"actors\":" + "[{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\"," + "\"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}"; Movie outputMovie = new Gson().fromJson(jsonInput, Movie.class); outputMovie.toString();

L'output siamo noi le nostre entità, popolate con i dati dal nostro input JSON:

Movie [imdbId=tt0472043, director=null, actors=[ActorGson [imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982, filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

Come nel caso del serializzatore semplice:

  • i nomi di input JSON devono corrispondere ai nomi delle entità Java o sono impostati su null.
  • Il campo dateOfBirth è stato tradotto con il modello di data Gson predefinito, ignorando il fuso orario.

4.2. Deserializzazione personalizzata

L'utilizzo di un deserializzatore personalizzato ci consente di modificare il comportamento del deserializzatore standard. In questo caso, vogliamo che la data rifletta il fuso orario corretto per dateOfBirth . Usiamo un personalizzato ActorGsonDeserializer sul ActorGson entità per raggiungere questo obiettivo:

public class ActorGsonDeserializer implements JsonDeserializer { private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); @Override public ActorGson deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); JsonElement jsonImdbId = jsonObject.get("imdbId"); JsonElement jsonDateOfBirth = jsonObject.get("dateOfBirth"); JsonArray jsonFilmography = jsonObject.getAsJsonArray("filmography"); ArrayList filmList = new ArrayList(); if (jsonFilmography != null) { for (int i = 0; i < jsonFilmography.size(); i++) { filmList.add(jsonFilmography.get(i).getAsString()); } } ActorGson actorGson = new ActorGson(jsonImdbId.getAsString(), sdf.parse(jsonDateOfBirth.getAsString()), filmList); return actorGson; } }

Abbiamo utilizzato un parser SimpleDateFormat per analizzare la data di input, tenendo conto del fuso orario.

Tieni presente che avremmo potuto decidere di scrivere semplicemente un deserializzatore personalizzato solo per la data, ma ActorGsonDeserializer offre una visualizzazione più dettagliata del processo di deserializzazione.

Si noti inoltre che l'approccio Gson non richiede la modifica dell'entità ActorGson , che è l'ideale in quanto potremmo non avere sempre accesso all'entità di input. Usiamo il deserializzatore personalizzato qui:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"actors\":" + "[{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\", + \"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}"; Gson gson = new GsonBuilder() .registerTypeAdapter(ActorGson.class,new ActorGsonDeserializer()) .create(); Movie outputMovie = gson.fromJson(jsonInput, Movie.class); outputMovie.toString();

L'output è simile al risultato del deserializzatore semplice, tranne per il fatto che la data utilizza il fuso orario corretto:

Movie [imdbId=tt0472043, director=null, actors=[ActorGson [imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982, filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

5. Jackson Maven Dipendenza

 com.fasterxml.jackson.core jackson-databind ${jackson.version} 

Puoi ottenere l'ultima versione di Jackson qui.

6. Serializzazione Jackson

6.1. Serializzazione semplice

Qui useremo Jackson per ottenere lo stesso contenuto serializzato che avevamo con Gson utilizzando le seguenti entità. Nota che i getter / setter dell'entità devono essere pubblici:

public class ActorJackson { private String imdbId; private Date dateOfBirth; private List filmography; // required getters and setters, default constructor // and field constructor details omitted } public class Movie { private String imdbId; private String director; private List actors; // required getters and setters, default constructor // and field constructor details omitted } SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); ActorJackson rudyYoungblood = new ActorJackson("nm2199632",sdf.parse("21-09-1982"), Arrays.asList("Apocalypto","Beatdown","Wind Walkers") ); Movie movie = new Movie("tt0472043","Mel Gibson", Arrays.asList(rudyYoungblood)); ObjectMapper mapper = new ObjectMapper(); String jsonResult = mapper.writeValueAsString(movie);

L'output è il seguente:

{"imdbId":"tt0472043","director":"Mel Gibson","actors": [{"imdbId":"nm2199632","dateOfBirth":401439600000, "filmography":["Apocalypto","Beatdown","Wind Walkers"]}]}

Alcune note di interesse:

  • ObjectMapper è il nostro serializzatore / deserializzatore Jackson
  • Il JSON di output non è formattato
  • Per impostazione predefinita, Java Date viene tradotto in valore lungo

6.2. Custom Serialization

We can create a Jackson serializer for ActorJackson element generation by extending StdSerializer for our entity. Again note that the entity getters/setters must be public:

public class ActorJacksonSerializer extends StdSerializer { private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); public ActorJacksonSerializer(Class t) { super(t); } @Override public void serialize(ActorJackson actor, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeStartObject(); jsonGenerator.writeStringField("imdbId", actor.getImdbId()); jsonGenerator.writeObjectField("dateOfBirth", actor.getDateOfBirth() != null ? sdf.format(actor.getDateOfBirth()) : null); jsonGenerator.writeNumberField("N° Film: ", actor.getFilmography() != null ? actor.getFilmography().size() : null); jsonGenerator.writeStringField("filmography", actor.getFilmography() .stream().collect(Collectors.joining("-"))); jsonGenerator.writeEndObject(); } }

We create a Movie entity to allow ignoring of the director field:

public class MovieWithNullValue { private String imdbId; @JsonIgnore private String director; private List actors; // required getters and setters, default constructor // and field constructor details omitted }

Now we can proceed with a custom ObjectMapper creation and setup:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy"); ActorJackson rudyYoungblood = new ActorJackson( "nm2199632", sdf.parse("21-09-1982"), Arrays.asList("Apocalypto", "Beatdown","Wind Walkers")); MovieWithNullValue movieWithNullValue = new MovieWithNullValue(null,"Mel Gibson", Arrays.asList(rudyYoungblood)); SimpleModule module = new SimpleModule(); module.addSerializer(new ActorJacksonSerializer(ActorJackson.class)); ObjectMapper mapper = new ObjectMapper(); String jsonResult = mapper.registerModule(module) .writer(new DefaultPrettyPrinter()) .writeValueAsString(movieWithNullValue);

The output is formatted JSON that handles null values, formats the date, excludes the director field and shows new output of :

{ "actors" : [ { "imdbId" : "nm2199632", "dateOfBirth" : "21-09-1982", "N° Film: " : 3, "filmography" : "Apocalypto-Beatdown-Wind Walkers" } ], "imdbID" : null }

7. Jackson Deserialization

7.1. Simple Deserialization

To illustrate the output, we implement the toString() method in both Jackson entity classes:

public class Movie { @Override public String toString() { return "Movie [imdbId=" + imdbId + ", director=" + director + ", actors=" + actors + "]"; } ... } public class ActorJackson { @Override public String toString() { return "ActorJackson [imdbId=" + imdbId + ", dateOfBirth=" + dateOfBirth + ", filmography=" + filmography + "]"; } ... }

Then we utilize the serialized JSON and run it through Jackson deserialization:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"actors\": [{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\", \"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}"; ObjectMapper mapper = new ObjectMapper(); Movie movie = mapper.readValue(jsonInput, Movie.class);

The output is us our entities, populated with the data from our JSON input:

Movie [imdbId=tt0472043, director=null, actors=[ActorJackson [imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982, filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

As was the case with the simple serializer:

  • the JSON input names must correspond with the Java entity names, or they are set to null,
  • dateOfBirth field was translated with the default Jackson date pattern, ignoring the time zone.

7.2. Custom Deserialization

Using a custom deserializer allows us to modify the standard deserializer behavior.

In this case, we want the date to reflect the correct time zone for dateOfBirth, so we add a DateFormatter to our Jackson ObjectMapper:

String jsonInput = "{\"imdbId\":\"tt0472043\",\"director\":\"Mel Gibson\", \"actors\":[{\"imdbId\":\"nm2199632\",\"dateOfBirth\":\"1982-09-21T12:00:00+01:00\", \"filmography\":[\"Apocalypto\",\"Beatdown\",\"Wind Walkers\"]}]}"; ObjectMapper mapper = new ObjectMapper(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); mapper.setDateFormat(df); Movie movie = mapper.readValue(jsonInput, Movie.class); movie.toString();

The output reflects the correct time zone with the date:

Movie [imdbId=tt0472043, director=Mel Gibson, actors=[ActorJackson [imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982, filmography=[Apocalypto, Beatdown, Wind Walkers]]]]

This solution is clean and simple.

Alternatively, we could have created a custom deserializer for the ActorJackson class, registered this module with our ObjectMapper, and deserialized the date using the @JsonDeserialize annotation on the ActorJackson entity.

The disadvantage of that approach is the need to modify the entity, which may not be ideal for cases when we don't have access to the input entity classes.

8. Conclusion

Both Gson and Jackson are good options for serializing/deserializing JSON data, simple to use and well documented.

Advantages of Gson:

  • Simplicity of toJson/fromJson in the simple cases
  • For deserialization, do not need access to the Java entities

Advantages of Jackson:

  • Integrato in tutti i framework JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) e Spring
  • Ampio supporto per le annotazioni

Puoi trovare il codice per Gson e Jackson su GitHub.