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 deN°:
{
"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