уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Возможность работы с json в delphi без использования сторонних библиотек и компонентов впервые появилась в Delphi 2010. Именно тогда в составе исходников были обнаружены такие файлы как DBXJSON.pas, DBXJSONCommon.pas и DBXJSONReflect.pas, которые содержали необходимый минимум объектов и классов для парсинга и создания json-объектов в Delphi. Впоследствии, работа с json совершенствовалась, развивалась и библиотека delphi для json была перемещена в модули System.JSON.***.pas и, вместе с этим, получила новые возможности.

И вот теперь — в Delphi 10.3 Rio нам предлагается сразу два фреймворка для работы с JSON, каждый из которых по своему хорош и может быть использоваться в разработках под любые, поддерживаемые Delphi, операционные системы.

Введение

Delphi 10.3 Rio предоставляет две платформы (фреймворка, библиотеки) для обработки JSON:

  • JSON Objects Framework: этот фреймворк создает временные объекты для чтения и записи данных JSON.
  • Readers and Writers JSON Framework: этот фреймворк позволяет напрямую считывать и записывать данные JSON.

По сути, именно с «JSON Objects Framework» начиналась вся работа с JSON в Delphi. Это достаточно простая в использовании библиотека, с помощью которой можно организовать обмен данными с каким-нибудь сервером, например, реализовать API онлайн-сервиса, или же сделать возможность сохранения настроек своей программы в json-формате и так далее.

В свою очередь, «Readers and Writers JSON Framework» — это логическое продолжение развития работы с JSON в Delphi. Как было указано выше, эта библиотека позволяет читать/писать json напрямую, не создавая при этом каких-либо промежуточных объектов. Логично предположить, что именно эту библиотеку стоит использовать при работе с «большими» JSON-данными, когда «JSON Objects Framework» не справляется с задачей парсинга json и выкидывает «Out of memory». Более того, согласно официальной документации, «Readers and Writers JSON Framework» поддерживает работу с BSON.

Собственно, в этой статье я буду рассматривать работу библиотеки «JSON Objects Framework» для работы с json в delphi — посмотрю на удобство работы (с моей точки зрения), скорость обработки «большого» json и так далее. Затем, рассмотрю вторую библиотеку и, в итоге сравню обе библиотеки по нескольким критериям.

JSON

Как Вы, видимо, знаете, в основу JSON положены следующие понятия:

  1. Объект (запись) – неупорядоченный набор пар “ключ: значение”. Объект всегда начинается с “{” и заканчивается “}”.
  2. Массив – упорядоченный коллекция значений. Массив начинается с “[” и заканчивается “]”. Элементы массива разделяются запятыми.
  3. Значение – строка в двойных кавычках. Значение может быть: строкой, числом, true, false, null, объектом, массивом.
  4. Строка – набор Unicode-символов.
  5. Число – любое число в десятичной системе счисления.

Например, вот такой JSON:

{
  "host_id": "https:webdelphi.ru:443",
  "verified": true,
  "ascii_host_url": "https://webdelphi.ru/",
  "unicode_host_url": "https://webdelphi.ru/",
  "main_mirror": {
    "unicode_host_url": "https://webdelphi.ru/",
    "verified": false
  },
  "host_data_status": "INDEXED",
  "host_display_name": "Блог WebDelphi.ru"
}

Содержит один вложенный объект с именем main_mirror и пары host_id, verified, host_display_name и так далее.

А этот JSON содержит массив вложенных объектов (original_texts) и пары (count и quota_remainder). При этом массив содержит вложенный объект (в примере объект один, однако в реальности таких объектов может быть сколько угодно), в котором содержатся свои пары (id, content_snippet и date).

{
  "original_texts": [
    {
      "id": "explicit error message",
      "content_snippet": "explicit error message",
      "date": "2016-01-01T00:00:00,000+0300"
    }
  ],
  "count": 1,
  "quota_remainder": 1
}

Таким образом, JSON может быть какой угодно сложности — содержать или не содержать массивы и вложенные объекты, пары могут содержать значения boolean, null, числа или строки, имена пар могут несколько раз повторяться и так далее.

Для просмотра небольших по объему JSON-объектов можете воспользоваться приложением «JSON Viewer». Скачать программу можно отсюда (ссылка для скачивания находится в конце статьи) или отсюда.

JSON Viewer покажет вам json в виде дерева. Например, последний представленный выше пример json в программе будет выглядеть вот так:

Теперь, освежив немного в памяти, что из себя представляет JSON, можно приступать к рассмотрению библиотек для работы с JSON в Delphi.

JSON Objects Framework

«JSON Objects Framework» поддерживает все типы JSON, которые в Delphi представлены соответствующими классами:

  • TJSONObject — объект
  • TJSONArray — массив
  • TJSONNumber — число
  • TJSONString — строка
  • TJSONTrue, TJSONFalse — True/False
  • TJSONNull — Null

Все эти классы являются потомками абстрактного класса TJSONValue, который, в свою очередь, является потомком TJSONAncestor. В этом плане ничего не поменялось с момента выхода Delphi 2010.

Создание JSON в Delphi

Простые примеры JSON-объектов

Начинать работу по созданию нового JSON следует с создания нового объекта (TJSONObject).

Для примера, создадим JSON-объект, который будет содержать всего одну пару следующего вида «Привет: мир!». То есть строка «Привет» — это будет имя пары, а строка «мир!» — её (пары) значения.

В Delphi такой JSON-объект создается элементарно:

