rzaripov.kz

Оптимизация JSON в Delphi: TJSONObject против Data.DBXJSON

Меня зовут Равиль Зарипов, я разрабатываю приложения для Android, iOS, Windows, macOS и Linux из Алматы. В моих проектах JSON часто связывает PHP-сервисы, мобильные клиенты и настольные программы. Поэтому выбор JSON-инструмента влияет не только на скорость разбора, но и на расход памяти, отзывчивость интерфейса и удобство сопровождения кода.

TJSONObject представляет объектную модель JSON, которая обычно доступна через модуль System.JSON. Это удобный способ получить дерево значений, обратиться к полям по имени, изменить их и снова сериализовать документ. Для небольших ответов API и объектов с понятной структурой такой подход часто оказывается оптимальным.

Data.DBXJSON обозначает юнит или пространство имён, связанное с JSON-инфраструктурой Delphi. В старых проектах JSON-классы размещались именно в этом модуле, а в современных версиях RAD Studio их основной путём became System.JSON; Data.DBXJSON может сохраняться как совместимый или DBX-ориентированный слой. Поэтому сравнивать название класса с названием юнита напрямую не совсем корректно.

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

Что на самом деле скрывается за сравнением

В ранних версиях Delphi модуль Data.DBXJSON содержал сами TJSONObject, TJSONArray, TJSONValue и связанные с ними классы. Сравнение TJSONObject с этим юнитом в таком проекте фактически сравнивает объект с местом его объявления. Алгоритм разбора, создание узлов и сериализация остаются теми же.

В новом коде следует разделять System.JSON как основной API и Data.DBXJSON как ссылку на прежнее или специализированное окружение. Если проект поддерживает разные версии RAD Studio, полезно изолировать юнит в небольшом собственном модуле и не смешивать два пространства имён в каждом файле. Такой приём упрощает перенос кода и исключает неоднозначность при разрешении типов.

Data.DBXJSON нельзя считать отдельным ускорителем только на основании названия. Если конкретная версия RAD Studio предоставляет через него DBX-специфический путь обработки, именно его следует сравнивать с прямым использованием TJSONObject. В противном случае различие сводится к имени юнита, а не к алгоритму.

Скорость разбора и расход памяти

Разбор через TJSONParser или ParseJSONValue создаёт полное дерево объектов. Его размер пропорционален объёму входного JSON, поэтому при работе с большими документами основной риск представляет не поиск одного поля, а выделение памяти для всех узлов, строк и контейнеров.

Для небольшого ответа, который нужно преобразовать в модель приложения, объектная модель работает предсказуемо. Для большого массива, где требуется только последовательная обработка записей, полный разбор может расходовать слишком много памяти. В таких случаях полезны DBX- или FireDAC-маршруты с построчным чтением либо отдельный потоковый разборщик.

Повторный вызов ParseJSONValue для одного и того же ответа почти всегда создаёт лишнюю работу. Документ стоит разобрать один раз, сохранить нужные ссылки на значения и затем работать с ними. Поиск ключей в TJSONObject выполняется перебором пар, поэтому часто используемое поле лучше получить в локальную переменную, а не искать многократно.

Клонирование дерева также требует осторожности. Оно удобно для изоляции изменяемой копии, но создаёт дополнительную структуру того же размера. В цикле обработки больших ответов особенно важно избегать копий, без необходимости создавать промежуточные строки и учитывать время освобождения объектов.

Изменение данных и связка с DBX

TJSONObject особенно удобен, когда документ требуется менять: заменять значения, добавлять пары, удалять элементы массива или формировать ответ для другого сервиса. Эти операции естественны для объектной модели и не требуют дополнительного слоя преобразования.

Если же JSON является внешним источником для TDataSet, полезно рассмотреть связку с DBX и FireDAC. В подходящем драйвере данные могут поступать в поля набора данных без длительного хранения полного JSON-дерева в приложении. Выигрыш возникает именно от маршрута преобразования, а не от самого факта использования Data.DBXJSON.

Сопоставление по рабочим критериям выглядит так:

Критерий TJSONObject из System.JSON Data.DBXJSON
Назначение Класс для чтения и изменения JSON-объекта Юнит, связанный с прежним или DBX-окружением
Модель данных Явное дерево из объектов, массивов и значений Зависит от версии; в старом коде часто содержит те же JSON-классы
Расход памяти Держит в памяти разобранный документ целиком При совместимом использовании практически совпадает; DBX-маршрут зависит от компонента
Изменение Удобное добавление, удаление и замена полей Не является отдельной моделью редактирования
Интеграция с наборами данных Требует собственного преобразования Может быть полезен в связке с DBX или FireDAC
Практический выбор Основной вариант для нового прикладного кода Имеет смысл при поддержке старого кода или DBX-зависимостей

Если задача состоит в чтении большого одноразового ответа, прямое дерево TJSONObject может оказаться избыточным. Если требуется редактирование сложной структуры, преимущество получает явная объектная модель. При интеграции с СУБД или TDataSet решающим становится маршрут FireDAC, а не название используемого юнита.

Потоковая обработка, UTF-8 и мобильные устройства

Загрузка файла в TStringStream удобна для эксперимента, но создаёт дополнительную копию данных. Для больших документов лучше читать через TStreamReader и передавать содержимое совместимому разборщику контролируемыми порциями. Если System.JSON не поддерживает нужный режим напрямую, применяют потоковый разборщик либо обработку набора данных.

JSON обычно передаётся в UTF-8, тогда как строки Delphi используют кодировку UnicodeString. Конвертация выполняется при чтении и сериализации, поэтому важно измерять её вместе с разбором. Следует учитывать наличие или отсутствие метки порядка байтов, поведение TEncoding.UTF8 и не использовать AnsiString как временное хранилище без явного преобразования.

На Android и iOS ограничения памяти проявляются сильнее, чем на настольном компьютере. Большой JSON-ответ способен вызвать рост памяти приложения, задержки отрисовки и завершение процесса системой. Особенно уязвимы сценарии, где документ одновременно находится в ответе HTTP, строке, потоке и объектном дереве.

Как выбрать API по результатам замеров

Для сравнения нужен набор реальных документов: малые объекты, большие массивы, вложенные структуры, строки на русском языке и другие Unicode-символы. Разбор, доступ к данным и сериализацию следует измерять отдельно, чтобы определить, на каком этапе возникает задержка.

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

В своей практике я использую TJSONObject из System.JSON как основной вариант для нового прикладного кода и изменяемых моделей. Data.DBXJSON сохраняю там, где важна совместимость или существующая интеграция с DBX. Крупные ответы, которые не требуют редактирования, перевожу в более экономный маршрут обработки и проверяю его замерами. Такой подход делает выбор API следствием реальной нагрузки, а не названия модуля.