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

Эта статья небольшое продолжение сразу трех статей: «Delphi XE2: Hello, Mac OS!» в которой рассмотрен простенький пример создания приложения а-ля «Hello, World!» под MacOS, «Работа с JSON в Delphi 2010 — XE2.» с информацией по классам для работы с JSON в Delphi и последней статьи, касающейся работы с Google API — «Использование JSON при работе с Google API.«. В общем, сегодня попытаемся распарсить под MacOS JSON-объект, содержащий информацию по контактам GMail и, в дополнение, посмотреть как туже самую операцию парсинга можно осуществить под Windows, используя не только «родные» классы Delphi XE2 для работы с JSON, но и такую замечательную библиотеку как SuperObject.

Прежде, чем рассмотрим основную тему статьи, я рассмотрю один момент, с которым, думаю, кто-то мог столкнуться при настройке соединения между Windows и MacOS, а именно — разного рода возникающие ошибки типа «SocketError #» и то, как этих ошибок можно избежать.

PAServer: ошибки «SocketError #»

В последнее время я практически не заглядывал в свою виртуалку с MacOS. А сегодня решил написать пост, запустил виртуальную машину и при попытке собрать проект под Mac получил сообщение «SocketError #10060» затем, при неизменных настройках, вдруг вылетела ошибка «SocketError #11004«. Пошел простым логическим путем: PAServer после последнего запуска никаким образом не трогал, настроек не менял, паролей не  устанавливал. Следовательно PAServer просто так взять и «упасть» не мог, тем более, выдавая ошибку сокета.  Значит произошла какая-то непонятная фигня с настройками сети. Вначале проверил настройку сети в виртуалке VMware. Настройки были и остались такими:

В виртуалке веб-браузер работает нормально — сайты открываются, файлы грузятся. Следовательно проблема заключается в настройках под Windows. Зашел в настройки Сети и увидел следующую картинку:

Где-то с месяц назад менял настройки Сети и видимо отключил это соединение за ненадобностью, а включить забыл. После того как адаптер виртуалки был включен заново PAServer как и раньше заработал без каких-либо проблем. Так что, если у вас при настройке PAServer’а возникают ошибки как у меня  «SocketError #10060«, «SocketError #11004» и т.д., то в первую очередь смотрите настройки сетевых соединений.

Теперь перейдем непосредственно к теме статьи — парсингу JSON в MacOS и Windows.

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

Сразу видно где какие объекты и массивы расположены в JSON и как до них добраться. Теперь перейдем непосредственно к парсингу.

Парсинг  JSON в Delphi под Windows

Итак, вид JSON-объекта, содержащего данные по контактам представлен на рисунке ниже. Для примера, попробуем прочитать title контакта. Судя по составу JSON-объекта от нас требуется:

  1. Получить объект feed
  2. В feed добраться до массива entry
  3. Пройти по каждому элементу в entry и получить объект title
  4. Получить из объекта title пару $t
  5. Прочитать значение пары $t

Для реализации этого простенького алгоритма вначале воспользуемся «родной» библиотекой Delphi XE2, которая содержится в модуле Data.DBXJSON.pas. Ниже представлен один из возможных вариантов парсинга JSON с помощью DBXJSON:

procedure TForm3.Button3Click(Sender: TObject);
var
  JsonObject, GFeed, GContact, GTitle: TJSONObject;
  JsonStream: TStringStream;
  JsonArray: TJSONArray;
  i: integer;
  Pairs: TJSONPairEnumerator;