var JsonObject: TJSONObject;
begin
  JsonObject:=TJSONObject.Create;
  JsonObject.AddPair('Привет', 'мир!');

В итоге, JsonObject будет содержать следующий json:

{
    "Привет": "мир!"
}

Метод AddPair добавляет в JSON-объект новую пару и имеет следующее описание:

function AddPair(const Pair: TJSONPair): TJSONObject; overload;
function AddPair(const Str: TJSONString; const Val: TJSONValue): TJSONObject; overload;
function AddPair(const Str: string; const Val: TJSONValue): TJSONObject; overload;
function AddPair(const Str: string; const Val: string): TJSONObject; overload;

Этих четырех реализаций метода AddPair вполне достаточно, чтобы создать любой по сложности JSON-объект. Например, создадим вот такой JSON:

{
    "String": "Строка",
    "Integer": 255,
    "Boolean": true,
    "Double": 10.532,
    "Null": null
}

Создать такой JSON достаточно просто:
Вариант №1. Последовательно добавить каждую новую пару в объект:

var JSON: TJSONObject;
begin
  JSON:= TJSONObject.Create;
  //Добавляем в json строку
  JSON.AddPair('String','Строка');
  //Добавляем в json число Integer
  JSON.AddPair('Integer',TJSONNumber.Create(255));
  //Добавляем в json boolean
  JSON.AddPair('Boolean',TJSONBool.Create(True));
  //Добавляем в json число Double
  JSON.AddPair('Double',TJSONNumber.Create(10.532));
  //Добавляем в json Null
  JSON.AddPair('Null',TJSONNull.Create);

Вариант №2. Использовать результат функции AddPair и добавлять новые пары «в одну строку»:

JSON.AddPair('String','Строка')
        .AddPair('Integer',TJSONNumber.Create(255))
        .AddPair('Boolean',TJSONBool.Create(True))
        .AddPair('Double',TJSONNumber.Create(10.532))
        .AddPair('Null',TJSONNull.Create);

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

Создание массивов и вложенных объектов JSON в Delphi

Например, создадим json следующего содержания:

{
  "kind":"tasks#task",
  "title":"Новая задача",
  "links":
  [
        {
      "type":"href",
      "description":"desc",
      "link":"http://webdelphi.ru"
    }
,
    
    {
      "type":"href2",
      "description":"desc",
      "link":"http://webdelphi.ru"
    }
  ]
}

Такой json уже содержит вложенный массив из двух элементов, где каждый элемент — вложенных объект с тремя парами. В JSON Viewer этот json выглядит следующим образом:

Создадим такой JSON в Delphi, используя JSON Objects Framework:

var JSON: TJSONObject;
    JSONArray: TJSONArray;
    JSONNestedObject: TJSONObject;
begin
  JSON:= TJSONObject.Create;
  try
    //Добавляем в json строки
    JSON.AddPair('kind','tasks#task')
        .AddPair('title','Новая задача');
    //Создаем массив
    JSONArray:=TJSONArray.Create;
    {-- добавляем в массив первый объект --}
    // 1. заносим в массив пустой json-объект
    JSONArray.AddElement(TJSONObject.Create);
    //получаем ссылку на добавленный объект
    JSONNestedObject:=JSONArray.Items[pred(JSONArray.Count)] as TJSONObject;
    //заполняем объект данными
    JSONNestedObject.AddPair('type','href')
                    .AddPair('description','desc')
                    .AddPair('link','http://webdelphi.ru');
 
    {-- добавляем в массив второй объект --}
    // 1. заносим в массив пустой json-объект
    JSONArray.AddElement(TJSONObject.Create);
    //получаем ссылку на добавленный объект
    JSONNestedObject:=JSONArray.Items[pred(JSONArray.Count)] as TJSONObject;
    //заполняем объект данными
    JSONNestedObject.AddPair('type','href2')
                    .AddPair('description','desc')
                    .AddPair('link','http://webdelphi.ru');
 
    //записываем массив в json-объект
    JSON.AddPair('links', JSONArray);

Поверьте, мы сейчас создали именно такой json-объект, как представлено выше. Как видите, ничего особенно сложного в создании любых JSON-объектов нет. Главное, на что стоит обращать внимание при создании json в delphi — какая пара является родителем для других пар. Все JSON-объекты, которые мы сейчас создавали были «видны» и понятны только нам, так как мы не пытались их каким-любо образом отобразить, например, в Memo. Вместе с этим, JSON Objects Framework представляет сразу несколько методов для «печати» созданных JSON-объектов.

Преобразование JSON в String

Для того, чтобы преобразовать созданный JSON-объект в строку у JSON Objects Framework имеются следующие методы:

function ToString: string; override;
function ToJSON: string;
function Format(Indentation: Integer = 4): string; overload;
function ToBytes(const Data: TArray; Offset: Integer): Integer;override;
procedure ToChars(Builder: TStringBuilder);override;

Метод ToBytes представляет JSON-объект в виде массива байтов. Этот метод удобно использовать, например, когда необходимо перекодировать полученную строку в какую-либо кодировку, например, ANSI или UTF-8. Этот метод используется в методе ToJSON.
Метод ToJSON представляет JSON-объект в виде строки в кодировке UTF-8. Так будет выглядеть JSON-объект из последнего примера:

{"kind":"tasks#task","title":"\u041D\u043E\u0432\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430","links":[{"type":"href","description":"desc","link":"https:\/\/www.webdelphi.ru"},{"type":"href2","description":"desc","link":"http:\/\/webdelphi.ru"}]}

