Введение в ObjectMapper Джексона

1. Обзор

В этом руководстве основное внимание уделяется пониманию класса Jackson ObjectMapper и тому, как сериализовать объекты Java в JSON и десериализовать строку JSON в объекты Java.

Чтобы больше узнать о библиотеке Джексона в целом, неплохо начать с Учебника Джексона.

2. Зависимости

Давайте сначала добавим в pom.xml следующие зависимости :

 com.fasterxml.jackson.core jackson-databind 2.11.1  

Эта зависимость также транзитивно добавит в путь к классам следующие библиотеки:

  1. аннотации Джексона
  2. Джексон-Кор

Всегда используйте последние версии из центрального репозитория Maven для jackson-databind .

3. Чтение и запись с помощью ObjectMapper

Начнем с основных операций чтения и записи.

Простой readValue API из ObjectMapper является точкой входа хорошо. Мы можем использовать его для анализа или десериализации содержимого JSON в объект Java.

Кроме того, на стороне записи мы можем использовать API writeValue для сериализации любого объекта Java в виде вывода JSON.

В этой статье мы будем использовать следующий класс Car с двумя полями в качестве объекта для сериализации или десериализации:

public class Car { private String color; private String type; // standard getters setters }

3.1. Объект Java в JSON

Давайте посмотрим , первый пример сериализации объекта Java в формате JSON , используя WriteValue метод ObjectMapper класса:

ObjectMapper objectMapper = new ObjectMapper(); Car car = new Car("yellow", "renault"); objectMapper.writeValue(new File("target/car.json"), car); 

Результатом вышеизложенного в файле будет:

{"color":"yellow","type":"renault"} 

Методы writeValueAsString и writeValueAsBytes из ObjectMapper класса генерировать JSON из объекта Java и возвращает сгенерированный JSON в виде строки или в виде массива байтов:

String carAsString = objectMapper.writeValueAsString(car); 

3.2. JSON в объект Java

Ниже приведен простой пример преобразования строки JSON в объект Java с использованием класса ObjectMapper :

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; Car car = objectMapper.readValue(json, Car.class); 

Функция readValue () также принимает другие формы ввода, такие как файл, содержащий строку JSON:

Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);

или URL:

Car car = objectMapper.readValue(new URL("file:src/test/resources/json_car.json"), Car.class);

3.3. JSON в Джексон JsonNode

В качестве альтернативы, JSON может быть преобразован в объект JsonNode и использован для извлечения данных из определенного узла:

String json = "{ \"color\" : \"Black\", \"type\" : \"FIAT\" }"; JsonNode jsonNode = objectMapper.readTree(json); String color = jsonNode.get("color").asText(); // Output: color -> Black 

3.4. Создание списка Java из строки массива JSON

Мы можем проанализировать JSON в форме массива в список объектов Java, используя TypeReference :

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; List listCar = objectMapper.readValue(jsonCarArray, new TypeReference
    
     (){}); 
    

3.5. Создание карты Java из строки JSON

Точно так же мы можем проанализировать JSON в Java Map :

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; Map map = objectMapper.readValue(json, new TypeReference(){}); 

4. Расширенные функции

Одна из самых сильных сторон библиотеки Джексона - это настраиваемый процесс сериализации и десериализации.

В этом разделе мы рассмотрим некоторые расширенные функции, в которых входной или выходной JSON-ответ может отличаться от объекта, который генерирует или потребляет ответ.

4.1. Настройка функции сериализации или десериализации

При преобразовании объектов JSON в классы Java, если строка JSON имеет несколько новых полей, процесс по умолчанию приведет к исключению:

String jsonString = "{ \"color\" : \"Black\", \"type\" : \"Fiat\", \"year\" : \"1970\" }"; 

The JSON string in the above example in the default parsing process to the Java object for the Class Car will result in the UnrecognizedPropertyException exception.

Through the configure method, we can extend the default process to ignore the new fields:

objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); Car car = objectMapper.readValue(jsonString, Car.class); JsonNode jsonNodeRoot = objectMapper.readTree(jsonString); JsonNode jsonNodeYear = jsonNodeRoot.get("year"); String year = jsonNodeYear.asText(); 

Yet another option is based on the FAIL_ON_NULL_FOR_PRIMITIVES, which defines if the null values for primitive values are allowed:

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false); 

