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

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

1. Вступление

В этой статье мы сравним APIGson иJackson для сериализации и десериализации данных JSON в объекты Java и наоборот.

Gson и Jackson - полные библиотеки, предлагающие поддержку связывания данных JSON для Java. Каждый активно разрабатывает проекты с открытым исходным кодом, которые предлагают обработку сложных типов данных и поддержку Java Generics.

И в большинстве случаев обе библиотеки могут десериализоваться в сущность без изменения класса сущности, что важно в тех случаях, когда разработчик не имеет доступа к исходному коду сущности.

2. Зависимость Gson от Maven


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

Вы можете получить последнюю версию Gsonhere.

3. Gson Сериализация

Сериализация преобразует объекты Java в вывод JSON. Рассмотрим следующие объекты:

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. Простая сериализация

Начнем с примера сериализации 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"]
    }]
}

По умолчанию:

  • Все свойства сериализованы, потому что у них нет значенийnull

  • ПолеdateOfBirth было переведено с использованием шаблона даты Gson по умолчанию

  • Вывод не отформатирован, а имена свойств JSON соответствуют объектам Java

3.2. Пользовательская сериализация

Использование собственного сериализатора позволяет нам изменять стандартное поведение. Мы можем ввести средство форматирования вывода с HTML, обработать значенияnull, исключить свойства из вывода или добавить новый вывод.

ActorGsonSerializer изменяет генерацию кода JSON для элемента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("-"));
    }
}

Чтобы исключить свойствоdirector, аннотация@Expose используется для свойств, которые мы хотим рассмотреть:

public class MovieWithNullValue {

    @Expose
    private String imdbId;
    private String director;

    @Expose
    private List 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": [
    {
      "IMDB Code": "nm2199632",
      "Date Of Birth": "21-09-1982",
      "N° Film: ": 3,
      "filmography": "Apocalypto-Beatdown-Wind Walkers"
    }
  ]
}

Заметить, что:

  • вывод отформатирован

  • некоторые имена свойств изменены и содержат HTML

  • Значенияnull включены, а полеdirector опущено

  • Date теперь в форматеdd-MM-yyyy

  • присутствует новое свойство -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 {

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

Мы использовали синтаксический анализатор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. Зависимость Джексона Maven


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

Вы можете получить последнюю версию Jacksonhere.

6. Сериализация Джексона

6.1. Простая сериализация

Здесь мы будем использовать Джексона для получения того же сериализованного контента, который мы имели с Gson, используя следующие объекты. Обратите внимание, что геттеры / сеттеры объекта должны быть общедоступными:

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

Вывод следующий:

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

Некоторые примечания интереса:

  • ObjectMapper - наш сериализатор / десериализатор Джексона

  • Выходной JSON не отформатирован

  • По умолчанию Java Date переводится в значениеlong

6.2. Пользовательская сериализация

Мы можем создать сериализатор Джексона для генерации элементаActorJackson, расширив StdSerializer для нашей сущности. Снова обратите внимание, что получатели / установщики сущностей должны быть открытыми:

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

Мы создаем объект Movie, чтобы можно было игнорировать полеdirector:

public class MovieWithNullValue {

    private String imdbId;

    @JsonIgnore
    private String director;

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

{
  "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 к нашему 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();

Вывод отражает правильный часовой пояс с датой:

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, просты в использовании и хорошо документированы.

Преимущества Gson:

  • ПростотаtoJson /fromJson в простых случаях

  • Для десериализации не требуется доступ к объектам Java

Преимущества Джексона:

  • Встроенный во все JAX-RS (Джерси, Apache CXF, RESTEasy, Restlet) и Spring Framework

  • Обширная поддержка аннотаций

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