В свою очередь, метод ToString переводит Json-объект в UnicodeString и результат представления будет уже таким:

{"kind":"tasks#task","title":"Новая задача","links":[{"type":"href","description":"desc","link":"https:\/\/www.webdelphi.ru"},{"type":"href2","description":"desc","link":"http:\/\/webdelphi.ru"}]}

Как видите, русские буквы будут представлены «как есть».
Метод ToChars использует объект TStringBuilder для представления JSON-объекта. Используется метод следующим образом:

var SB: TStringBuilder;
    SB:=TStringBuilder.Create;
    JSON.ToChars(SB);
    SB.ToString

Соответственно, если после этого не проводить с TStringBuilder никаких манипуляций, то результатом выполнения метода ToChars будет всё та же строка, что и при использовании метода ToString:

{"kind":"tasks#task","title":"Новая задача","links":[{"type":"href","description":"desc","link":"https:\/\/www.webdelphi.ru"},{"type":"href2","description":"desc","link":"http:\/\/webdelphi.ru"}]}

Метод Format применяется в том случае, если JSON-объект необходимо представить в человеко-понятном виде (с отступами и форматированием). При этом, в качестве параметра функции Format можно указать количество отступов для дочерних объектов (по умолчанию значение равно 4). Результатом работы Format будет следующее представление JSON-объекта:

{
    "kind": "tasks#task",
    "title": "Новая задача",
    "links": [
        {
            "type": "href",
            "description": "desc",
            "link": "https:\/\/www.webdelphi.ru"
        },
        {
            "type": "href2",
            "description": "desc",
            "link": "http:\/\/webdelphi.ru"
        }
    ]
}

Также следует обратить внимание на то, что при представлении JSON-объекта в виде строки delphi используется экранирование таких символов как ‘/’, ‘»‘, »’ и так далее.

Таким образом, любой JSON-объект в «JSON Objects Framework» можно представлять: в виде массива байтов, в виде строки UTF-8, в виде строки Unicode, в любой кодировке, используя массив байтов или объект TStringBuilder, в человеко-читаемом виде, настраивая при этом количество отступов дочерних элементов.

С созданием и представлением JSON-объектов в Delphi разобрались. Следующая задача — научиться разбирать JSON любой сложности в Delphi.

Парсинг JSON в Delphi

Разбор json в delphi при использовании «JSON Objects Framework» следует начинать также как и при создании нового json-объекта, то есть создать объект TJSONObject. Далее, в зависимости от того, что нам необходимо получить в результате парсинга JSON мы можем:

  1. Использовать перечислители (TJSONObject.TEnumerator)
  2. Получать доступ к паре в json-объекте, указывая путь до неё
  3. Получать доступ к паре в json-объекте, указав её имя
  4. и так далее.

Как я уже сказал выше, парсинг начинается с создания (получения) объекта TJSONObject.

Сделать это можно следующими способами:
во-первых, воспользоваться классовым методом ParseJSONValue, который в Delphi имеет следующее описание:

class function ParseJSONValue(const Data: PByte; const Offset: Integer; const ALength: Integer; Options: TJSONParseOptions): TJSONValue; overload; static;
class function ParseJSONValue(const Data: TArray; const Offset: Integer; IsUTF8: Boolean = True): TJSONValue; overload; inline; static;
class function ParseJSONValue(const Data: TArray; const Offset: Integer; Options: TJSONParseOptions): TJSONValue; overload; inline; static;
class function ParseJSONValue(const Data: TArray; const Offset: Integer; const ALength: Integer; IsUTF8: Boolean = True): TJSONValue; overload; inline; static;
class function ParseJSONValue(const Data: TArray; const Offset: Integer; const ALength: Integer; Options: TJSONParseOptions): TJSONValue; overload; inline; static;
class function ParseJSONValue(const Data: string; UseBool: Boolean = False; RaiseExc: Boolean = False): TJSONValue; overload; static;
class function ParseJSONValue(const Data: UTF8String; UseBool: Boolean = False; RaiseExc: Boolean = False): TJSONValue; overload; static;

Параметры метода следующие:

  • Data — это массив байтов, строка или строка UTF-8 для анализа.
  • Offset — это количество байтов, которое нужно пропустить с начала Data.
  • ALength — это количество байтов для чтения из Data.
  • IsUTF8 указывает, является Data текстом UTF-8, который содержит метку порядка байтов (True) или нет (False).
  • UseBool определяет, используют ли экземпляры, представляющие логическое значение, класс TJSONBool (True) или классы TJSONTrue и TJSONFalse (False).
  • Options — это набор параметров синтаксического анализа ParseJSONValue.
  • RaiseExc — определяет поведение метода при неудачном анализе json. True — вызывает исключение типа EJSONParseException, False — метод вернет nil, не создавая исключения.

Например, используя метод ParseJSONValue, можно распарсить json и получить TJSONObject можно следующим образом:

var JSON: TJSONObject;
begin
   JSON:=TJSONObject.ParseJSONValue('{"firstName": "Петров","lastName": "Пётр"}', False, True) as TJSONObject;
   {работаем с полученным TJSONObject}

во-вторых, распарсить json в delphi можно, используя метод Parse:

function Parse(const Data: TArray; const Pos: Integer; UseBool: Boolean = False): Integer; overload;
function Parse(const Data: TArray; const Pos: Integer; const Count: Integer; UseBool: Boolean = False): Integer; overload;

