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

В прошлой статье, касающейся работы с JSON в Delphi мы рассматривали работу одной из библиотек Delphi 10.3 Rio — JSON Objects Framework, которая позволяет нам достаточно быстро и комфортно создавать JSON-объекты, читать данные, используя поиск по пути, по имени пары и так далее.Вместе с этим, необходимость создания отдельных объектов для работы с JSON, несмотря на удобство использования, имеет и свои недостатки — не всегда удается без проблем разобрать большие по объему JSON-данные. В том числе для таких случаев, когда  JSON Objects Framework не справляется, в Delphi появилась вторая библиотека для json — Readers and Writers JSON Framework, которая позволяет читать/писать json в потоке. именно этой библиотеке и будет посвящена сегодняшняя статья. 

Readers and Writers JSON Framework 

С помощью библиотеки «Readers and Writers JSON Framework» можно выполнять следующие операции:

  1. Записывать (создавать) JSON: для этого мы можем использовать класс TJSONObjectBuilder с помощью которого создаются объекты JSON. Класс TJSONObjectBuilder является оболочкой класса TJsonWriter, который предоставляет логику для сериализации данных JSON. И, несмотря на то, что мы можем писать объекты JSON, используя как класс TJSONObjectBuilder, так и TJsonWriter, разработчики Delphi рекомендуют использовать именно класс TJSONObjectBuilder.
  2. Читать JSON: для этого мы можем использовать класс TJSONIterator для итерации данных JSON. Класс TJSONIterator является оболочкой класса TJsonReader, который позволяет читать данные, сериализованные в формате JSON. И, опять же, несмотря на то, что мы можем читать данные JSON как с помощью класса TJSONIterator, так и TJsonReader, разработчики Delphi рекомендуют нам использовать класс TJSONIterator, поскольку он предоставляет дополнительные функции для перемещения по данным JSON.
  3. Читать и записывать данные BSON: для этого используются, соответственно, классы TBsonReader и TBsonWriter. Следует также отметить, что, в отличие от «Readers and Writers JSON Framework«, библиотека «JSON Objects Framework» не позволяет нам работать с BSON.

Таким образом, сегодня мы будем следовать рекомендациям разработчиков и используем в работе следующие классы:

  1. TJSONObjectBuilder — для записи json
  2. TJSONIterator — для чтения json
  3. TBsonReader и TBsonWriter — для работы с BSON

Создание JSON с помощью «Readers and Writers JSON Framework«

Для того, чтобы можно было сравнить удобство работы двух библиотек для работы с json в delphi, я буду воспроизводить примеры различных json-объектов из предыдущей статьи. И первый пример — json, содержащий «Привет: мир!»

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

Создадим вот такой JSON:

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

Чтобы создать и показать пользователю такой JSON-объект, используя «Readers and Writers JSON Framework» у меня получился вот такой код:

uses System.JSON.Builders,
     System.JSON.Types,
     System.JSON.Writers;
 
{$R *.dfm}
 
procedure TForm8.Button1Click(Sender: TObject);
var Builder: TJSONObjectBuilder;
    Writer: TJsonTextWriter;
    StringWriter: TStringWriter;
    StringBuilder: TStringBuilder;
begin
  StringBuilder := TStringBuilder.Create;
  StringWriter := TStringWriter.Create(StringBuilder);
  Writer := TJsonTextWriter.Create(StringWriter);
  Writer.Formatting := TJsonFormatting.Indented;
  Builder := TJSONObjectBuilder.Create(Writer);
 
  try
    Builder
      .BeginObject
         .Add('Привет', 'мир!')
      .EndObject;
    ShowMessage(StringBuilder.ToString)
  finally
    FreeAndNil(Builder);
    FreeAndNil(Writer);
    FreeAndNil(StringWriter);
    FreeAndNil(StringBuilder);
  end;
end;

Для интереса можете сравнить сколько строк кода потребовалось для создания этого же json в «JSON Objects Framework». Разберемся с тем, что было сделано.

Итак:

  1. для того, чтобы получить возможность создавать json нам необходимо создать экземпляр TJSONObjectBuilder (переменная Builder).
  2. В конструктор TJSONObjectBuilder необходимо передать экземпляр TJsonTextWriter (переменная Writer).
  3. Конструктор TJsonTextWriter, в свою очередь, требует указать экземпляр TStringWriter (переменная StringWriter).
  4. Конструктор же TStringWriter требует указания экземпляра TStringBuilder (переменнаяStringBuilder).

