«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° Фильмография — это отформатированное свойство, а не список 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 для анализа входной даты с учетом часового пояса.

Обратите внимание, что мы могли бы просто написать собственный десериализатор только для даты, но 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. Зависимость Jackson Maven

<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 — это наш сериализатор/десериализатор Jackson. Выходной JSON не форматируется. По умолчанию Java Date преобразуется в длинное значение

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

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

    Простота toJson/fromJson в простых случаях Для десериализации не нужен доступ к объектам Java

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

    Встроен во все JAX-RS (Jersey, Apache CXF, RESTEasy, Restlet) и среда Spring Расширенная поддержка аннотаций

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