Параметры метода, аналогичны параметрам ParseJSONValue, то есть:

  • Data — массив байтов для анализа
  • Pos — это количество байтов, которое нужно пропустить с начала Data
  • Count — это количество байтов для чтения из Data.
  • UseBool — определяет, используют ли экземпляры, представляющие логическое значение, класс TJSONBool (True) или классы TJSONTrue и TJSONFalse (False)

Метод Parse можно использовать, например, так:

const
  cJsonStr = '{'+
   '"firstName": "Петров",'+
   '"lastName": "Пётр",'+
   '"address": {'+
   '    "streetAddress": "ул. Дельфийская, 101, кв.101",'+
   '    "city": "Дельфийск",'+
   '    "postalCode": "100301"'+
   '},'+
   '"phoneNumbers": ['+
   '    "812 123-1234",'+
   '    "916 123-4567"'+
   ']'+
'}';
 
var JSON: TJSONObject;
    JSONArray: TJSONArray;
    JSONNestedObject: TJSONObject;
begin
   JSON:=TJSONObject.Create;
   JSON.Parse(TEncoding.UTF8.GetBytes(cJsonStr),0);

В приведенном примере мы использовали TEncoding.UTF8 для получения массива байтов в кодировке UTF-8. Если бы мы этого не сделали, то в результате вызова метода Parse мы получили бы исключение. Пример ошибочного вызова метода Parse (в примере используется тот же json, то и выше):

JSON:=TJSONObject.Create;
JSON.Parse(BytesOf(cJsonStr),0);

В результате, получим вот такое исключение:

UTF8: An unexpected continuation byte in 2-byte UTF8. Path », line 1, position 17 (offset 16)

Теперь, получив в свое распоряжение TJSONObject, можно приступать к получению из него необходимых на данных. Рассмотрим несколько примеров того, как можно как парсить json в Delphi 10.3 Rio, используя JSON Objects Framework.

Пример №1. Работа с парами JSON в Delphi

Например, нам необходимо получить имя и фамилию человека из вот такого объекта JSON:

{
   "firstName": "Петров",
   "lastName": "Пётр",
   "address": {
       "streetAddress": "ул. Дельфийская, 101, кв.101",
       "city": "Дельфийск",
       "postalCode": "100301"
   },
   "phoneNumbers": [
       "812 123-1234",
       "916 123-4567"
   ]
}

Сделать это можно следующими способами:
1. Получить доступ к парам json-объекта, используя свойство Values у TJSONObject:

const
  cJsonStr = '{'+
   '"firstName": "Петров",'+
   '"lastName": "Пётр",'+
   '"address": {'+
   '    "streetAddress": "ул. Дельфийская, 101, кв.101",'+
   '    "city": "Дельфийск",'+
   '    "postalCode": "100301"'+
   '},'+
   '"phoneNumbers": ['+
   '    "812 123-1234",'+
   '    "916 123-4567"'+
   ']'+
'}';
 
 
var JSON: TJSONObject;
begin
   JSON:=TJSONObject.ParseJSONValue(cJsonStr,False, True) as TJSONObject;
   try
     ShowMessage(JSON.Values['firstName'].Value);
     ShowMessage(JSON.Values['lastName'].Value);
   finally
     JSON.Free;
   end;
end;

здесь мы указали в свойстве Value имя пары значение которой необходимо получить. Value возвращает объект TJSONValue свойство Value которого содержит значение пары. Аналогичным образом работает и метод TJSONObject GetValue:

function GetValue(const Name: string): TJSONValue; overload;

Поиск чувствителен к регистру и возвращает первую пару со строковой частью (именем), совпадающей с аргументом Name.

2. Использовать метод FindValue
В отличие от свойства Value этот метод позволяет указывать путь к определенному значению в json-объекте.

function FindValue(const APath: string): TJSONValue;

Если значение не будет найдено, то метод вернет значение nil.

Применительно к нашей задаче (чтение имени и фамилии из json) использование FindValue будет точно таким же, как и в предыдущем способе использование свойства Value, то есть код Delphi будет вот таким:

JSON:=TJSONObject.Create;
   try
     JSON.Parse(TEncoding.UTF8.GetBytes(cJsonStr),0);
     ShowMessage(JSON.FindValue('firstName').Value);
     ShowMessage(JSON.FindValue('lastName').Value);
   finally
     JSON.Free;
   end;

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

JSON.FindValue('address.city').Value;

Здесь мы указали уже не имя пары JSON, а путь — прочитали значение пары «city» из вложенного json «address».

3. Использовать перечислитель всех пар JSON-объекта
Этот способ удобно использовать, например, когда необходимо получить вообще все данные из json, а не только отдельные значения. Или же в том случае, если какой-либо пары в json-объекте может не быть в зависимости от наличия той или иной информации на сервере (такое, кстати, вполне возможно при работе с API онлайн-сервисов).

Вот как может выглядеть чтение строк из json с использованием перечислителя (TJSONObject.TEnumerator):

var JSON: TJSONObject;
    JSONEnum: TJSONObject.TEnumerator;