Поэтому у нас получился такой код создания экземпляра класса TJSONObjectBuilder.

Следующий момент — код:

Writer.Formatting := TJsonFormatting.Indented;

Здесь мы указали, что при форматировании JSON дочерние объекты должны иметь отступ. Тип TJsonFormatting имеет следующее описание в Delphi:

TJsonFormatting = (None, Indented);

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

После создания экземпляра TJSONObjectBuilder мы создали наш объект:

try
    Builder
      .BeginObject
         .Add('Привет', 'мир!')
      .EndObject;

Здесь метод BeginObject возвращает TJSONCollectionBuilder.TPairs для цепочки методов создания пар в json-объекте. После того, как мы добавили все необходимые пары в json, мы закончили его создание, вызвав метод EndObject.

TJSONCollectionBuilder.TPairs предоставляет группу методов для добавления пар в json-объект. Вот только некоторые из них:

      function Add(const AKey: string; const AValue: string): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Int32): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: UInt32): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Int64): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: UInt64): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Single): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Double): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Extended): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Boolean): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Char): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Byte): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TDateTime): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TGUID): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TBytes;
        ABinaryType: TJsonBinaryType = TJsonBinaryType.Generic): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TJsonOid): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TJsonRegEx): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TJsonDBRef): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TJsonCodeWScope): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TValue): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: TVarRec): TPairs; overload; inline;
      function Add(const AKey: string; const AValue: Variant): TPairs; overload; inline;
      function AddNull(const AKey: string): TPairs; inline;
      function AddUndefined(const AKey: string): TPairs; inline;
      function AddMinKey(const AKey: string): TPairs; inline;
      function AddMaxKey(const AKey: string): TPairs; inline;

Используя методы TJSONCollectionBuilder.TPairs  можно добавлять пары с практически любыми значениями — строками, числами, символами и так далее. Для того, чтобы продемонстрировать работу некоторых из представленных выше методов, добавим в наш json-объект ещё несколько пар. Делается это следующим образом:

    Builder
      .BeginObject
         .Add('Привет', 'мир!')
         .AddMinKey('MinKey')
         .AddMaxKey('MaxKey')
         .AddNull('NullPair')
         .AddUndefined('UndefinedPair')
      .EndObject;

В результате будет сформирован следующий JSON:

{
    "Привет": "мир!",
    "MinKey": MinKey,
    "MaxKey": MaxKey,
    "NullPair": null,
    "UndefinedPair": undefined
}

В методах AddMinKey, AddMaxKey, AddNull и AddUndefined мы указываем только имя пары. Перейдем к следующему примеру — созданию массивов и вложенных json-объектов в delphi.

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

Напомню, что в статье про «JSON Objects Framework» мы создавали вот такой json:

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

При использовании «Readers and Writers JSON Framework» такой json создается аналогично предыдущему примеру, то есть последовательно выполняя цепочку методов Begin***, Add*** и End***:

    Builder
      .BeginObject
         .Add('kind', 'tasks#task')
         .Add('title','Новая задача')
         .BeginArray('links')
//каждый элемент массива - это объект, поэтому снова вызываем BeginObject
            .BeginObject  
              .Add('type','href')
              .Add('description','desc')
              .Add('link','http://webdelphi.ru')
            .EndObject //заканчиваем создание объекта
            .BeginObject
              .Add('type','href2')
              .Add('description','desc')
              .Add('link','http://webdelphi.ru')
            .EndObject //заканчиваем создание объекта 
         .EndArray //заканчиваем создание массива json
      .EndObject; //заканчиваем создание json

Соответственно, если вам необходимо создать вложенный json-объект в delphi, то алгоритм создания json будет такой — вначале вызываем BeginObject у TJSONObjectBuilder, а, затем, BeginObject(Key: string) у TPairs, например вот такой код:

    Builder
      .BeginObject
        .BeginObject('Nested')
           .Add('Value',123)
           .Add('Value2', True)
        .EndObject
      .EndObject;

Создает json с вложенным объектом ‘Nested’:

{
    "Nested": {
        "Value": 123,
        "Value2": true
    }
}

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

