1. Обзор
В этой статье показано, как обрабатывать JSON, используя только ядро Java EE, без использования сторонних зависимостей, таких как Jersey или Jackson. Почти все, что мы будем использовать, предоставляется пакетом javax.json .
2. Запись объекта в строку JSON
Преобразование объекта Java в строку
JSON очень просто. Предположим, у нас есть простой класс Person :
public class Person {
private String firstName;
private String lastName;
private Date birthdate;
// getters and setters
}
Чтобы преобразовать экземпляр этого класса в строку
JSON , сначала нам нужно создать экземпляр JsonObjectBuilder
и добавить пары свойство/значение с помощью метода add()
:
JsonObjectBuilder objectBuilder = Json.createObjectBuilder()
.add("firstName", person.getFirstName())
.add("lastName", person.getLastName())
.add("birthdate", new SimpleDateFormat("DD/MM/YYYY")
.format(person.getBirthdate()));
Обратите внимание, что метод add()
имеет несколько перегруженных версий. Он может принимать большинство примитивных типов (а также упакованные объекты) в качестве второго параметра.
Как только мы закончим настройку свойств, нам просто нужно записать объект в String
:
JsonObject jsonObject = objectBuilder.build();
String jsonString;
try(Writer writer = new StringWriter()) {
Json.createWriter(writer).write(jsonObject);
jsonString = writer.toString();
}
Вот и все! Сгенерированная строка
будет выглядеть так:
{"firstName":"Michael","lastName":"Scott","birthdate":"06/15/1978"}
2.1. Использование JsonArrayBuilder
для построения массивов
Теперь, чтобы немного усложнить наш пример, давайте предположим, что класс Person
был изменен, чтобы добавить новое свойство, называемое электронной почтой
, которое будет содержать список адресов электронной почты:
public class Person {
private String firstName;
private String lastName;
private Date birthdate;
private List<String> emails;
// getters and setters
}
Чтобы добавить все значения из этого списка в JsonObjectBuilder
, нам понадобится помощь JsonArrayBuilder
:
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for(String email : person.getEmails()) {
arrayBuilder.add(email);
}
objectBuilder.add("emails", arrayBuilder);
Обратите внимание, что мы используем еще одну перегруженную версию метода add()
, которая принимает объект JsonArrayBuilder
в качестве второго параметра.
Итак, давайте посмотрим на сгенерированную строку для объекта Person
с двумя адресами электронной почты:
{"firstName":"Michael","lastName":"Scott","birthdate":"06/15/1978",
"emails":["michael.scott@dd.com","michael.scarn@gmail.com"]}
2.2. Форматирование вывода с помощью PRETTY_PRINTING
Итак, мы успешно преобразовали объект Java в допустимую строку
JSON . Теперь, прежде чем перейти к следующему разделу, давайте добавим простое форматирование, чтобы сделать вывод более «JSON-подобным» и более удобным для чтения.
В предыдущих примерах мы создали JsonWriter
, используя простой Json
. `` статический метод createWriter() .
Чтобы получить больший контроль над сгенерированным String
, мы воспользуемся возможностью Java 7 JsonWriterFactory
для создания модуля записи с определенной конфигурацией.
Map<String, Boolean> config = new HashMap<>();
config.put(JsonGenerator.PRETTY_PRINTING, true);
JsonWriterFactory writerFactory = Json.createWriterFactory(config);
String jsonString;
try(Writer writer = new StringWriter()) {
writerFactory.createWriter(writer).write(jsonObject);
jsonString = writer.toString();
}
Код может выглядеть немного многословным, но на самом деле он мало что делает.
Во-первых, он создает экземпляр JsonWriterFactory
, передавая карту конфигурации своему конструктору. Карта содержит только одну запись, которая устанавливает значение true для свойства PRETTY_PRINTING. Затем мы используем этот экземпляр фабрики для создания модуля записи вместо использования Json.createWriter()
.
Новый вывод будет содержать отличительные разрывы строк и табуляцию, которые характеризуют строку
JSON :
{
"firstName":"Michael",
"lastName":"Scott",
"birthdate":"06/15/1978",
"emails":[
"michael.scott@dd.com",
"michael.scarn@gmail.com"
]
}
3. Создание объекта
Java из строки
Теперь давайте проделаем обратную операцию: преобразуем строку
JSON в объект Java.
Основная часть процесса преобразования вращается вокруг JsonObject
. Чтобы создать экземпляр этого класса, используйте статический метод Json.createReader()
, за которым следует readObject()
:
JsonReader reader = Json.createReader(new StringReader(jsonString));
JsonObject jsonObject = reader.readObject();
Метод createReader()
принимает в качестве параметра InputStream .
В этом примере мы используем StringReader,
поскольку наш JSON содержится в объекте String
, но этот же метод можно использовать для чтения содержимого из файла, например, с помощью FileInputStream
.
Имея под рукой экземпляр JsonObject
, мы можем прочитать свойства с помощью метода getString()
и присвоить полученные значения вновь созданному экземпляру нашего класса Person
:
Person person = new Person();
person.setFirstName(jsonObject.getString("firstName"));
person.setLastName(jsonObject.getString("lastName"));
person.setBirthdate(dateFormat.parse(jsonObject.getString("birthdate")));
3.1. Использование JsonArray
для получения значений списка
Нам нужно будет использовать специальный класс JsonArray
для извлечения значений списка из JsonObject
:
JsonArray emailsJson = jsonObject.getJsonArray("emails");
List<String> emails = new ArrayList<>();
for (JsonString j : emailsJson.getValuesAs(JsonString.class)) {
emails.add(j.getString());
}
person.setEmails(emails);
Вот и все! Мы создали полный экземпляр Person из
строки
Json .
4. Запрос значений
Теперь предположим, что нас интересует очень конкретный фрагмент данных, который находится внутри строки
JSON .
Рассмотрим приведенный ниже JSON, представляющий клиента из зоомагазина. Допустим, вам по какой-то причине нужно получить имя третьего питомца из списка питомцев:
{
"ownerName": "Robert",
"pets": [{
"name": "Kitty",
"type": "cat"
}, {
"name": "Rex",
"type": "dog"
}, {
"name": "Jake",
"type": "dog"
}]
}
Преобразование всего текста в объект Java только для получения одного значения было бы не очень эффективным. Итак, давайте проверим несколько стратегий для запроса строк
JSON без необходимости проходить через все испытания преобразования.
4.1. Запросы с использованием API объектной модели
Запросить значение свойства с известным местоположением в структуре JSON очень просто. Мы можем использовать экземпляр JsonObject,
того же класса, который использовался в предыдущих примерах:
JsonReader reader = Json.createReader(new StringReader(jsonString));
JsonObject jsonObject = reader.readObject();
String searchResult = jsonObject
.getJsonArray("pets")
.getJsonObject(2)
.getString("name");
Загвоздка здесь в том, чтобы перемещаться по свойствам jsonObject
, используя правильную последовательность методов get*()
.
В этом примере мы сначала получаем ссылку на список «питомцев» с помощью getJsonArray()
, который возвращает список с 3 записями. Затем мы используем метод getJsonObject()
, который принимает индекс в качестве параметра и возвращает другой JsonObject
, представляющий третий элемент в списке. Наконец, мы используем getString()
, чтобы получить искомое строковое значение.
4.2. Запросы с помощью Streaming API
Другой способ выполнения точных запросов к строке JSON —
использование Streaming API, основным классом которого является JsonParser .
``
JsonParser
обеспечивает чрезвычайно быстрый прямой доступ только для чтения к JS, но его недостаток состоит в том, что он несколько сложнее, чем объектная модель:
JsonParser jsonParser = Json.createParser(new StringReader(jsonString));
int count = 0;
String result = null;
while(jsonParser.hasNext()) {
Event e = jsonParser.next();
if (e == Event.KEY_NAME) {
if(jsonParser.getString().equals("name")) {
jsonParser.next();
if(++count == 3) {
result = jsonParser.getString();
break;
}
}
}
}
Этот пример дает тот же результат, что и предыдущий. Он возвращает имя
третьего питомца в списке питомцев .
Как только JsonParser
создан с использованием Json.createParser()
, нам нужно использовать итератор (отсюда характер «прямого доступа» JsonParser
) для навигации по токенам JSON, пока мы не доберемся до свойства (или свойств), которые мы ищем. .
Каждый раз, когда мы проходим через итератор, мы переходим к следующему токену данных JSON. Поэтому мы должны быть осторожны, чтобы проверить, имеет ли текущий токен ожидаемый тип. Это делается путем проверки события
, возвращаемого вызовом next() .
Существует множество различных типов токенов. В этом примере нас интересуют типы KEY_NAME
, которые представляют имя свойства (например, «ownerName», «pets», «name», «type»). После того, как мы прошли через токен KEY_NAME
со значением «имя» в третий раз, мы знаем, что следующий токен будет содержать строковое значение, представляющее имя третьего питомца из списка.
Это определенно сложнее, чем использование API объектной модели, особенно для более сложных структур JSON. Выбор между тем или иным, как всегда, зависит от конкретного сценария, с которым вы будете иметь дело.
5. Вывод
Мы рассмотрели много вопросов по Java EE JSON Processing API на нескольких простых примерах. Чтобы узнать другие интересные вещи об обработке JSON, ознакомьтесь с нашей серией статей о Джексоне .
Проверьте исходный код классов, используемых в этой статье, а также некоторые модульные тесты в нашем репозитории GitHub .