begin
   //создаем JSON-объект и парсим json
   JSON:=TJSONObject.Create;
   JSON.Parse(TEncoding.UTF8.GetBytes(cJsonStr),0);
   try
     //получаем доступ к перечислителю
     JSONEnum:=JSON.GetEnumerator;
     //пока есть не перечисленные пары в json - переходим на следующую пару
     while JSONEnum.MoveNext do
       //проверяем имя json-пары и, если находим подходящее - выводим сообщение
       if SameText(JSONEnum.Current.JsonString.Value, 'firstName')
         or SameText(JSONEnum.Current.JsonString.Value, 'lastName') then
           ShowMessage(JSONEnum.Current.JsonValue.Value);

Таким образом, прочитать строку из json в delphi можно, как минимум, тремя способами:

  1. Использовать свойство Value или метод GetValue объекта TJSONObject,
  2. Использовать метод FindValue,
  3. Использовать перечислитель TJSONObject.TEnumerator.

Ну и, напоследок, следующий код вернет значение пары или сообщение об ошибке, если пара отсутствует в json-объекте:

var S: string;
if JSON.TryGetValue('lastName', S) then
  ShowMessage(S)
else
  ShowMessage('Пара lastName не найдена')
Пример №2. Работа с массивом json в delphi

Отдельный пример работы с JSON в Delphi — получение данных из массива json. В приведенном выше примере JSON массив содержит номера телефонов:

{
   ....
   "phoneNumbers": [
       "812 123-1234",
       "916 123-4567"
   ]
}

Как в этом случае перечислить все элементы массива json, используя возможности «JSON Objects Framework»?

Опять же, решение этой задачи в Delphi подразумевает использование нескольких вариантов.

1.Использование свойств TJSONArray для перечисления всех элементов массива json

Объект TJSONObject, в числе прочих, содержит следующие свойства:

  • Count — количество элементов массива
  • Items[Index: integer] — возвращает элемент массива с индексом Index в виде TJSONValue 

Используя эти свойства, перечислить все элементы массива из примера можно следующим образом:

   JSON:=TJSONObject.Create;
   JSON.Parse(TEncoding.UTF8.GetBytes(cJsonStr),0);
   try
    //получаем доступ к массиву json
    JSONArray:=JSON.Values['phoneNumbers'].AsType;
    //перечисляем все элементы массива
    for I := 0 to Pred(JSONArray.Count) do
      ShowMessage(JSONArray.Items[i].Value)

Здесь строит обратить внимание на последнюю строку:

ShowMessage(JSONArray.Items[i].Value)

так как мы знаем, что массив json содержит только строки, то мы смогли сразу использовать свойство Value и вывести значение элемента массива пользователю. Но массив json может содержать не только отдельные строки, но и, например, объекты json. В этом случае парсинг массива json в delphi немного усложняется.

2. Использование перечислителей массивов json
У объекта TJSONArray имеется свой тип перечислителя — TJSONArray.TEnumerator. работает он аналогично перечислителю объекта TJSONObject, который мы рассматривали выше. Например, вот так мы можем перечислить все номера телефонов из json, представленного выше:

var JSON: TJSONObject;
    JSONArray: TJSONArray;
    JsonArrEnum: TJSONArray.TEnumerator;
begin
   JSON:=TJSONObject.Create;
   JSON.Parse(TEncoding.UTF8.GetBytes(cJsonStr),0);
   try
    //получаем доступ к массиву json
    JSONArray:=JSON.Values['phoneNumbers'].AsType;
    //получаем перечислитель массива
    JsonArrEnum:=JSONArray.GetEnumerator;
    //перечисляем все элементы массива json
    while JsonArrEnum.MoveNext do
      ShowMessage(JSONArrEnum.Current.Value);

Таким образом, используя представленные выше варианты работы с массивом json в «JSON Objects Framework» можно получать любую интересующую вас информацию.

Рассмотрим более интересную задачу, которую можно решать в delphi при использовании с json — работу с вложенными объектами.

Пример №3. Работа с вложенными объектами json в delphi

Попробуем разобрать вот такой JSON:

{
  "symbol":"AAPL",
  "stock_exchange_short":"NASDAQ",
  "timezone_name":"America/New_York",
  "intraday":
  {
    "2018-10-1915:59:00":
    {
      "open":"23",
      "low":"4"
    }
,
    "2018-10-1915:58:00":
    {
      "open":"25",
      "low":"21"
    }
  }
}

Дерево этого JSON в программе JSON Viewer будет выглядеть следующим образом:

Обратите внимание на пару «interday» — она является объектом json и, при этом, каждая пара этого объекта в качестве значения содержит вложенный json-объект. Что касается пар в начале объекта — symbol и прочих, то они уже для нас не представляют особого интереса — примеры по работе с ними представлены вышеа вот как разобрать intraday — рассмотрим более детально. 

Итак, каждая пара в interday имеет свое уникальное имя (дату). Вложенный JSON-объект, в свою очередь, содержит две пары – open и low, которые и требуется прочитать.

Алгоритм парсинга json может быть следующим:

  1. Ищем в JSON объект intraday (как искать пары в json — см. выше)
  2. Используя перечислитель (enumerator), перечисляем пары, содержащиеся в JSON-объекте intraday;
  3. Используя возможности перечислителя проходим по парам вложенного объекта JSON и считываем значения из полей open и low.

Реализация этого алгоритма парсинга json, содержащего вложенные объекты может быть следующей:

  cJsonStr = '{' +
    '  "symbol":"AAPL",' +
    '  "stock_exchange_short":"NASDAQ",' +
    '  "timezone_name":"America/New_York",' +
    '  "intraday":' +
    '  {           ' +
    '    "2018-10-1915:59:00":' +
    '    {' +
    '      "open":"23",' +
    '      "low":"4"' +
    '    }' +
    ',' +
    '    "2018-10-1915:58:00":' +
    '    {' +
    '      "open":"25",' +
    '      "low":"21"   ' +
    '    }' +
    '  }' +
    '}';
 