При создании экземпляра TJsonTextWriter мы указали в его свойствах, что дочерние объекты будут иметь отступ. При этом, по умолчанию каждый дочерний объект будет иметь отступ в 4 символа. Используя свойства TJsonTextWriter мы можем указать другие условия форматирования, например:

Writer := TJsonTextWriter.Create(StringWriter);
  Writer.Formatting := TJsonFormatting.Indented; //делать отступы при форматировании
  Writer.Indentation:=10; //десять отступов
  Writer.IndentChar:='0'; //символ отступа - 0

В этом случае Json окажется вот таким:

{
0000000000"Nested": {
00000000000000000000"Value": 123,
00000000000000000000"Value2": true
0000000000}
}

Так как для создания JSON с использованием «Readers and Writers JSON Framework» мы создаем целую цепочку экземпляров классов, то для того, чтобы представить созданный JSON в виде строки, я использую метод ToString у экземпляра класса TStringBuilder:

 StringBuilder := TStringBuilder.Create;
 StringWriter := TStringWriter.Create(StringBuilder);
 Writer := TJsonTextWriter.Create(StringWriter);
 Builder := TJSONObjectBuilder.Create(Writer);
  try
    Builder
      .BeginObject
        .BeginObject('Nested')
           .Add('Value',123)
           .Add('Value2', True)
        .EndObject
      .EndObject;
    //получаем строку, содержащую отформатированный json
    StringBuilder.ToString

Парсинг JSON в Delphi

Как было указано выше, для чтения json в delphi с использованием «Readers and Writers JSON Framework» мы должны использовать экземпляр класса TJSONIterator. Прочитаем, например, вот такой JSON:

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

Используя рассматриваемую библиотеку работы с json в delphi, мы можем:

  1. рекурсивно перечислить все пары объекта;
  2. найти необходимые элемент объекта и получить только его значение;
  3. вернуться при обходе на уровень вверх, пропустить какой-либо элемент json;
  4. и так далее.

Рассмотрим эти возможности библиотеки по порядку, начиная с создания экземпляра TJSONIterator.

Как создать экземпляр TJSONIterator и использовать его для парсинга json в delphi?

Экземпляр TJSONIterator создается аналогично TJSONObjectBuilder:

var Json: TJSONIterator;
    Reader: TJsonTextReader;
    TextReader: TStringReader;
begin
  TextReader:=TStringReader.Create(cJsonStr);
  Reader:=TJsonTextReader.Create(TextReader);
  Json:=TJSONIterator.Create(Reader);

Таким образом мы при создании экземпляра класса TStringReader сразу указываем строку, содержащую Json и после создания TJSONIterator можем сразу приступать к чтению данных.

Как рекурсивно пройтись по всем парам объекта json в delphi?

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

var Json: TJSONIterator;
    ...
begin
  ...
  Json:=TJSONIterator.Create(Reader);
  try
    Json.Iterate(
                  function (AIter: TJSONIterator): Boolean
                  begin
                    ShowMessage(AIter.Key+': '+AIter.AsString);
                    Result:=True;
                  end
                );

Разберемся, что здесь было сделано.
Метод TJSONIterator.Iterate имеет следующее описание в Delphi

procedure Iterate(AFunc: TIterateFunc);

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

Метод Iterate работает следующим образом:

  1. Если AFunc возвращает True, то метод продолжает перебирать элементы JSON.
  2. Если AFunc возвращает False, метод немедленно останавливает процесс итерации.

Внутри анонимного метода мы использовали следующий код:

ShowMessage(AIter.Key+': '+AIter.AsString);

Соответственно,

  1. AIter — это ссылка на итератор.
  2. AIter.Key — это ключ (имя) текущей пары
  3. AIter.AsString — одно из свойств AIter, представляющее значение пары в виде строки.

В нашем случае функция, переданная в параметрах Iterate всегда возвращает True. В результате, по ходу выполнения Iterate можно было увидеть следующие строки:

firstName: Петров
lastName: Пётр
streetAddress: ул. Дельфийская, 101, кв.101
city: Дельфийск
postalCode: 100301
0: 812 123-1234
1: 916 123-4567

Обратите внимание, что для каждого элемента массива TJSONIterator также вернул порядковый номер элемента в качестве ключа.

Таким образом, используя метод TJSONIterator.Iterate мы получили так называемый «плоский» список всех пар и их значений json-объекта.