begin
  JsonStream := TStringStream.Create;
  try
    JsonStream.LoadFromFile(edFile.Text); //загружаем данные из вайла
    //создаем объект TJSONObject
    JsonObject := TJSONObject.ParseJSONValue(JsonStream.DataString) as TJSONObject;
    if Assigned(JsonObject) then
    begin
      //получаем перечислитель всех пар в объекте
      Pairs := JsonObject.GetEnumerator;
      try
        while Pairs.MoveNext do //проходим по парам в цикле
        begin
          //ищем пару feed
          if Pairs.Current.JsonString.Value = 'feed' then
          begin
            GFeed := Pairs.Current.JsonValue as TJSONObject;
            //получаем массив entry
            JsonArray := GFeed.Get('entry').JsonValue as TJSONArray;
            if Assigned(JsonArray) then
            begin
              //проходим по каждому элементу массива
              for i := 0 to JsonArray.Size - 1 do
              begin
                //получаем объект контакта
                GContact := JsonArray.Get(i) as TJSONObject;
                //получаем объект title
                GTitle := GContact.Get('title').JsonValue as TJSONObject;
                //читаем значение пары $t
                if Assigned(GTitle) then
                  Memo1.Lines.Add(GTitle.Get('$t').JsonValue.Value)
              end;
            end;
            break;
          end;
        end;
      finally
        Pairs.Free;
      end;
      JsonObject.Free;
    end;
  finally
    JsonStream.Free;
  end;
end;

Процедура расписана как можно более полно, вплоть до создания отдельных переменных под каждый объект. Поэтому, как говориться, комментарии излишни :). Остается только в который уже раз (наверное сотый) напомнить про строки, а именно, указать вот на эту строку в коде:

Memo1.Lines.Add(GTitle.Get('$t').JsonValue.Value)

И сказать, что значение Value довольно часто может быть в UTF-8, а в Google API так вообще всегда в UTF-8, поэтому при разработке под Windows, для преобразования строк следует использовать один из способов, предложенных, например, в статье «3 варианта работы с кодировками веб-страниц в Delphi.» и писать, например, так:

Memo1.Lines.Add(UTF8ToAnsi(GTitle.Get('$t').JsonValue.Value))

Вот, пожалуй, основная «тонкость» в этом коде. Теперь посмотрим аналогичный парсинг, но уже с использованием библиотеки SuperObject. Вот как выглядит процедура парсинга названий контактов GMail с использованием SuperObject:

procedure TForm3.Button2Click(Sender: TObject);
var
  JsonObject, GContact: ISuperObject;
  JsonStream: TStringStream;
  JsonArray: TSuperArray;
  i: integer;
begin
  JsonStream := TStringStream.Create;
  try
    //загружаем файл
    JsonStream.LoadFromFile(edFile.Text);
    //создаем объект
    JsonObject := SO(JsonStream.DataString);
    //получаем массив entry
    JsonArray := JsonObject.A['feed.entry'];
    if Assigned(JsonArray) then
      //читаем названия контактов из title
      for i := 0 to JsonArray.Length - 1 do
      begin
        GContact := JsonArray.O[i];
        Memo1.Lines.Add(Utf8ToAnsi(GContact.S['title.$t']));
      end;
  finally
    JsonStream.Free;
  end;
end;

Ощущаете разницу? :) Очевидным и неоспоримым преимуществом SuperObject является работа с путями. Как видите в коде выше мы всего одной строкой кода добрались до массива entry и потом также просто прочитали значение пары $t из объекта title. В результате код получился…более изящным что  ли? Но, как оказалось, и SuperObject не лишен некоторых недостатков.

Парсинг  JSON в Delphi под MacOS

С точки зрения написания кода, что под Windows, что под MacOS практически ничего не изменяется, разве, что не требуется преобразование строк из UTF-8. То есть всё та же «проблемная строка» кода вновь становится такой:

  Memo1.Lines.Add(GTitle.Get('$t').JsonValue.Value)

А вот по части использования библиотек для парсинга JSON под MacOS — тут пока SuperObject не применить. К сожалению, попытка собрать проект под Mac, содержащий SuperObject не увенчалась успехом. Виной тому, используемый в библиотеке модуль Windows.pas.

Вполне возможно, что  в будущем разработчики SuperObject доработают библиотеку и она станет полностью кроссплатформенной, но пока остается либо ждать, либо самим «допиливать» библиотеку, или — использовать уже готовую к работе под MacOS «родную» DBXJson — с ней программка под MacOS «взлетела» вообще без проблем.

