«1. Введение

Приложения Java часто используют JSON в качестве общего формата для отправки и получения данных. Более того, он используется как протокол сериализации для хранения данных. Благодаря меньшим размерам данных JSON наши приложения становятся дешевле и быстрее.

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

2. Модель домена и тестовые данные

Давайте создадим модель домена для клиента с некоторыми контактными данными:

public class Customer {
    private long id;
    private String firstName;
    private String lastName;
    private String street;
    private String postalCode;
    private String city;
    private String state;
    private String phoneNumber;
    private String email;

Обратите внимание, что все поля будут обязательными, кроме номера телефона и электронной почты.

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

Давайте настроим mockaroo для нашей модели домена:

Вот некоторые моменты, о которых следует помнить:

    Здесь мы указали имена полей Здесь мы выбрали типы данных наших полей 50% телефонных номеров пусто в фиктивных данных 30% адресов электронной почты тоже пусты

Во всех приведенных ниже примерах кода используются одни и те же данные 1000 клиентов из mockaroo. Мы используем фабричный метод Customer.fromMockFile(), чтобы прочитать этот файл и превратить его в объекты Customer.

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

3. Размер данных JSON с параметрами Джексона по умолчанию

Давайте запишем объект Java в JSON с параметрами Джексона по умолчанию:

Customer[] customers = Customer.fromMockFile();
ObjectMapper mapper = new ObjectMapper();
byte[] feedback = mapper.writeValueAsBytes(customers); 

Давайте посмотрим фиктивные данные для первого клиента:

{
  "id" : 1, 
  "firstName" : "Horatius", 
  "lastName" : "Strognell", 
  "street" : "4848 New Castle Point", 
  "postalCode" : "33432", 
  "city" : "Boca Raton", 
  "state" : "FL", 
  "phoneNumber" : "561-824-9105", 
  "email" : "[email protected]"
}

При использовании параметров Jackon по умолчанию массив байтов данных JSON со всеми 1000 клиентов имеет размер 181,0 КБ.

4. Сжатие с помощью gzip

Как и текстовые данные, данные JSON хорошо сжимаются. Вот почему gzip — наш первый способ уменьшить размер данных JSON. Более того, его можно автоматически применять в HTTP, общем протоколе для отправки и получения JSON.

Давайте возьмем JSON, созданный с параметрами Джексона по умолчанию, и сожмем его с помощью gzip. В результате получается 45,9 КБ, всего 25,3% от исходного размера. Поэтому, если мы сможем включить сжатие gzip через конфигурацию, мы сократим размер данных JSON на 75% без каких-либо изменений в нашем коде Java!

Если наше приложение Spring Boot передает данные JSON другим службам или внешним интерфейсам, мы включим сжатие gzip в конфигурации Spring Boot. Давайте посмотрим на типичную конфигурацию сжатия в синтаксисе YAML:


server:
  compression:
    enabled: true
    mime-types: text/html,text/plain,text/css,application/javascript,application/json
    min-response-size: 1024

Во-первых, мы включили сжатие в целом, задав значение true. Затем мы специально включили сжатие данных JSON, добавив application/json в список mime-типов. Наконец, обратите внимание, что мы установили для параметра min-response-size значение 1024 байта. Это связано с тем, что если мы сжимаем небольшие объемы данных, мы можем получить данные большего размера, чем исходные.

Часто прокси-серверы, такие как NGINX, или веб-серверы, такие как HTTP-сервер Apache, доставляют данные JSON другим службам или внешним интерфейсам. Настройка сжатия данных JSON в этих инструментах выходит за рамки данного руководства.

В предыдущем руководстве по gzip говорилось, что gzip имеет различные уровни сжатия. В наших примерах кода используется gzip с уровнем сжатия Java по умолчанию. Spring Boot, прокси или веб-серверы могут получить разные результаты сжатия для одних и тех же данных JSON.

Если мы используем JSON в качестве протокола сериализации для хранения данных, нам нужно будет сжимать и распаковывать данные самостоятельно.

5. Более короткие имена полей в JSON

Рекомендуется использовать имена полей, которые не являются ни слишком короткими, ни слишком длинными. Давайте опустим это для демонстрации: мы будем использовать односимвольные имена полей в JSON, но мы не будем менять имена полей Java. Это уменьшает размер данных JSON, но снижает читаемость JSON. Поскольку для этого также потребуются обновления для всех сервисов и внешних интерфейсов, мы, вероятно, будем использовать эти короткие имена полей только при хранении данных:

{
  "i" : 1,
  "f" : "Horatius",
  "l" : "Strognell",
  "s" : "4848 New Castle Point",
  "p" : "33432",
  "c" : "Boca Raton",
  "a" : "FL",
  "o" : "561-824-9105",
  "e" : "[email protected]"
}

Легко изменить имена полей JSON с помощью Jackson, оставив имена полей Java. нетронутый. Мы будем использовать аннотацию @JsonProperty:

@JsonProperty("p")
private String postalCode;

«

«Использование односимвольных имен полей приводит к тому, что объем данных составляет 72,5 % от исходного размера. Более того, использование gzip сожмет его до 23,8%. Это не намного меньше, чем 25,3%, которые мы получили, просто сжав исходные данные с помощью gzip. Нам всегда нужно искать подходящее соотношение затрат и выгод. Потеря читабельности из-за небольшого увеличения размера не рекомендуется для большинства сценариев.

6. Сериализация в массив


[ 1, "Horatius", "Strognell", "4848 New Castle Point", "33432", "Boca Raton", "FL", "561-824-9105", "[email protected]" ]

Давайте посмотрим, как мы можем еще больше уменьшить размер данных JSON, полностью исключив имена полей. Мы можем добиться этого, сохранив массив клиентов в нашем JSON. Обратите внимание, что мы также уменьшим читаемость. И нам также потребуется обновить все сервисы и внешние интерфейсы, использующие наши данные JSON:

Сохранение Customer в виде массива приводит к выходным данным размером 53,1% от исходного размера и 22,0% при сжатии gzip. . Это наш лучший результат на данный момент. Тем не менее, 22% ненамного меньше, чем 25,3%, которые мы получили, просто сжав исходные данные с помощью gzip.

Чтобы сериализовать клиента как массив, нам нужно получить полный контроль над сериализацией JSON. Снова обратитесь к нашему учебнику по Джексону для получения дополнительных примеров.

7. Исключение нулевых значений

Jackson и другие библиотеки обработки JSON могут неправильно обрабатывать нулевые значения JSON при чтении или записи JSON. Например, Джексон записывает нулевое значение JSON по умолчанию, когда встречает нулевое значение Java. Вот почему рекомендуется удалять пустые поля в данных JSON. Это оставляет инициализацию пустых значений для каждой библиотеки обработки JSON и уменьшает размер данных JSON.

В наших фиктивных данных мы установили 50% телефонных номеров и 30% адресов электронной почты как пустые. Отсутствие этих нулевых значений уменьшает размер наших данных JSON до 166,8 КБ или 92,1% от исходного размера данных. Затем сжатие gzip снизит его до 24,9%.

Теперь, если мы объединим игнорирование нулевых значений с более короткими именами полей из предыдущего раздела, то получим более значительную экономию: 68,3% исходного размера и 23,4% с помощью gzip.

Мы можем настроить пропуск полей нулевого значения в Джексоне для каждого класса или глобально для всех классов.

8. Новый класс предметной области

Мы достигли наименьшего размера данных JSON, сериализовав их в массив. Один из способов еще больше уменьшить это — новая модель предметной области с меньшим количеством полей. Но зачем нам это делать?


{
  "id" : 1,
  "name" : "Horatius Strognell",
  "address" : "4848 New Castle Point, Boca Raton FL 33432"
}

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

Обратите внимание, как мы объединили поля имени в имя и поля адреса в адрес. Кроме того, мы не указали адрес электронной почты и номер телефона.

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

public class CustomerSlim {
    private long id;
    private String name;
    private String address;

Давайте создадим новый класс домена CustomerSlim для этого внешнего интерфейса:

Если мы преобразуем наши тестовые данные в этот новый класс домена CustomerSlim, мы уменьшим его до 46,1% от исходного размера. Это будет использовать настройки Джексона по умолчанию. Если мы используем gzip, он снижается до 15,1%. Этот последний результат уже является значительным преимуществом по сравнению с предыдущим лучшим результатом в 22,0%.

Далее, если мы также будем использовать односимвольные имена полей, это уменьшит наш размер до 40,7% от исходного размера, а gzip уменьшит его до 14,7%. Этот результат представляет собой лишь небольшой прирост, превышающий 15,1%, которого мы достигли с настройками Jackson по умолчанию.

Никакие поля в CustomerSlim не являются необязательными, поэтому отсутствие пустых значений не влияет на размер данных JSON.

Наша последняя оптимизация — сериализация массива. Сериализируя CustomerSlim в массив, мы достигаем нашего лучшего результата: 34,2% от исходного размера и 14,2% с gzip. Таким образом, даже без сжатия мы удаляем почти две трети исходных данных. А сжатие сжимает наши данные JSON всего до одной седьмой от исходного размера!

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

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