Следующий момент работы с  «Readers and Writers JSON Framework» — как определить тип значения очередной пары в json и, исходя из этого, выстраивать логику работы приложения?

Работа с методами TJSONIterator Next, Recurse и Return

Помимо метода Iterate у TJSONIterator определены также следующие методы:

function Next(const AKey: String = ''): Boolean;

Пересылает итератор к следующему элементу JSON в текущем массиве или объекте. Если указан AKey, то Next найдет пару с указанным именем и вернет True, если пара будет найдена.

Если AKey не указан, то Next будет переходить к следующему элементу json.

Основное отличие метода Next от Iterate заключается в том, что Next не заходит внутрь вложенных json-объектов и массивов. Для того, чтобы войти внутрь вложенного объекта или массива используется метод Recurse:

function Recurse: Boolean;

Recurse заходит внутрь вложенного объекта json или массива и устанавливает позицию итератора перед первым элементом. Если удалось зайти внутрь вложенного об json-объекта, то функция вернет True.
После того, как с помощью Recurse/Next пройдены все необходимые элементы объекта или массива json итератор необходимо вернуть на уровень вверх, для этого используется метод Return:

procedure Return;

Теперь, для того, чтобы начать писать код Delphi для разбора json необходимо научиться определять — когда необходимо вызывать методы Recurce и Return.

Когда итератор встает на очередную позицию в json мы можем определить следующие значения:

  1. Тип текущего элемента json (свойство &Type)
  2. Тип родительского элемента json (свойство ParentType)
  3. Текущую глубину на которой находится итератор (свойство Depth)
  4. Индекс текущего элемента (свойство Index)
  5. Путь до текущего элемента (свойство Path)
  6. Определить имеется ли у текущего элемента родительский элемент json (свойство IsRecurse)

Свойства &Type и ParentType имеют следующее описание:

property &Type: TJsonToken read FType;
property ParentType: TJsonToken read GetParentType;
 
type
TJsonToken = (
    None,
    StartObject,
    StartArray,
    StartConstructor,
    PropertyName,
    Comment,
    Raw,
    Integer,
    Float,
    &String,
    Boolean,
    Null,
    Undefined,
    EndObject,
    EndArray,
    EndConstructor,
    Date,
    Bytes,
    // Only in Extended JSON, BSON
    Oid,
    RegEx,
    DBRef,
    CodeWScope,
    MinKey,
    MaxKey
  );

По значению свойства &Type мы можем определить какой токен перед нами находится, например:

  • StartObject — начало вложенного объекта (символ ‘{‘)
  • StartArray — начало массива (символ ‘[‘)
  • EndObject — окончание вложенного объекта (символ ‘}’)
  • EndArray — окончание массива (символ ‘]’)

и, уже исходя из полученного значения выстраивать логику приложения — вызывать Recurse или, наоборот, возвращаться на уровень вверх, вызывая метод Return и так далее.

Попробуем воспользоваться рассмотренными методами и свойствами TJSONIterator, чтобы получить все значения json-объекта, рассмотренного в предыдущей части статьи. Рассмотрим вот такой код:

  cLogString = 'Depth: %d, Key: %s; Path: %s; Value: %s';
 
  procedure NestedJson(JSON: TJSONIterator);
  begin
    JSON.Recurse;
    while JSON.Next do
      Memo1.Lines.Add(Format(cLogString, [JSON.Depth,
        JSON.Key,
        JSON.Path,
        JSON.AsString]));
    JSON.Return;
  end;
 
var
  JSON: TJSONIterator;
  Reader: TJsonTextReader;
  TextReader: TStringReader;
begin
...
  JSON := TJSONIterator.Create(Reader);
  try
    while JSON.Next do
    begin
      case JSON.&Type of
        TJsonToken.StartObject:
          begin
            Memo1.Lines.Add('Вложенный объект ' + JSON.Key);
            NestedJson(JSON);
          end;
        TJsonToken.StartArray:
          begin
            Memo1.Lines.Add('Вложенный массив ' + JSON.Key);
            NestedJson(JSON);
          end;
      else
        Memo1.Lines.Add(Format(cLogString, [JSON.Depth,
          JSON.Key,
          JSON.Path,
          JSON.AsString]));
      end;
    end;
...