Остается только добавить, что в настоящее время я пишу свою часть проекта с использованием как раз DBXJSON и не испытываю никакого особенного дискомфорта по этому поводу.  Да, после SuperObject было немного не привычно и неудобно «выковыривать» данные из JSON-объектов, но со временем привык и, теперь работа идёт без каких-либо проволочек и проблем. А SuperObject — это ИМХО самая удобная библиотека для парсинга JSON в Delphi. Жаль только, что пока не кроссплатформенная :)

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

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

Я может чего-то не понимаю, но чем конструкция 
//получаем перечислитель всех пар в объекте
Pairs := JsonObject.GetEnumerator;
try
while Pairs.MoveNext do //проходим по парам в цикле
begin
//ищем пару feed
if Pairs.Current.JsonString.Value = ‘feed’ then
begin
GFeed := Pairs.Current.JsonValue as TJSONObject;
лучше чем
for GFeed in  JsonObject do

Сергей Румянцев

Влад, спасибо, очень познавательный код получился, теперь я понял логику этих пар в DBXJSON и впервые услышал об относительных путях в SuperObject, раньше использовал пути типа  objJSON.O[‘имя объекта’].S[‘имя строки’] и т.д. Если брать основные отличия, то DBXJSON предоставляет интерфейс ручного разбора JSON объекта, один раз разбираешь и сразу все данные нужно уложить в свой класс данных, чтоб процедуру перебора не повторять каждый раз. В свою очередь SuperObject эмулирует работу с данными как в JS, где все данные один раз перебираются и укладываются для дальнейшей работы в структурированный объект. То есть SuperObject на одну ступень выше DBXJSON в плане ООП. Если… Подробнее »

Георгий
Георгий
11/04/2012 03:05

Классный пост.

Есть ещё один важный плюс у SO, помимо xPath, к примеру:

  obj := so('{"index": 1, "items": ["item 1", "item 2", "item 3"]}');
  obj['items[index]'] // return "item 2"

Ни obj := T....Create; , ни try .. finally obj.Free; end; Писать не надо

Сергей Румянцев

Капризный DBXJSON однако =)Сегодня переписал свой парсер с SuperObject на DBXJSON. Из плюсов, программа стала кушать меньше оперативной памяти в 2 раза было 60Мб, теперь 30Мб. Из минусов, не могу никак предотвратить утечку памяти %) У меня при парсинге используется порядка 26 переменных типа TJSONObject раскладывается один большой объект на много мелких и из них уже идет наполнение базы данных. Все это работает в отдельном потоке. При стандартном вызове Free часть переменных освобождают память, но на некоторых вылетает поток и программа стопорится. Пробовал FreeInstance, то же самое. try .. finally obj.Free; end; Пробовал, тоже не помогает и вылетает поток. Может… Подробнее »

Сергей Румянцев

А теперь впечатления, DBXJSON работает намного быстрее SuperObject особенно если вы разбираете большие объекты со множеством вложенных объектов. По крайней мере, то что у меня делал SO 10 секунд, то DBXJSON делает за 1 секунду. Скорей всего это связанно с тем, что при работе с DBXJSON я не использую xPath. А теперь о моей проблеме с утечкой памяти в DBXJSON, при стандартных способах добавления пар, создается ссылка на область памяти той переменной, которую вы указываете в пару, так что возникают ошибки при работе процедуры Free. Обойти это оказалось просто: 1) При добавлении новой пары: obj1.AddPair(‘obj2’, TJSONObject(obj2.Clone)); 2) При извлечении объекта:… Подробнее »

Сергей Румянцев

При тестировании выявил ещё одно отличие SO от DBXJSON.

SO.AsJson — выдает строку сразу с закодированными символами и её можно смело отправлять на сервервер.
DBXJSON.ToString — выдает строку без кодирования символов и их необходимо закодировать, только вот вопрос как? =)

Пока в поисках алгоритма кодирования, а то с сервера могу получить данные, а отправить ему нет =(.

Ежик
16/10/2012 12:25

А можно ли использовать DBXJSON в Delphi 7, если можно, то где скачать библиотеки?

trackback

[…] Библиотека для работы с JSON […]

Георгий
Георгий
01/10/2013 23:21

https://code.google.com/p/x-superobject/

Support ( WinX, OS X, Mobile)