Перейти к основному содержимому

Уменьшение размера данных JSON

· 8 мин. чтения

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;

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

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

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

./0ff9bee4d5ee88dd2fb5091a37ac04a2.png

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

  • Здесь мы указали имена полей
  • Здесь мы выбрали типы данных наших полей
  • 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);

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

{
"id" : 1,
"firstName" : "Horatius",
"lastName" : "Strognell",
"street" : "4848 New Castle Point",
"postalCode" : "33432",
"city" : "Boca Raton",
"state" : "FL",
"phoneNumber" : "561-824-9105",
"email" : "hstrognell0@dailymail.co.uk"
}

При использовании параметров 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" : "hstrognell0@dailymail.co.uk"
}

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

@JsonProperty("p")
private String postalCode;

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

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

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

[ 1, "Horatius", "Strognell", "4848 New Castle Point", "33432", "Boca Raton", "FL", "561-824-9105", "hstrognell0@dailymail.co.uk" ]

Сохранение 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, сериализовав их в массив. Один из способов еще больше уменьшить это — новая модель предметной области с меньшим количеством полей. Но зачем нам это делать?

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

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

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

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

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

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

Если мы преобразуем наши тестовые данные в этот новый класс домена 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 с помощью модели предметной области, настраиваемой для одного внешнего интерфейса.

Полный код, как всегда, доступен на GitHub .