«1. Введение

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

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

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

Последнюю версию Jsoniter можно найти в репозитории Maven Central.

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

<dependency>
    <groupId>com.jsoniter<groupId> 
    <artifactId>jsoniter</artifactId>
    <version>0.9.23</version>
</dependency>

Точно так же мы можем добавить зависимость в наш файл build.gradle:

compile group: 'com.jsoniter', name: 'jsoniter', version: '0.9.23'

3. Разбор JSON с помощью Jsoniter

Jsoniter предоставляет 3 API для анализа документов JSON:

    Bind API Any API Iterator API

Давайте рассмотрим каждый из вышеперечисленных API.

3.1. Разбор JSON с помощью Bind API

Bind API использует традиционный способ привязки документа JSON к классам Java.

Давайте рассмотрим документ JSON со сведениями об ученике:

{"id":1,"name":{"firstName":"Joe","surname":"Blogg"}}

Теперь определим классы схемы Student и Name для представления вышеуказанного JSON:

public class Student {
    private int id;
    private Name name;
    
    // standard setters and getters
}
public class Name {
    private String firstName;
    private String surname;
    
    // standard setters and getters
}

@Test
public void whenParsedUsingBindAPI_thenConvertedToJavaObjectCorrectly() {
    String input = "{\"id\":1,\"name\":{\"firstName\":\"Joe\",\"surname\":\"Blogg\"}}";
    
    Student student = JsonIterator.deserialize(input, Student.class);

    assertThat(student.getId()).isEqualTo(1);
    assertThat(student.getName().getFirstName()).isEqualTo("Joe");
    assertThat(student.getName().getSurname()).isEqualTo("Blogg");
}

Десериализация JSON в Java объекта с помощью API привязки очень просто. Мы используем метод десериализации JsonIterator:

{"id":"1","name":{"firstName":"Joe","surname":"Blogg"}}

Класс схемы Student объявляет идентификатор типа данных int. Однако что, если JSON, который мы получаем, содержит значение String для идентификатора вместо числа? Например:

Обратите внимание, что идентификатор в JSON на этот раз является строковым значением «1». Jsoniter предоставляет декодеры Maybe для работы с этим сценарием.

3.2. Декодеры Maybe

public class Student {
    @JsonProperty(decoder = MaybeStringIntDecoder.class)
    private int id;
    private Name name;
    
    // standard setters and getters
}

Декодеры Maybe от Jsoniter пригодятся, когда тип данных элемента JSON нечеткий. Тип данных для поля student.id нечеткий — это может быть либо строка, либо целое число. Чтобы справиться с этим, нам нужно аннотировать поле id в нашем классе схемы с помощью MaybeStringIntDecoder:

@Test
public void givenTypeInJsonFuzzy_whenFieldIsMaybeDecoded_thenFieldParsedCorrectly() {
    String input = "{\"id\":\"1\",\"name\":{\"firstName\":\"Joe\",\"surname\":\"Blogg\"}}";
    
    Student student = JsonIterator.deserialize(input, Student.class);

    assertThat(student.getId()).isEqualTo(1); 
}

Теперь мы можем анализировать JSON, даже если значение id является строкой:

Аналогично, Jsoniter предлагает другие декодеры, такие как MaybeStringLongDecoder и MaybeEmptyArrayDecoder.

{"error":404,"description":"Student record not found"}

Теперь давайте представим, что мы ожидали получить документ JSON с информацией о студенте, но вместо этого получили следующий документ:

Что здесь произошло? Мы ожидали успешного ответа с данными о студентах, но получили ответ об ошибке. Это очень распространенный сценарий, но как бы мы справились с этим?

Один из способов — выполнить нулевую проверку, чтобы увидеть, получили ли мы ответ об ошибке перед извлечением данных Student. Однако нулевые проверки могут привести к сложному для чтения коду, и проблема усугубляется, если у нас есть многоуровневый вложенный JSON.

На помощь приходит парсинг Jsoniter с помощью Any API.

3.3. Разбор JSON с использованием Any API

Когда сама структура JSON является динамической, мы можем использовать Any API Jsoniter, который обеспечивает анализ JSON без схемы. Это работает аналогично синтаксическому анализу JSON в Map\u003cString, Object\u003e.

@Test
public void whenParsedUsingAnyAPI_thenFieldValueCanBeExtractedUsingTheFieldName() {
    String input = "{\"id\":1,\"name\":{\"firstName\":\"Joe\",\"surname\":\"Blogg\"}}";
    
    Any any = JsonIterator.deserialize(input);

    assertThat(any.toInt("id")).isEqualTo(1);
    assertThat(any.toString("name", "firstName")).isEqualTo("Joe");
    assertThat(any.toString("name", "surname")).isEqualTo("Blogg"); 
}