Здесь мы в цикле проходимся по каждой паре в Json, проверяя тип текущего токена (&Type). В зависимости от полученного типа токена в Memo выводятся соответствующие строки: «вложенный объект» или «вложенный массив» с указание ключа. Если мы находим вложенный объект или массив, то вызывается метод NestedJson который перебирает пары вложенного объекта или элементы массива, выводя в Memo текущую глубину, ключ, путь и строковое значение текущего элемента. Конечно, этот код совсем не универсальный и представлен исключительно в виде примера того, как разобрать конкретный json и получить из него все значения. В результате в Memo появятся следующие строки:

Depth: 1, Key: firstName; Path: firstName; Value: Петров
Depth: 1, Key: lastName; Path: lastName; Value: Пётр
Вложенный объект address
Depth: 2, Key: streetAddress; Path: address.streetAddress; Value: ул. Дельфийская, 101, кв.101
Depth: 2, Key: city; Path: address.city; Value: Дельфийск
Depth: 2, Key: postalCode; Path: address.postalCode; Value: 100301
Вложенный массив phoneNumbers
Depth: 2, Key: 0; Path: phoneNumbers[0]; Value: 812 123-1234
Depth: 2, Key: 1; Path: phoneNumbers[1]; Value: 916 123-4567

Остается только добавить, что после того, как весь json пройден, то итератор не сбрасывает свою позицию снова на начало, поэтому, если вам необходимо вернуться на начало json, то необходимо вызвать метод итератора Rewind:

procedure Rewind;

Осталось проверить производительность библиотеки и рассмотреть пример парсинга большого json.

Производительность «Readers and Writers JSON Framework«

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

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

Код первого теста:

var
  Builder: TJSONObjectBuilder;
  Writer: TJsonTextWriter;
  StringWriter: TStringWriter;
  I: Integer;
  t: TStopwatch;
begin
  t:= TStopwatch.StartNew;
 
  StringWriter := TStringWriter.Create;
  Writer := TJsonTextWriter.Create(StringWriter);
  Builder := TJSONObjectBuilder.Create(Writer);
 
  try
    with Builder.BeginObject do
      begin
        for I := 1 to 500000 do
          Add('Pair', 'Value');
        EndObject;
      end;
 
  finally
    FreeAndNil(Builder);
    FreeAndNil(Writer);
    FreeAndNil(StringWriter);
  end;
  t.Stop;
  ShowMessage(t.ElapsedMilliseconds.ToString);
end;

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

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

Код второго теста:

var
  Builder: TJSONObjectBuilder;
  Writer: TJsonTextWriter;
  StringWriter: TStringWriter;
  t: TStopwatch;
begin
  t := TStopwatch.StartNew;
 
  StringWriter := TStringWriter.Create;
  Writer := TJsonTextWriter.Create(StringWriter);
  Builder := TJSONObjectBuilder.Create(Writer);
 
  try
    with Builder.BeginObject do
    begin
      for var I := 1 to 10 do
      begin
        with BeginArray('Array') do
        begin
          for var J := 1 to 500000 do
            Add('TestValue');
          EndArray;
        end;
      end;
      EndObject;
    end;
  finally
    FreeAndNil(Builder);
    FreeAndNil(Writer);
    FreeAndNil(StringWriter);
  end;
  t.Stop;
  ShowMessage(t.ElapsedMilliseconds.ToString);
end;

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

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

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

Код первого теста (для того, чтобы результаты можно было сравнить с предыдущими тестами чтение строки вынесено за пределы счётчика времени):

var
  JSON: TJSONIterator;
  Reader: TJsonTextReader;
  TextReader: TStringReader;
  t: TStopwatch;
  S: TStringStream;
  Bytes: string;
begin
  S := TStringStream.Create('', TEncoding.UTF8);
  try
    S.LoadFromFile('Simple.txt');
    Bytes := S.DataString;
  finally
    FreeAndNil(S);
  end;
 
  t:=TStopwatch.StartNew;
  TextReader := TStringReader.Create(Bytes);
  Reader := TJsonTextReader.Create(TextReader);
  JSON := TJSONIterator.Create(Reader);
  try
    JSON.Iterate(function (AIter: TJSONIterator):boolean
                 begin
                   Result:=True;
                 end);
  finally
    FreeAndNil(JSON);
    FreeAndNil(Reader);
    FreeAndNil(TextReader);
  end;
  t.Stop;
  Memo1.Lines.Add(t.ElapsedMilliseconds.ToString)