Similarly, FAIL_ON_NUMBERS_FOR_ENUM controls if enum values are allowed to be serialized/deserialized as numbers:

objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);

You can find the comprehensive list of serialization and deserialization features on the official site.

4.2. Creating Custom Serializer or Deserializer

Another essential feature of the ObjectMapper class is the ability to register a custom serializer and deserializer.

Custom serializers and deserializers are very useful in situations where the input or the output JSON response is different in structure than the Java class into which it must be serialized or deserialized.

Below is an example of a custom JSON serializer:

public class CustomCarSerializer extends StdSerializer { public CustomCarSerializer() { this(null); } public CustomCarSerializer(Class t) { super(t); } @Override public void serialize( Car car, JsonGenerator jsonGenerator, SerializerProvider serializer) { jsonGenerator.writeStartObject(); jsonGenerator.writeStringField("car_brand", car.getType()); jsonGenerator.writeEndObject(); } } 

This custom serializer can be invoked like this:

ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule("CustomCarSerializer", new Version(1, 0, 0, null, null, null)); module.addSerializer(Car.class, new CustomCarSerializer()); mapper.registerModule(module); Car car = new Car("yellow", "renault"); String carJson = mapper.writeValueAsString(car); 

Here's what the Car looks like (as JSON output) on the client side:

var carJson = {"car_brand":"renault"} 

And here's an example of a custom JSON deserializer:

public class CustomCarDeserializer extends StdDeserializer { public CustomCarDeserializer() { this(null); } public CustomCarDeserializer(Class vc) { super(vc); } @Override public Car deserialize(JsonParser parser, DeserializationContext deserializer) { Car car = new Car(); ObjectCodec codec = parser.getCodec(); JsonNode node = codec.readTree(parser); // try catch block JsonNode colorNode = node.get("color"); String color = colorNode.asText(); car.setColor(color); return car; } } 

This custom deserializer can be invoked in this way:

String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }"; ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule("CustomCarDeserializer", new Version(1, 0, 0, null, null, null)); module.addDeserializer(Car.class, new CustomCarDeserializer()); mapper.registerModule(module); Car car = mapper.readValue(json, Car.class); 

4.3. Handling Date Formats

The default serialization of java.util.Date produces a number, i.e., epoch timestamp (number of milliseconds since January 1, 1970, UTC). But this is not very human readable and requires further conversion to be displayed in a human-readable format.

Let's wrap the Car instance we used so far inside the Request class with the datePurchased property:

public class Request { private Car car; private Date datePurchased; // standard getters setters } 

To control the String format of a date and set it to, e.g., yyyy-MM-dd HH:mm a z, consider the following snippet:

ObjectMapper objectMapper = new ObjectMapper(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm a z"); objectMapper.setDateFormat(df); String carAsString = objectMapper.writeValueAsString(request); // output: {"car":{"color":"yellow","type":"renault"},"datePurchased":"2016-07-03 11:43 AM CEST"} 

To learn more about serializing dates with Jackson, read our more in-depth write-up.

4.4. Handling Collections

Еще одна небольшая, но полезная функция, доступная через класс DeserializationFeature, - это возможность генерировать тип коллекции, который мы хотим, из ответа JSON Array.

Например, мы можем сгенерировать результат в виде массива:

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.USE_JAVA_ARRAY_FOR_JSON_ARRAY, true); Car[] cars = objectMapper.readValue(jsonCarArray, Car[].class); // print cars

Или в виде списка :

String jsonCarArray = "[{ \"color\" : \"Black\", \"type\" : \"BMW\" }, { \"color\" : \"Red\", \"type\" : \"FIAT\" }]"; ObjectMapper objectMapper = new ObjectMapper(); List listCar = objectMapper.readValue(jsonCarArray, new TypeReference
    
     (){}); // print cars
    

Более подробная информация об обработке коллекций с помощью Jackson доступна здесь.

5. Заключение

Jackson - надежная и зрелая библиотека сериализации / десериализации JSON для Java. ObjectMapper API обеспечивает простой способ для синтаксического анализа и генерации объектов ответа JSON с большим количеством гибкости. В этой статье обсуждались основные функции, которые сделали библиотеку такой популярной.

Исходный код, прилагаемый к статье, можно найти на GitHub.