Давайте разберем JSON Student, как и раньше, но на этот раз с помощью Any API:

Давайте разберемся в этом примере. Во-первых, мы используем JsonIterator.deserialize(..) для разбора JSON. Однако в этом случае мы не указываем класс схемы. Результат имеет тип Any.

Далее мы читаем значения полей, используя имена полей. Мы считываем значение поля «id» с помощью метода Any.toInt. Метод toInt преобразует значение «id» в целое число. Точно так же мы считываем значения полей «name.firstName» и «name.surname» как строковые значения, используя метод toString.

Используя Any API, мы также можем проверить, присутствует ли элемент в JSON. Мы можем сделать это, найдя элемент, а затем проверив valueType результата поиска. Тип значения будет INVALID, если элемент отсутствует в JSON.

@Test
public void whenParsedUsingAnyAPI_thenFieldValueTypeIsCorrect() {
    String input = "{\"id\":1,\"name\":{\"firstName\":\"Joe\",\"surname\":\"Blogg\"}}";
    
    Any any = JsonIterator.deserialize(input);

    assertThat(any.get("id").valueType()).isEqualTo(ValueType.NUMBER);
    assertThat(any.get("name").valueType()).isEqualTo(ValueType.OBJECT);
    assertThat(any.get("error").valueType()).isEqualTo(ValueType.INVALID);
}

Например:

Поля «id» и «name» присутствуют в JSON и, следовательно, их тип значения — NUMBER и OBJECT соответственно. Однако во входных данных JSON нет элемента с именем «ошибка», поэтому тип значения имеет значение INVALID.

String input = "{\"error\":404,\"description\":\"Student record not found\"}";
Any response = JsonIterator.deserialize(input);

if (response.get("error").valueType() != ValueType.INVALID) {
    return "Error!! Error code is " + response.toInt("error");
}
return "Success!! Student id is " + response.toInt("id");

«Возвращаясь к сценарию, упомянутому в конце предыдущего раздела, нам нужно определить, является ли полученный нами ввод JSON успешным или ошибочным. Мы можем проверить, получили ли мы ответ об ошибке, проверив значение типа элемента «error»:

При запуске приведенный выше код вернет «Error!! Код ошибки 404€.

Далее мы рассмотрим использование Iterator API для разбора документов JSON.

3.4. Анализ JSON с использованием API-интерфейса Iterator

{"firstName":"Joe","surname":"Blogg"}

Если мы хотим выполнить привязку вручную, мы можем использовать API-интерфейс Jsoniter Iterator. Давайте рассмотрим JSON:

@Test
public void whenParsedUsingIteratorAPI_thenFieldValuesExtractedCorrectly() throws Exception {
    Name name = new Name();    
    String input = "{\"firstName\":\"Joe\",\"surname\":\"Blogg\"}";
    JsonIterator iterator = JsonIterator.parse(input);

    for (String field = iterator.readObject(); field != null; field = iterator.readObject()) {
        switch (field) {
            case "firstName":
                if (iterator.whatIsNext() == ValueType.STRING) {
                    name.setFirstName(iterator.readString());
                }
                continue;
            case "surname":
                if (iterator.whatIsNext() == ValueType.STRING) {
                    name.setSurname(iterator.readString());
                }
                continue;
            default:
                iterator.skip();
        }
    }

    assertThat(name.getFirstName()).isEqualTo("Joe");
    assertThat(name.getSurname()).isEqualTo("Blogg");
}

Мы будем использовать класс схемы Name, который мы использовали ранее для анализа JSON с помощью Iterator API:

  1. We start by invoking the readObject method which returns the next field name (or a null if the end of the document has been reached).
  2. If the field name is not of interest to us, we skip the JSON element by using the skip method. Otherwise, we inspect the data type of the element by using the whatIsNext method. Invoking the whatIsNext method is not mandatory but is useful when the datatype of the field is unknown to us.
  3. Finally, we extract the value of the JSON element using the readString method.

Давайте разберемся в приведенном выше примере. Сначала мы анализируем документ JSON как итератор. Мы используем полученный экземпляр JsonIterator для перебора элементов JSON:

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

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

Сначала мы рассмотрели стандартный способ анализа документа JSON с использованием класса схемы.

Далее мы рассмотрели обработку нечетких типов данных и динамических структур при анализе документов JSON с использованием декодеров Maybe и любого типа данных соответственно.

Наконец, мы рассмотрели API Iterator для ручной привязки JSON к объекту Java.