end;

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

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

Второй тест (код тот же):

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

Сравнение результатов работы «Readers and Writers JSON Framework» и “JSON Objects Framework”

Сравнение производительности двух библиотек представлено на рисунке ниже:

Казалось бы, что «Readers and Writers JSON Framework» сильно проигрывает в работе “JSON Objects Framework”, однако у этой библиотеки есть, как минимум, одно преимущество: «Readers and Writers JSON Framework» экономит память. А это немаловажно, когда дело касается обработки больших объемов данных JSON. Чтобы продемонстрировать преимущество «Readers and Writers JSON Framework» я вернусь к тому файлу (на 544 Мб) о котором говорил в прошлый раз и на который дал ссылку.

Когда «Readers and Writers JSON Framework» необходим

Итак, файл содержит 544 Мб открытой информации по экологии. Кому интересно узнать сколько всяких гадостей выбрасывается в воздух вашего родного города — самое время разобрать этот JSON :) Файл в кодировке UTF-8 с BOM.

Структура JSON достаточно простая, но есть в нем и вложенные объекты и массивы, содержащие json-объекты. Кстати, JSON начинается сразу с символа ‘[‘, то есть внутри не объект с массивом json, а массив json с кучей вложенных объектов и вложенных же в эти объекты массивами. Но не в этом, собственно, суть. Суть заключается в том, чтобы пройтись по этому JSON и прочитать все значения.

Код для JSON Objects Framework

Чтобы распарсить json из файла я обычно использую вот такой код:

uses System.JSON, 
     System.IOUtils;
 
var JSON: TJSONValue;
begin
  JSON:=TJSONObject.ParseJSONValue(TFile.ReadAllBytes('open-data.json'),
                                   0,
                                   True);

Здесь в метод ParseJSONValue передается массив байтов. Теперь, если запустить приложение под Win32 и попробовать распарсить файл, представленный выше, то через некоторое время программа выдаст «Out of memory». В диспетчере задач при этом будет вот такая картина:

Если пересобрать приложение под Win64, то, по крайней мере, мой комп справился и парсинг прошел успешно, но какой ценой:

JSON Objects Framework создает кучу объектов для хранения данных json и, соответственно, «ест» память. Другое дело — Readers and Writers JSON Framework. 

Код для Readers and Writers JSON Framework

Чтобы пробежаться по всему json я воспользовался методом Iterate и загрузил данные сразу из файла, используя вот такой код:

var
  JSON: TJSONIterator;
  Reader: TJsonTextReader;
  TextReader: TStreamReader;
  t: TStopwatch;
begin
  t:=TStopwatch.StartNew;
  TextReader := TStreamReader.Create('open-data.json', TEncoding.UTF8, True);
  Reader := TJsonTextReader.Create(TextReader);
  JSON := TJSONIterator.Create(Reader);
  try
 
    JSON.Iterate(function (AIter: TJSONIterator):boolean
                 begin
                   Result:=True;
                 end);
 
  finally
    FreeAndNil(JSON);
    FreeAndNil(Reader);
    FreeAndNil(TextReader);
  end;
  t.Stop;
  Memo1.Lines.Add(t.ElapsedMilliseconds.ToString)
end;

Результат работы приложения в Диспетчере Задач при работе метода Iterate:


После того, как Iterate окончил работу нагрузка на ЦП, соответственно, упала до нуля, потребление памяти осталось на том же уровне. На прохождение по всему файлу у «Readers and Writers JSON Framework» ушло чуть больше 18 секунд. 

Вот такой получился не большой, но, на мой взгляд, показательный пример, когда «Readers and Writers JSON Framework» в delphi явно необходим при работе с JSON. Ну и, само собой, не следует забывать, что только эта «родная» библиотека в delphi умеет работать с BSON.

Выводы 

По моим ощущениям, работать с «Readers and Writers JSON Framework» немногим сложнее, чем с «JSON Objects Framework«. Возможно, что это первое ощущение после того, как долгое время, практически с момента выхода Delphi 2010 я использовал исключительно «JSON Objects Framework», привык ней и теперь относительно новая для меня библиотека выглядит немного сложноватой в плане работы с ней. Однако, использование «Readers and Writers JSON Framework» имеет свои очевидные плюсы, касающиеся экономии памяти при работе с большими JSON и, конечно же, работа с BSON о котором я постараюсь также написать отдельную статью.    

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

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

Подскажите, как прочитать массив array в цикле?

     if Iterator.&Type = TJsonToken.StartArray then
       if Iterator.Recurse then
       begin
         // "ID":[0,55,true,false]
         while Iterator.Next do
         begin
           sName := Iterator.Key;


         end;
         Iterator.Return;
akella225
akella225
25/11/2020 18:14

В случае с массивом здесь будет ошибка, на строке JSON.AsString, нужно использовать AsValue.AsString

if Iterator.AsValue.IsType<String> then
sMyValue := Iterator.AsValue.AsString
procedure NestedJson(JSON: TJSONIterator);
  begin
    JSON.Recurse;
    while JSON.Next do
      Memo1.Lines.Add(Format(cLogString, [JSON.Depth,
        JSON.Key,
        JSON.Path,
        JSON.AsString]));
    JSON.Return;
  end;
akella225
akella225
25/11/2020 18:15

Еще непонятно, как получать количество объектов и/или количество элементов массива.

akella225
akella225
29/11/2020 18:11

Обратите внимание, что один и тот же код даст разные результаты в Delphi 10.3.3 и в Delphi 10.4.1

procedure TForm1.btnReadArrayClick(Sender: TObject);
const sJSon = '{"ID":[0,55,true,false], "ID2":[1,60,false,false]}';

var
 Iterator: TJSONIterator;
 Reader: TJsonTextReader;
 TextReader: TStringReader;
begin

  TextReader  := TStringReader.Create(sJSon);
  Reader      := TJsonTextReader.Create(TextReader);
  Iterator    := TJSONIterator.Create(Reader);

  try
    while Iterator.Next do
    begin
      if Iterator.&Type = TJsonToken.StartArray then
        if Iterator.Recurse then
          while Iterator.Next do
          begin
            Memo1.Lines.Add('Key: ' + Iterator.Key);
            Memo1.Lines.Add('Index:' + Iterator.Index.ToString);
          end;

    end;//while

  finally
    FreeAndNil(TextReader);
    FreeAndNil(Iterator);
    FreeAndNil(Reader);
  end;
end;

снимок экрана http://prntscr.com/vs77nu

Сергей Плахов
Сергей Плахов
11/02/2021 22:30

Непонятно как в этом фреймворке “Readers and Writers JSON Framework” формировать один json вкладывая в него другой в виде массива, при условии что второй формируется в другом классе и может быть получен в виде строки.Есть класс словарь TGlossary = class(TObjectList), есть словарная статья TGlEntry. TGlossary содержит, как наследник TObjectList, словарные статьи TGlEntry в виде объектов. У словарной статьи есть метод function TGlEntry.GetEntryJSON: TStringBuilder;Что я там в цикле должен вставить после того как получил StringBuilder от статьи? Текст процедуры ниже. Задача: в методе procedure TGlossary.SaveToJSON; запихнуть все в общий JSON, примерно такого вида: {   «DateCreation»: «28.01.2021 12:26:03:173»,   «Words»: [     {       «Wrd»: «аба»,       «Sample»: 1,… Подробнее »

Сергей Плахов
Сергей Плахов
12/02/2021 17:10

В этой технологии «Readers and Writers JSON Framework» не совсем понятно что является собственно JSON объектом. Допустим есть два класса словарь TGlossary = class(TObjectList) и статья TGlEntry = class(TComponent). Словарь собственно и содержит статьи как свои объекты. У словаря есть метод procedure SaveToJSON(const AFileName: String); который в цикле сохраняет статьи в формате JSON. У статьи есть метод function TGlEntry.GetEntryJSON: TStringBuilder; который выдает свойства статьи как JSON строку     {       «Wrd»: «аба»,       «Sample»: 1,       «Gender»: 2,       «Soul»: false,       «DeclType»: 1,       «Plural»: false,       «Suffix»: «»,       «Accent»: true,       «RunVocal»: false,       «Circle»: false,       «Ring1»: false,       «Ring2»: false,       «Note»: «»     } в SaveToJSON хочу получить некий JSON который содержит… Подробнее »