var
  JSONValue: TJSONValue;
  JsonNestedObject: TJSONObject;
  quote, low, open: string;
begin
  JSONValue := TJSONObject.ParseJSONValue(cJsonStr).AsType;
  JsonNestedObject := JSONValue.FindValue('intraday').AsType;
  with JsonNestedObject.GetEnumerator do
    // цикл будет выполняться до тех пор, пока в JSON-объекте есть хотя бы одна не пройденная пара
    while MoveNext do
    begin
      // получаем название вложенного объекта (для первого прохода в цикле - это 2018-10-1915:59:00).
      quote := Current.JsonString.Value;
      // считываем значение пары low
      low := (Current.JSONValue as TJSONObject).Values['low'].Value;
      // считываем значение пары open
      open := (Current.JSONValue as TJSONObject).Values['open'].Value;
      // выводим полученные значения в Memo
      Memo1.Lines.Add(Format('%s, %s, %s', [quote, low, open]))
    end;
end;

Как видите, несмотря на кажущуюся сложность, разбор json в delphi оказался не таким уж и сложным, если воспользоваться знаниями о возможностях  «JSON Objects Framework».

Жизненный цикл объектов в «JSON Objects Framework»

В JSON родительский объект владеет любым из содержащихся в нем значений, если для свойства Owned не установлено значение False. В этом случае уничтожение объекта JSON пропускает каждый элемент, для которого установлен флаг False. Эта возможность позволяет объединять различные объекты в более крупные, сохраняя при этом право владения объектами. По умолчанию свойство имеет значение True, что означает, что все содержащиеся в нем экземпляры принадлежат их родителям.

Производительность «JSON Objects Framework»

Чтобы оценить производительность «JSON Objects Framework» я решил провести следующие тесты:

  1. Оценить время создания нового JSON, содержащий пары без массивов и вложенных объектов
  2. Оценить время создания нового JSON с массивами и вложенными объектами
  3. Оценить время чтения JSON, содержащего пары без массивов и вложенных объектов
  4. Оценить время чтения JSON, содержащего пары с массивами и вложенными объектами.

На чем тестировалась работа:

  • Процессор Intel Core i5 8400 (6-ти ядерный)
  • ОЗУ: 16 ГБ
  • ОС: Windows 10 x64

Производительность «JSON Objects Framework» при создании JSON

В качестве первого теста я создавал вот такой JSON:

{
  "Pair":"Value",
  "Pair":"Value",
  ...
}

Количество пар в json-объекте: 500 000
Время выполнения операции засекалось от момента создания экземпляра TJSONObject:

JSON := TJSONObject.Create;

До полного уничтожения объекта:

FreeAndNil(JSON);

Код теста:

var
  JSON: TJSONObject;
  PName, PValue: string;
  t1: TStopwatch;
  Count, Limit: integer;
  Str: TStringStream;
begin
  Count :=10;//количество повторений теста
  Limit :=500000;//количество пар в json
  PName :='Pair';
  PValue:='Value';
 
  StringGrid1.RowCount:=Count+1; //настраиваем количество строк таблицы
 
  for var I := 1 to Count do
  begin
    // засекаем всё время - от создания объекта до его уничтожения
    t1 := TStopwatch.StartNew;
    JSON := TJSONObject.Create;
    try
      for var J := 0 to Pred(Limit) do
        JSON.AddPair(PName, PValue);
    finally
      FreeAndNil(JSON);
    end;
    t1.Stop;
    //выводим результаты теста
    StringGrid1.Cells[0,i]:=I.ToString;
    StringGrid1.Cells[1,i]:=t1.ElapsedMilliseconds.ToString;
  end;
end;

Результаты тестирования:

  • Размер полученного JSON: 7500 kb
  • Максимальное время: 156 мс.
  • Минимальное время: 144 мс.

Для второго теста я создавал JSON следующего содержания:

{
  "Array":
  [
    "TestValue",
    "TestValue"
  ]
,
  "Array":
  [
    "TestValue",
    "TestValue"
  ]
}
  • Количество массивов в JSON: 10
  • Количество элементов массива: 500 000

Код теста:

var
  JSON: TJSONObject;
  JSONArray: TJSONArray;
  t1: TStopwatch;
  ArrayCount, ArrayLimit: integer;
  TestCount: integer;
begin
  ArrayCount :=  10;
  ArrayLimit := 500000;
  TestCount:=10;
 
  StringGrid2.RowCount:=TestCount+1;
 
  for var I := 1 to TestCount do
  begin
    // засекаем всё время - от создания объекта до его уничтожения
    t1 := TStopwatch.StartNew;
    JSON := TJSONObject.Create;
    try
      for var j := 1 to ArrayCount do
        begin
          JSONArray:=TJSONArray.Create;
          for var k := 1 to ArrayLimit do
            JSONArray.AddElement(TJSONString.Create('TestValue'));
          JSON.AddPair('Array',JSONArray);
        end;
    finally
      FreeAndNil(JSON);
    end;
    t1.Stop;
    StringGrid2.Cells[0,i]:=I.ToString;
    StringGrid2.Cells[1,i]:=t1.ElapsedMilliseconds.ToString;
  end;
end;

