Jackson vs Gson

Jackson vs Gson

1. Introdução

Neste artigo, compararemos as APIsGson eJackson para serializar e desserializar dados JSON para objetos Java e vice-versa.

Gson e Jackson são bibliotecas completas que oferecem suporte a ligação de dados JSON para Java. Cada um deles é um projeto de código aberto desenvolvido ativamente, que oferece manipulação de tipos de dados complexos e suporte a Java Generics.

E na maioria dos casos, ambas as bibliotecas podem desserializar para uma entidade sem modificar uma classe de entidade, o que é importante nos casos em que um desenvolvedor não tem acesso ao código-fonte da entidade.

2. Dependência Gson Maven


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

Você pode obter a versão mais recente do Gsonhere.

3. Serialização Gson

A serialização converte objetos Java em saída JSON. Considere as seguintes entidades:

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. Serialização Simples

Vamos começar com um exemplo de serialização de Java para 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);

Isso resultará em:

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

Por padrão:

  • Todas as propriedades são serializadas porque não têm valoresnull

  • O campodateOfBirth foi traduzido com o padrão de data Gson padrão

  • A saída não está formatada e os nomes de propriedades JSON correspondem às entidades Java

3.2. Serialização Personalizada

O uso de um serializador personalizado nos permite modificar o comportamento padrão. Podemos introduzir um formatador de saída com HTML, manipular valores denull, excluir propriedades da saída ou adicionar uma nova saída.

ActorGsonSerializer modifica a geração de código JSON para o elementoActorGson:

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

Para excluir a propriedadedirector, a anotação@Expose é usada para propriedades que desejamos considerar:

public class MovieWithNullValue {

    @Expose
    private String imdbId;
    private String director;

    @Expose
    private List actors;
}

Agora podemos prosseguir com a criação do objeto Gson usando a classeGsonBuilder:

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

O resultado é o seguinte:

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

Notar que:

  • a saída está formatada

  • alguns nomes de propriedades são alterados e contêm HTML

  • Os valores denull são incluídos e o campodirector é omitido

  • Date agora está no formatodd-MM-yyyy

  • uma nova propriedade está presente -N° Film

  • filmografia é uma propriedade formatada, não a lista JSON padrão

4. Desserialização Gson

4.1. Deserialização Simples

A desserialização converte a entrada JSON em objetos Java. Para ilustrar a saída, implementamos o métodotoString() em ambas as classes de entidade:

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 + "]";
    }
    ...
}

Em seguida, utilizamos o JSON serializado e o executamos através da desserialização padrão do Gson:

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

A saída somos nós nossas entidades, preenchidas com os dados de nossa entrada JSON:

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

Como foi o caso do serializador simples:

  • os nomes de entrada JSON devem corresponder aos nomes das entidades Java ou estão configurados como nulos.

  • O campodateOfBirth foi traduzido com o padrão de data Gson padrão, ignorando o fuso horário.

4.2. Desserialização personalizada

O uso de um desserializador personalizado nos permite modificar o comportamento padrão do desserializador. Nesse caso, queremos que a data reflita o fuso horário correto paradateOfBirth. Usamos umActorGsonDeserializer personalizado na entidadeActorGson para conseguir isso:

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

Empregamos um analisadorSimpleDateFormat para analisar a data de entrada, levando em consideração o fuso horário.

Observe que poderíamos ter decidido simplesmente escrever um desserializador personalizado apenas para a Data, mas oActorGsonDeserializer oferece uma visão mais detalhada do processo de desserialização.

Observe também que a abordagem Gson não requer a modificação da entidadeActorGson, o que é ideal, pois nem sempre podemos ter acesso à entidade de entrada. Usamos o desserializador personalizado aqui:

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

A saída é semelhante ao resultado do desserializador simples, exceto que a data usa o fuso horário correto:

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

5. Dependência de Jackson Maven


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

Você pode obter a versão mais recente de Jacksonhere.

6. Jackson Serialization

6.1. Serialização Simples

Aqui usaremos Jackson para obter o mesmo conteúdo serializado que tínhamos com o Gson usando as seguintes entidades. Observe que os getters / setters da entidade devem ser públicos:

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

A saída é a seguinte:

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

Algumas notas de interesse:

  • ObjectMapper é nosso serializador / desserializador Jackson

  • A saída JSON não está formatada

  • Por padrão, a data Java é traduzida para o valorlong

6.2. Serialização Personalizada

Podemos criar um serializador Jackson para a geração de elementosActorJackson estendendo StdSerializer para nossa entidade. Observe novamente que os getters / setters de entidades devem ser públicos:

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

Criamos uma entidade de filme para permitir ignorar o campodirector:

public class MovieWithNullValue {

    private String imdbId;

    @JsonIgnore
    private String director;

    private List actors;

    // required getters and setters, default constructor
    // and field constructor details omitted
}

Agora podemos prosseguir com a criação e configuração deObjectMapper personalizados:

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

A saída é formatada em JSON que lida com valoresnull, formata a data, exclui o campodirector e mostra a nova saída de:

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

7. Desserialização de Jackson

7.1. Deserialização Simples

Para ilustrar a saída, implementamos o métodotoString() em ambas as classes de entidade Jackson:

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 + "]";
    }
    ...
}

Em seguida, utilizamos o JSON serializado e o executamos através da desserialização de Jackson:

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

A saída somos nós nossas entidades, preenchidas com os dados de nossa entrada JSON:

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

Como foi o caso do serializador simples:

  • os nomes de entrada JSON devem corresponder aos nomes das entidades Java ou são definidos comonull,

  • O campodateOfBirth foi traduzido com o padrão de data Jackson padrão, ignorando o fuso horário.

7.2. Desserialização personalizada

O uso de um desserializador personalizado nos permite modificar o comportamento padrão do desserializador.

Neste caso, queremos que a data reflita o fuso horário correto paradateOfBirth,, então adicionamos um DateFormatter ao nosso JacksonObjectMapper:

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

A saída reflete o fuso horário correto com a data:

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

Esta solução é limpa e simples.

Alternativamente, poderíamos ter criado um desserializador personalizado para a classeActorJackson, registrado este módulo com nossoObjectMapper e desserializado a data usando a anotação@JsonDeserialize na entidadeActorJackson.

A desvantagem dessa abordagem é a necessidade de modificar a entidade, o que pode não ser ideal para os casos em que não temos acesso às classes de entidade de entrada.

8. Conclusão

Gson e Jackson são boas opções para serializar / desserializar dados JSON, simples de usar e bem documentados.

Vantagens do Gson:

  • Simplicidade detoJson /fromJson nos casos simples

  • Para desserialização, não precisa acessar as entidades Java

Vantagens de Jackson:

  • Construído em todas as estruturas JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) e Spring

  • Suporte extensivo à anotação

Você pode encontrar o código paraGson eJackson no GitHub.