1. Введение
В этой статье мы сравним API-интерфейсы Gson и Jackson для сериализации и десериализации данных JSON в объекты Java и наоборот.
Gson и Jackson — это полные библиотеки, предлагающие поддержку привязки данных JSON для Java. Каждый из них является активно разрабатываемым проектом с открытым исходным кодом, который предлагает обработку сложных типов данных и поддержку Java Generics.
И в большинстве случаев обе библиотеки могут десериализоваться в сущность без изменения класса сущности, что важно в тех случаях, когда у разработчика нет доступа к исходному коду сущности.
2. Зависимость от Gson Maven
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
Вы можете получить последнюю версию Gson здесь .
3. Сериализация Gson
Сериализация преобразует объекты Java в выходные данные JSON. Рассмотрим следующие сущности:
public class ActorGson {
private String imdbId;
private Date dateOfBirth;
private List<String> filmography;
// getters and setters, default constructor and field constructor omitted
}
public class Movie {
private String imdbId;
private String director;
private List<ActorGson> actors;
// getters and setters, default constructor and field constructor omitted
}
3.1. Простая сериализация
Начнем с примера сериализации Java в 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);
Это приведет к:
{
"imdbId": "tt0472043",
"director": "Mel Gibson",
"actors": [{
"imdbId": "nm2199632",
"dateOfBirth": "Sep 21, 1982 12:00:00 AM",
"filmography": ["Apocalypto", "Beatdown", "Wind Walkers"]
}]
}
По умолчанию:
- Все свойства сериализуются, поскольку они не имеют
нулевых
значений . Поле dateOfBirth
было переведено с шаблоном даты Gson по умолчанию.- Вывод не форматируется, а имена свойств JSON соответствуют объектам Java.
3.2. Пользовательская сериализация
Использование пользовательского сериализатора позволяет нам изменить стандартное поведение. Мы можем ввести средство форматирования вывода с помощью HTML, обрабатывать нулевые
значения, исключать свойства из вывода или добавлять новый вывод.
ActorGsonSerializer
модифицирует генерацию JSON-кода для элемента ActorGson
:
public class ActorGsonSerializer implements JsonSerializer<ActorGson> {
private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
@Override
public JsonElement serialize(ActorGson actor, Type type,
JsonSerializationContext jsonSerializationContext) {
JsonObject actorJsonObj = new JsonObject();
actorJsonObj.addProperty("<strong>IMDB Code</strong>", actor.getImdbId());
actorJsonObj.addProperty("<strong>Date Of Birth</strong>",
actor.getDateOfBirth() != null ?
sdf.format(actor.getDateOfBirth()) : null);
actorJsonObj.addProperty("<strong>N° Film:</strong> ",
actor.getFilmography() != null ?
actor.getFilmography().size() : null);
actorJsonObj.addProperty("filmography", actor.getFilmography() != null ?
convertFilmography(actor.getFilmography()) : null);
return actorJsonObj;
}
private String convertFilmography(List<String> filmography) {
return filmography.stream()
.collect(Collectors.joining("-"));
}
}
Чтобы исключить свойство директора , аннотация
@Expose
используется для свойств, которые мы хотим рассмотреть:
public class MovieWithNullValue {
@Expose
private String imdbId;
private String director;
@Expose
private List<ActorGson> actors;
}
Теперь мы можем приступить к созданию объекта Gson с помощью класса 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);
Результат следующий:
{
"imdbId": null,
"actors": [
{
"<strong>IMDB Code</strong>": "nm2199632",
"<strong>Date Of Birth</strong>": "21-09-1982",
"<strong>N° Film:</strong> ": 3,
"filmography": "Apocalypto-Beatdown-Wind Walkers"
}
]
}
Заметь:
- вывод отформатирован
- имена некоторых свойств изменены и содержат HTML
нулевые
значения включены, а поледиректора
опущеноДата
теперь в форматедд-мм-гггг
- присутствует новое свойство –
N° Film
- фильмография — это отформатированное свойство, а не список JSON по умолчанию.
4. Десериализация Gson
4.1. Простая десериализация
Десериализация преобразует ввод JSON в объекты Java. Чтобы проиллюстрировать вывод, мы реализуем метод toString()
в обоих классах сущностей:
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 + "]";
}
...
}
Затем мы используем сериализованный JSON и запускаем его через стандартную десериализацию 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();
На выходе мы получаем наши объекты, заполненные данными из нашего ввода JSON:
Movie [imdbId=tt0472043, director=null, actors=[ActorGson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
Как и в случае с простым сериализатором:
- входные имена JSON должны соответствовать именам сущностей Java, иначе им присваивается значение null.
Поле dateOfBirth
было переведено с шаблоном даты Gson по умолчанию, игнорируя часовой пояс.
4.2. Пользовательская десериализация
Использование пользовательского десериализатора позволяет нам изменить поведение стандартного десериализатора. В этом случае мы хотим, чтобы дата отражала правильный часовой пояс для dateOfBirth
. Для этого мы используем пользовательский ActorGsonDeserializer
в объекте ActorGson
:
public class ActorGsonDeserializer implements JsonDeserializer<ActorGson> {
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<String> filmList = new ArrayList<String>();
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;
}
}
Мы использовали синтаксический анализатор SimpleDateFormat
для анализа введенной даты с учетом часового пояса.
Обратите внимание, что мы могли бы просто написать собственный десериализатор только для Date, но ActorGsonDeserializer
предлагает более подробное представление о процессе десериализации.
Также обратите внимание, что подход Gson не требует изменения объекта ActorGson
, что идеально, поскольку у нас не всегда может быть доступ к входному объекту. Здесь мы используем собственный десериализатор:
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();
Вывод аналогичен результату простого десериализатора, за исключением того, что дата использует правильный часовой пояс:
Movie [imdbId=tt0472043, director=null, actors=[ActorGson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
5. Зависимость от Джексона Мейвена
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
Вы можете получить последнюю версию Джексона здесь .
6. Сериализация Джексона
6.1. Простая сериализация
Здесь мы будем использовать Jackson для получения того же сериализованного контента, что и у Gson, используя следующие объекты. Обратите внимание, что геттеры/сеттеры объекта должны быть общедоступными:
public class ActorJackson {
private String imdbId;
private Date dateOfBirth;
private List<String> filmography;
// required getters and setters, default constructor
// and field constructor details omitted
}
public class Movie {
private String imdbId;
private String director;
private List<ActorJackson> 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);
Результат выглядит следующим образом:
{"imdbId":"tt0472043","director":"Mel Gibson","actors":
[{"imdbId":"nm2199632","dateOfBirth":401439600000,
"filmography":["Apocalypto","Beatdown","Wind Walkers"]}]}
Некоторые интересные заметки:
ObjectMapper
— это сериализатор/десериализатор Джексона.- Выходной JSON не отформатирован
- По умолчанию дата Java переводится в
длинное
значение.
6.2. Пользовательская сериализация
Мы можем создать сериализатор Джексона для генерации элементов ActorJackson
, расширив StdSerializer для нашей сущности. Снова обратите внимание, что геттеры/сеттеры сущностей должны быть общедоступными:
public class ActorJacksonSerializer extends StdSerializer<ActorJackson> {
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();
}
}
Мы создаем объект Movie, чтобы разрешить игнорирование поля режиссера
:
public class MovieWithNullValue {
private String imdbId;
@JsonIgnore
private String director;
private List<ActorJackson> actors;
// required getters and setters, default constructor
// and field constructor details omitted
}
Теперь мы можем приступить к созданию и настройке пользовательского ObjectMapper :
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);
Вывод отформатирован в формате JSON, который обрабатывает нулевые
значения, форматирует дату, исключает поле директора
и показывает новый вывод N°
:
{
"actors" : [ {
"imdbId" : "nm2199632",
"dateOfBirth" : "21-09-1982",
"N° Film: " : 3,
"filmography" : "Apocalypto-Beatdown-Wind Walkers"
} ],
"imdbID" : null
}
7. Десериализация Джексона
7.1. Простая десериализация
Чтобы проиллюстрировать вывод, мы реализуем метод toString()
в обоих классах сущностей Джексона:
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 + "]";
}
...
}
Затем мы используем сериализованный JSON и запускаем его через десериализацию Джексона:
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);
На выходе мы получаем наши объекты, заполненные данными из нашего ввода JSON:
Movie [imdbId=tt0472043, director=null, actors=[ActorJackson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 04:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
Как и в случае с простым сериализатором:
- входные имена JSON должны соответствовать именам объектов Java, или они имеют значение
null,
Поле dateOfBirth
было переведено с шаблоном даты Джексона по умолчанию, игнорируя часовой пояс.
7.2. Пользовательская десериализация
Использование пользовательского десериализатора позволяет нам изменить поведение стандартного десериализатора.
В этом случае мы хотим, чтобы дата отражала правильный часовой пояс для dateOfBirth,
поэтому мы добавляем DateFormatter в наш ObjectMapper Jackson
:
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();
Вывод отражает правильный часовой пояс с датой:
Movie [imdbId=tt0472043, director=Mel Gibson, actors=[ActorJackson
[imdbId=nm2199632, dateOfBirth=Tue Sep 21 12:00:00 PDT 1982,
filmography=[Apocalypto, Beatdown, Wind Walkers]]]]
Это решение чистое и простое.
В качестве альтернативы мы могли бы создать пользовательский десериализатор для класса ActorJackson
, зарегистрировать этот модуль в нашем ObjectMapper
и десериализовать дату, используя аннотацию @JsonDeserialize для объекта
ActorJackson
.
Недостатком этого подхода является необходимость изменения сущности, что может быть не идеальным для случаев, когда у нас нет доступа к входным классам сущностей.
8. Заключение
И Gson, и Jackson — хорошие варианты для сериализации/десериализации данных JSON, простые в использовании и хорошо документированные.
Преимущества Гсон:
- Простота
toJson
/fromJson
в простых случаях - Для десериализации не нужен доступ к сущностям Java
Преимущества Джексона:
- Встроен во все JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) и Spring framework.
- Расширенная поддержка аннотаций