Результаты тестирования:

  • Размер полученного JSON: 60 Mb
  • Максимальное время: 601 мс.
  • Минимальное время: 565 мс.

В целом, учитывая, что тестирование проводилось на не самом слабом ПК, можно сказать, что производительность «JSON Objects Framework» по созданию новых объектов JSON в Delphi достаточно хорошая.

Теперь попробуем распарсить полученные JSON.

Производительность «JSON Objects Framework» при парсинге JSON

Оба JSON-объекта, созданные в предыдущем тесте были сохранены в файл. В этой части статьи будем их парсить.

Разбор JSON, содержащего пары значений:

  • Количество пар в JSON: 500 000
  • Размер файла, содержащего JSON: 7500 kb

Код теста:

var
  S: TStringStream;
  JSON: TJSONObject;
  t_parse: TStopwatch;
  Bytes: TArray;
begin
  //считываем файл в TStringStream
  S := TStringStream.Create('', TEncoding.UTF8);
  try
    S.LoadFromFile('SimpleJson.txt');
    Bytes := S.Bytes;
  finally
    FreeAndNil(S);
  end;
 
  StringGrid2.RowCount:=11;
 
  for var I := 1 to 10 do
  begin
    //засекаем время от момента создания TJSONObject
    t_parse := TStopwatch.StartNew;
    JSON := TJSONObject.Create;
    try
      //парсим JSON
      JSON.Parse(Bytes, 0);
    finally
      FreeAndNil(JSON);
    end;
    //останавливаем таймер после уничтожения TJSONObject
    t_parse.Stop;
    //выводим результаты теста в TStringGrid
    StringGrid2.Cells[0, I] := I.ToString;
    StringGrid2.Cells[1, I] := t_parse.ElapsedMilliseconds.ToString;
  end;
end;

Результаты парсинга JSON:

  • Минимальное время: 225 мс.
  • Максимальное время: 232 мс.

Парсинг JSON, содержащего массивы значений. Здесь код теста идентичный предыдущему (меняется только название файла), поэтому приводить я его не буду, а сразу покажу результаты теста.

  • Минимальное время: 971 мс.
  • Максимальное время: 1021 мс.

Как видно из представленных результатов, парсинг JSON происходит примерно в 1,5 раза дольше, чем его (JSON) создание, но, в принципе, тоже приемлемо для небольших по объему JSON-объектов.

Однако, как я говорил ранее, в начале статьи, бывают случаи, когда «JSON Objects Framework» не справляется с задачей парсинга. Примером JSON, который нет возможности разобрать может служить набор открытых данных одного из органов государственной власти РФ. Архив с данными можно скачать здесь. Если по каким-либо причинам вы не можете скачать  этот файл, то здесь лежит его копия. Файл размером порядка 544 Мб. Очевидно, что в этом случае необходимо использовать второй фреймворк для работы с JSON в Delphi — Readers and Writers JSON Framework. И именно о нем мы поговорим с вами в следующей статье блога.

Выводы

Итак, я постарался подробно рассмотреть возможности «JSON Objects Framework» для работы с json в delphi. Библиотека, в настоящий момент, представляет собой достаточно удобный инструмент для работы с небольшими и средними по размеру JSON-объектами, позволяет создавать любые по сложности объекты, работать с массивами json, вложенными объектами, позволяет учитывать наличие BOM в строке, содержащей json и так далее. Более того, с момента своего первого выхода, библиотека стала, на мой взгляд, более удобной и производительной.

Таким образом, я бы рекомендовал «JSON Objects Framework» в качестве отправной точки для начала знакомства работы с json в delphi, а также для использования этой библиотеки для организации обмена данными при реализации какого-либо API онлайн-сервиса в Delphi.

Книжная полка

Описание: Рассмотрены практические вопросы по разработке клиент-серверных приложений в среде Delphi 7 и Delphi 2005 с использованием СУБД MS SQL Server 2000, InterBase и Firebird. Приведена информация о теории построения реляционных баз данных и языке SQL. Освещены вопросы эксплуатации и администрирования СУБД.
купить книгу delphi на ЛитРес
Описание: Рассмотрены малоосвещенные вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные режимы их работы, особенности для протоколов TCP и UDP и др.
купить книгу delphi на ЛитРес
4.7 9 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
11 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Андрей Посметухов

