Возможность работы с 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 положены следующие понятия:
- Объект (запись) – неупорядоченный набор пар “ключ: значение”. Объект всегда начинается с “{” и заканчивается “}”.
- Массив – упорядоченный коллекция значений. Массив начинается с “[” и заканчивается “]”. Элементы массива разделяются запятыми.
- Значение – строка в двойных кавычках. Значение может быть: строкой, числом, true, false, null, объектом, массивом.
- Строка – набор Unicode-символов.
- Число – любое число в десятичной системе счисления.
Например, вот такой 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 мы можем:
- Использовать перечислители (TJSONObject.TEnumerator)
- Получать доступ к паре в json-объекте, указывая путь до неё
- Получать доступ к паре в json-объекте, указав её имя
- и так далее.
Как я уже сказал выше, парсинг начинается с создания (получения) объекта 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);
В результате, получим вот такое исключение:
Теперь, получив в свое распоряжение 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 можно, как минимум, тремя способами:
- Использовать свойство Value или метод GetValue объекта TJSONObject,
- Использовать метод FindValue,
- Использовать перечислитель 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 может быть следующим:
- Ищем в JSON объект intraday (как искать пары в json — см. выше)
- Используя перечислитель (enumerator), перечисляем пары, содержащиеся в JSON-объекте intraday;
- Используя возможности перечислителя проходим по парам вложенного объекта 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» я решил провести следующие тесты:
- Оценить время создания нового JSON, содержащий пары без массивов и вложенных объектов
- Оценить время создания нового JSON с массивами и вложенными объектами
- Оценить время чтения JSON, содержащего пары без массивов и вложенных объектов
- Оценить время чтения 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 и др.
|
Здравствуйте! Спасибо за статье очень помогла разобраться в нужно мне в данный момент теме. Но у меня немного не получается распарсить один 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, "Цена":… Подробнее »
Приветствую. Порядок парсинга должен быть такой: 1. Получаем объект TJSONObject (например, используем ParseJSONValue) 2. В полученном объекте получаем массив value. В представленном вами примере этот массив должен содержать два элемента. Каждый элемент массива — это объект JSON (TJSONObject) 3. Создаем цикл, например, for для прохождения по элементам массива value 3.1. Получаем доступ к i-му элементу массива — это будет TJSONObject 3.2. В этом TJsonObject получаем доступ к паре «Товары». Значение этой пары — массив (TJSONArray) 3.3. Создаем ещё один цикл для прохождения по элементам массива «Товары» 3.3.1 Получаем доступ к j-му элементу массива «Товары» — это будет снова объект TJsonObject… Подробнее »
Спасибо за ответ. У меня еще вопрос.
Вы везде используете выражение
JSONArray:=JSON.Values[‘phoneNumbers’].AsType;
У меня этот метод не работает. Выдает ошибку компиляции:
E2531 Method ‘AsType’ requires explicit type argument(s)
Почему не подскажите?
Спасибо за то, что заметили. Это ошибка подсветки исходного кода в плагине. Правильно должно быть так:
JSONArray:=JSON.Values[‘phoneNumbers’].AsType < TJSONArray >
Спасибо за Ваш труд. Очень нужный материал в очень нужный момент. Как раз делаю один проект где это применяю парсинг 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
}
]
}
]
}
Здравствуйте. Помоги пожалуйста, как передать изображение в 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’,… Подробнее »
Спасибо за обновленную статью по JSON. Всегда пользуюсь вашим сайтом, когда спустя время забываю что и как) Хочу только добавить один нюанс. FindValue выдаст ошибку если такого пути нет. А TryGetValue не ищет по пути. Однако можно делать вот так: JSON.FindValue(‘result.deezer.album.cover_xl’).TryGetValue(s); В этом случае, если пути нет, то нее будет ошибки, а переменная S останется пустой. Ещё хочу отметить что теперь JSON в Delphi стал более терпим к JSON строке когда она «кривая». Т.е. раньше если даже в смом конце не стоит знак «}», то это приводило к ошибке. Сейчас же данные считываются до тех пор пока строки всё ещё… Подробнее »
Исходники всего этого в виде работающего проекта есть?
Нет, не сохранял
Здравствуйте. Пример 3 не компилируется. Ошибка на двух строках:
[dcc32 Error] Unit1.pas(38): E2531 Method ‘AsType’ requires explicit type argument(s)
Как решить проблему?
Уважаемый автор, большое спасибо за обстоятельный и подробный разбор темы! Прошу помочь разобраться. Использую Embarcadero Delphi 10.4. В строке
quote := Current.JsonString.Value;
имею ошибку:
E2003 Undeclared identifier: 'JsonString'
Что я делаю неправильно? На что это исправить?