Вступ до Jackson ObjectMapper

1. Огляд

Цей підручник зосереджений на розумінні класу Jackson ObjectMapper та способі серіалізації об'єктів Java у JSON та десеріалізації рядка JSON у об'єкти Java.

Щоб зрозуміти більше про бібліотеку Джексона загалом, підручник з Джексона - це гарне місце для початку.

2. Залежності

Давайте спочатку додамо наступні залежності до pom.xml :

 com.fasterxml.jackson.core jackson-databind 2.11.1  

Ця залежність також транзитивно додасть наступні бібліотеки до шляху до класу:

  1. Джексон-анотації
  2. jackson-core

Завжди використовуйте найновіші версії центрального сховища 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 :

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

Рядок JSON у наведеному вище прикладі в процесі аналізу за замовчуванням об’єкта Java для автомобіля класів призведе до винятку UnrecognizedPropertyException .

За допомогою методу configure ми можемо розширити процес за замовчуванням, щоб ігнорувати нові поля :

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

Ще один варіант заснований на FAIL_ON_NULL_FOR_PRIMITIVES , який визначає, чи дозволені нульові значення для примітивних значень:

objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false); 

Подібним чином FAIL_ON_NUMBERS_FOR_ENUM контролює, чи дозволено значення перелічень серіалізувати / десеріалізувати як числа:

objectMapper.configure(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS, false);

Вичерпний перелік функцій серіалізації та десериалізації ви можете знайти на офіційному сайті.

4.2. Створення користувацького серіалізатора або десериалізатора

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.

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

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
    

Більше інформації про обробку колекцій з Джексоном можна знайти тут.

5. Висновок

Jackson - це солідна та зріла бібліотека для серіалізації / десеріалізації JSON для Java. ObjectMapper API забезпечує простий спосіб для синтаксичного аналізу та генерації об'єктів відповіді JSON з великою кількістю гнучкості. У цій статті обговорювалися основні особливості, що роблять бібліотеку настільки популярною.

Вихідний код, що супроводжує статтю, можна знайти на GitHub.