Перейти к основному содержимому

Джексон против Гсона

· 9 мин. чтения

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

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, который обрабатывает нулевые значения, форматирует дату, исключает поле директора и показывает новый вывод :

{
"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.
  • Расширенная поддержка аннотаций

Вы можете найти код для Gson и Jackson на GitHub.