Jackson contre Gson

1. Introduction

Dans cet article, nous comparerons les API Gson et Jackson pour la sérialisation et la désérialisation des données JSON en objets Java et vice-versa.

Gson et Jackson sont des bibliothèques complètes offrant une prise en charge de la liaison de données JSON pour Java. Chacun d'entre eux sont des projets open source activement développés qui permettent de gérer des types de données complexes et de prendre en charge les génériques Java.

Et dans la plupart des cas, les deux bibliothèques peuvent se désérialiser en une entité sans modifier une classe d'entité, ce qui est important dans les cas où un développeur n'a pas accès au code source de l'entité.

2. Dépendance de Gson Maven

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

Vous pouvez obtenir la dernière version de Gson ici.

3. Sérialisation Gson

La sérialisation convertit les objets Java en sortie JSON. Considérez les entités suivantes:

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. Sérialisation simple

Commençons par un exemple de sérialisation Java vers 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);

Cela se traduira par:

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

Par défaut:

  • Toutes les propriétés sont sérialisées car elles n'ont aucune valeur nulle
  • Le champ dateOfBirth a été traduit avec le modèle de date Gson par défaut
  • La sortie n'est pas formatée et les noms de propriété JSON correspondent aux entités Java

3.2. Sérialisation personnalisée

L'utilisation d'un sérialiseur personnalisé nous permet de modifier le comportement standard. Nous pouvons introduire un formateur de sortie avec HTML, gérer des valeurs nulles , exclure des propriétés de la sortie ou ajouter une nouvelle sortie.

ActorGsonSerializer modifie la génération de code JSON pour l' élément 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("-")); } }

Afin d'exclure la propriété director , l' annotation @Expose est utilisée pour les propriétés que nous voulons considérer:

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

Nous pouvons maintenant procéder à la création d'objets Gson à l'aide de 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);

Le résultat est le suivant:

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

Remarquerez que:

  • la sortie est formatée
  • certains noms de propriétés sont modifiés et contiennent du HTML
  • les valeurs nulles sont incluses et le champ directeur est omis
  • La date est maintenant au format jj-MM-aaaa
  • une nouvelle propriété est présente - N ° Film
  • la filmographie est une propriété formatée, pas la liste JSON par défaut

4. Désérialisation Gson

4.1. Désérialisation simple

La désérialisation convertit l'entrée JSON en objets Java. Pour illustrer la sortie, nous implémentons la méthode toString () dans les deux classes d'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 + "]"; } ... }

Ensuite, nous utilisons le JSON sérialisé et l'exécutons via la désérialisation 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();

La sortie est nous nos entités, remplie avec les données de notre entrée JSON:

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

Comme c'était le cas avec le sérialiseur simple:

  • les noms d'entrée JSON doivent correspondre aux noms d'entités Java, ou ils sont définis sur null.
  • Le champ dateOfBirth a été traduit avec le modèle de date Gson par défaut, ignorant le fuseau horaire.

4.2. Désérialisation personnalisée

L'utilisation d'un désérialiseur personnalisé nous permet de modifier le comportement du désérialiseur standard. Dans ce cas, nous voulons que la date reflète le fuseau horaire correct pour dateOfBirth . Nous utilisons un ActorGsonDeserializer personnalisé sur l' entité ActorGson pour y parvenir:

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

Nous avons utilisé un analyseur SimpleDateFormat pour analyser la date d'entrée, en tenant compte du fuseau horaire.

Notez que nous aurions pu décider d'écrire simplement un désérialiseur personnalisé pour uniquement la date, mais ActorGsonDeserializer offre une vue plus détaillée du processus de désérialisation.

Notez également que l'approche Gson ne nécessite pas de modifier l' entité ActorGson , ce qui est idéal car nous n'avons pas toujours accès à l'entité d'entrée. Nous utilisons le désérialiseur personnalisé ici:

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

La sortie est similaire au résultat du désérialiseur simple, sauf que la date utilise le fuseau horaire correct:

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

5. Dépendance de Jackson Maven

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

Vous pouvez obtenir la dernière version de Jackson ici.

6. Sérialisation Jackson

6.1. Sérialisation simple

Ici, nous utiliserons Jackson pour obtenir le même contenu sérialisé que nous avions avec Gson en utilisant les entités suivantes. Notez que les getters / setters de l'entité doivent être publics:

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

La sortie est la suivante:

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

Quelques notes d'intérêt:

  • ObjectMapper est notre sérialiseur / désérialiseur Jackson
  • Le JSON de sortie n'est pas formaté
  • Par défaut, Java Date est converti en valeur longue

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:

  • Intégré à tous les frameworks JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) et Spring
  • Prise en charge complète des annotations

Vous pouvez trouver le code pour Gson et Jackson sur GitHub.