Здравствуйте! Спасибо за статье очень помогла разобраться в нужно мне в данный момент теме. Но у меня немного не получается распарсить один JSON приходящий от 1С. { "value": [{ "Number": "000000001", "Date": "2019-05-06T09:31:52", "Тема": "Доверенность", "Ref_Key": "57c6003a-6faf-11e9-8288-689423fc743a", "DataVersion": "AAAAAgAAAAA=", "DeletionMark": false, "Товары": [{ "Представление": "Труба D20", "Количество": 10, "Цена": 150, "Сумма": 1500 }, { "Представление": "Болт 10х50", "Количество": 5, "Цена": 10, "Сумма": 50 } ] }, { "Number": "000000002", "Date": "2019-05-06T09:32:34", "Тема": "Счет на оплату", "Ref_Key": "57c6003d-6faf-11e9-8288-689423fc743a", "DataVersion": "AAAAAwAAAAA=", "DeletionMark": false, "Товары": [{ "Представление": "Туалетная бумага", "Количество": 10, "Цена": 150, "Сумма": 1500 }, { "Представление": "Освежитель воздуха", "Количество": 5, "Цена":… Подробнее »

Андрей Посметухов
Ответить на  Vlad

Спасибо за ответ. У меня еще вопрос.
Вы везде используете выражение
JSONArray:=JSON.Values[‘phoneNumbers’].AsType;
У меня этот метод не работает. Выдает ошибку компиляции:
E2531 Method ‘AsType’ requires explicit type argument(s)
Почему не подскажите?

Андрей Посметухов

Спасибо за Ваш труд. Очень нужный материал в очень нужный момент. Как раз делаю один проект где это применяю парсинг JSON.
Но у меня не получается распарсить один файл. В нем получается вложенный массив в массиве. Подскажите как его можно разобрать?
Пример привожу.

Спойлер
{
«value»: [{
«Number»: «000000001»,
«Date»: «2019-05-06T09:31:52»,
«Тема»: «Доверенность»,
«Ref_Key»: «57c6003a-6faf-11e9-8288-689423fc743a»,
«DataVersion»: «AAAAAgAAAAA=»,
«DeletionMark»: false,
«Товары»: [{
«Представление»: «Труба D20»,
«Количество»: 10,
«Цена»: 150,
«Сумма»: 1500
}, {
«Представление»: «Болт 10х50»,
«Количество»: 5,
«Цена»: 10,
«Сумма»: 50
}
]
}, {
«Number»: «000000002»,
«Date»: «2019-05-06T09:32:34»,
«Тема»: «Счет на оплату»,
«Ref_Key»: «57c6003d-6faf-11e9-8288-689423fc743a»,
«DataVersion»: «AAAAAwAAAAA=»,
«DeletionMark»: false,
«Товары»: [{
«Представление»: «Туалетная бумага»,
«Количество»: 10,
«Цена»: 150,
«Сумма»: 1500
}, {
«Представление»: «Освежитель воздуха»,
«Количество»: 5,
«Цена»: 10,
«Сумма»: 50
}
]
}
]
}

alexpmitrov
alexpmitrov
09/01/2020 17:13

Здравствуйте. Помоги пожалуйста, как передать изображение в JSON. По документации должно быть так { fields: { «NAME»: «Глеб», «SECOND_NAME»: «Егорович», «LAST_NAME»: «Титов», «PHOTO»: { «fileData»: document.getElementById(‘photo’) }, «PHONE»: [ { «VALUE»: «555888», «VALUE_TYPE»: «WORK» } ] }, params: { «REGISTER_SONET_EVENT»: «Y» } } Проблема в этой строке «PHOTO»: { «fileData»: document.getElementById(‘photo’) } Как я понял это изображения передаются строкой в Base64. Вот мой код sBase64 := dm.ImageToBase64(‘D:\ArtalManager\img\gmap.jpg’); JsonObject := TJSONObject.Create; JsonPhone := TJSONObject.Create; JsonPhoto := TJSONObject.Create; JsonPhoneArr := TJSONArray.Create; JsonParams := TJSONObject.Create; JsonParams.AddPair(‘REGISTER_SONET_EVENT’,’Y’); JsonPhone.AddPair(‘VALUE’,’+380989423230′); JsonPhone.AddPair(‘VALUE_TYPE’,’WORK’); JsonPhoneArr.AddElement(JsonPhone); JsonFields:=TJSONObject.Create; JsonFields.AddPair(‘NAME’, ‘Alex’); JsonFields.AddPair(‘LAST_NAME’, ‘Mitrov’); JsonFields.AddPair(‘SECOND_NAME’, ‘Petrovich’); JsonFields.AddPair(‘PHONE’,JsonPhoneArr); /// JsonPhoto.AddPair(‘fileData’, sBase64); JsonFields.AddPair(‘PHOTO’,JsonPhoto); /// JsonObject.AddPair(‘fields’,… Подробнее »

brunnengi
brunnengi
27/01/2020 08:22

Спасибо за обновленную статью по JSON. Всегда пользуюсь вашим сайтом, когда спустя время забываю что и как) Хочу только добавить один нюанс. FindValue выдаст ошибку если такого пути нет. А TryGetValue не ищет по пути. Однако можно делать вот так: JSON.FindValue(‘result.deezer.album.cover_xl’).TryGetValue(s); В этом случае, если пути нет, то нее будет ошибки, а переменная S останется пустой. Ещё хочу отметить что теперь JSON в Delphi стал более терпим к JSON строке когда она «кривая». Т.е. раньше если даже в смом конце не стоит знак «}», то это приводило к ошибке. Сейчас же данные считываются до тех пор пока строки всё ещё… Подробнее »

Сергей Плахов
Сергей Плахов
27/09/2020 02:26

Исходники всего этого в виде работающего проекта есть?

Денис Бикинеев
Денис Бикинеев
30/08/2021 14:41

Здравствуйте. Пример 3 не компилируется. Ошибка на двух строках:

JSONValue := TJSONObject.ParseJSONValue(cJsonStr.Text).AsType;
JsonNestedObject := JSONValue.FindValue('items').AsType;

[dcc32 Error] Unit1.pas(38): E2531 Method ‘AsType’ requires explicit type argument(s)
Как решить проблему?

plyton.xana
plyton.xana
22/10/2023 00:26

Уважаемый автор, большое спасибо за обстоятельный и подробный разбор темы! Прошу помочь разобраться. Использую Embarcadero Delphi 10.4. В строке

quote := Current.JsonString.Value;


имею ошибку:

E2003 Undeclared identifier: 'JsonString'


Что я делаю неправильно? На что это исправить?