(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Пример использования перечислителей в SuperObject.

Источник: webdelphi
webdelphi

Уже давненько собирался подробнее разобраться в работе с SuperObject, да все как-то не было подходящего момента - в последнее время использую сугубо родной DBXJSON, т.к. его возможностей с лихвой хватает для решения моих задач, а чисто ради спортивного интересу вникать во что-то новое сейчас особенного желания нет. Исключение - конкурс по FireMonkey...даже не так - FireMonkey изучается совсем не ради спортивного интереса :). Ну так вот, что касается SuperObject и почему сегодня речь пойдет про эту библиотеку. Где-то в районе майских праздников пришло ко мне на почту письмо с просьбой помочь немного разобраться с одним JSON-объектом, а именно - распарсить его с использованием SuperObject и вывести кое-какие данные из этого объекта в программу.  Вот я и решил немного помочь с этой задачкой, т.к. это уже не просто "спортивный интерес" - вдруг да пригодится кому мое решение, если не в 1 в 1 скопированное, то хотя бы какие-то моменты из него, да и, думаю, примерчик окажется полезным для тех, кто решит вникнуть в работу с SuperObject.

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

Одно из решений задачи смотрим ниже.

Во-первых, что из себя представляет наш JSON-объект? Его внешний вид представлен на рисунке ниже:

Проанализируем немного этот объект:

  1. Каждый элемент нашего JSON-объекта представляет из себя массив. Имя каждого элемента - какое-либо число.
  2. В каждом массиве имеется ровно пять элементов, при этом:
    1. элемент с индексом 1 - это название области/края/республики. Это значение нам надо использовать в программе.
    2. элемент с индексом 5 - это JSON-объект, содержащий список населенных пунктов. При этом:
      1. каждый элемент этого объекта, как и в случае с основным json-объектом - это массив, в котором:
        1. элемент с индексом 0 - название города. Это значение используется в программе
        2. элемент с индексом 1 - URL города. Это значение также используется в программе.

Остальная информация нам в программе не пригодится. Теперь посмотрим как можно разобрать такой JSON-объект в программе. Сразу оговорюсь, ниже представлено решение при котором JSON постоянно храниться в "мозгах" и мы к нему активно обращаемся при работе программы. Это, может, не совсем эффективно, но зато в этом случае мы можем посмотреть на то как используются различные классы SuperObject, а на это, собственно, и рассчитывалась статья - показать максимум возможностей в SuperObject, при решении конкретной задачи. Потом, если у кого-то возникнет желание, можете оптимизировать код сколько душе угодно - исходники, как обычно, будут вас ждать в конце поста и на странице с одноименным названием. Итак, начнем с того, что создадим новый проект VCL Application и разместим на форме два ComboBox'а, несколько Label'ов, 1 Button, 1 OpenDialog  и 1 TEdit как показано на рисунке ниже:

Главная форма программы

Да, JSON будет выгружаться сегодня из простого текстового файла. Да и, собственно, какая разница откуда его грузить? Теперь снова посмотрим на первый рисунок с видом JSON-объекта и напишем небольшой класс, который будет возвращать нам всю необходимую информацию. Назовем его TRegions. И первое, что мы сделаем - это напишем конструктор/деструктор и получим список всех районов:

type
  TRegions = class
  private
    FJSONObject: ISuperObject;
    FAvlEnum:  TSuperAvlIterator;
  public
    constructor Create(const AJsonString: string);
    destructor Destroy; override;
  function GetRegions(List: TStrings):integer;
end;  
 
implementation
 
constructor TRegions.Create(const AJsonString: string);
begin
  inherited Create;
  FJSONObject:=TSuperObject.ParseString(PChar(AJsonString),false);
  if not Assigned(FJSONObject) then
    raise Exception.Create('Невозможно распарсить JSON')
  else
    FAvlEnum:=FJSONObject.AsObject.GetEnumerator;
end;
 
destructor TRegions.Destroy;
begin
  if Assigned(FAvlEnum) then
    FAvlEnum.Free;
  inherited;
end;
 
function TRegions.GetRegions(List: TStrings): integer;
begin
  Result:=0;
  if (not Assigned(FAvlEnum))or(not Assigned(List)) then Exit;
  List.Clear;
  FAvlEnum.First;
  repeat
    List.Add(FAvlEnum.Current.Value.AsArray.S[cRegionNameID]);
  until not FAvlEnum.MoveNext;
  Result:=List.Count;
end;

В конструкторе мы пробуем получить ISuperObject, используя классовый метод TSuperObject.ParseString. И, если ISuperObject будет успешно получен, то сразу же запрашиваем у FJSONObject перечислитель для его пар. Что здесь стоит отметить? Во-первых то, что кроме обозначенного выше метода TSuperObject.ParseString мы могли бы спокойно воспользоваться и такими:

class function ParseStream(stream: TStream; strict: Boolean; partial: boolean = true; const this: ISuperObject = nil; options: TSuperFindOptions = [];
     const put: ISuperObject = nil; dt: TSuperType = stNull): ISuperObject;
class function ParseFile(const FileName: string; strict: Boolean; partial: boolean = true; const this: ISuperObject = nil; options: TSuperFindOptions = [];
     const put: ISuperObject = nil; dt: TSuperType = stNull): ISuperObject;

то есть получить представление JSON-объекта в нашей программе, передав в парсер поток или файл с данными. Или же воспользоваться одной из вспомогательных функций в модуле superobject.pas:

function SO(const s: SOString = '{}'): ISuperObject; overload;
function SO(const value: Variant): ISuperObject; overload;
function SO(const Args: array of const): ISuperObject; overload;

Например, могли бы написать так:

FJSONObject:=SO(AJsonString); //TSuperObject.ParseString(PChar(AJsonString),false,false);

и результат, в нашем случае, был бы идентичным. В общем, можно отметить, что SuperObject имеет массу всевозможных методов для парсинга JSON: из строк, файлов, потоков, переменных типа Variant и т.д.

Следующий момент - это запрос перечислителя TSuperAvlIterator.  Перечислитель этого типа удобно использовать, когда нам надо получать не только значение (Value) какой-либо пары, но и её имя (Name). Класс TSuperAvlIterator имеет следующее описание:

TSuperAvlIterator = class
  private
    FTree: TSuperAvlTree;
    FBranch: TSuperAvlBitArray;
    FDepth: LongInt;
    FPath: array[0..SUPER_AVL_MAX_DEPTH - 2] of TSuperAvlEntry;
  public
    constructor Create(tree: TSuperAvlTree); virtual;
    procedure Search(const k: SOString; st: TSuperAvlSearchTypes = [stEQual]);
    procedure First;
    procedure Last;
    function GetIter: TSuperAvlEntry;
    procedure Next;
    procedure Prior;
    function MoveNext: Boolean;
    property Current: TSuperAvlEntry read GetIter;
  end;

В принципе, здесь все методы и свойства должны быть понятны: First - переход к первому элементу, Prior - к предыдущему, MoveNext - вернет True, если удалось перейти к следующему и т.д. Соответственно TSuperAvlEntry - это класс, представляющий отдельную пару (ака TJsonPair в DBXJSON). И этот перечислитель мы запрашиваем для того, чтобы потом с помощью него "бегать" по парам нашего JSON-объекта и получать необходимую информацию. Ну, а самый первый метод, который активно использует перечислитель этого типа - GetRegions. Здесь мы, как сказано выше, читаем названия регионов:

//cRegionNameID = 1
  FAvlEnum.First;
  repeat
    List.Add(FAvlEnum.Current.Value.AsArray.S[cRegionNameID]);
  until not FAvlEnum.MoveNext;

Проясним один маленький момент относительно использования repeat..until. При создании нашего перечислителя типа TSuperAvlIterator индекс текущего элемента в списке устанавливается в значение -1, что позволяет нам сразу же использовать метод MoveNext, который вернет нам первый элемент. Если бы мы гарантированно (железно, на 100%) были уверены, что метод GetRegions вызовется всего один раз за все время работы программы и существования нашего класса, то мы могли бы сделать проще и написать так:

while FAvlEnum.MoveNext do
  List.Add(FAvlEnum.Current.Value.AsArray.S[cRegionNameID]);

Но, ИМХО, надеяться на такое положение дел не стоит и поэтому мы вначале гарантированно устанавливаем курсор на первый элемент списка и уже после этого спокойно проходим по всему списку пар и выводим необходимые данные в список List . Кстати, про сам вывод. Вот какая была последовательность получения классов и интерфейсов:

  FAvlEnum.Current.Value.AsArray.S[cRegionNameID];
  //FAvlEnum.Current - получили TSuperAvlEntry
  //FAvlEnum.Current.Value - получили Value пары в виде ISuperObject
  //FAvlEnum.Current.Value.AsArray - представили ISuperObject в виде массива TSuperArray
  //FAvlEnum.Current.Value.AsArray.S[cRegionNameID] - прочитали второй элемент массива как простую строку string.

Теперь, если мы напишем в нашей основной программе, например, вот такой обработчик OnClick кнопки:

var Stream: TStringStream;
begin
  if OpenDialog1.Execute then
    begin
      Edit1.Text:=OpenDialog1.FileName;
      Stream:=TStringStream.Create;
      try
      Stream.LoadFromFile(Edit1.Text);
      Regions:=TRegions.Create(Utf8ToAnsi(Stream.DataString));
      Regions.GetRegions(ComboBox1.Items);
      finally
        Stream.Free;
      end;
    end;

То в запущенной программе получим вот такой результат:

Список регионов из JSON

Двигаемся далее. Так как при чтении регионов мы ничего нигде не запоминали кроме того, что записывали название региона в список List, то, в этом случае, логично было бы предусмотреть такой метод, который вернул бы нам json-объект конкретного региона по его названию, например, если во входящем параметре метода будет задана строка "Омская область", то в результате будет получен такой объект:

Напишем такой метод, используя все тот же перечислитель, что и в предыдущем случае:

function TRegions.GetRegionObject(const ARegion: string; out RegionID:integer): ISuperObject;
begin
  if not Assigned(FAvlEnum) then Exit;
  FAvlEnum.First;
  repeat
     if SameText(FAvlEnum.Current.Value.AsArray.S[1], ARegion)then
       begin
         RegionID:=StrToInt(FAvlEnum.Current.Name);
         Exit(FAvlEnum.Current.Value);
       end;
  until not FAvlEnum.MoveNext;
end;

Работа метода аналогична тому, что было рассмотрено выше в методе GetRegions. Остается только отметить, что кроме самого объекта GetRegionObject также вернет в выходном параметре RegionID:integer - имя пары которая содержит необходимый нам объект, чтобы в последствии можно было избегать повторной "пробежки" по всему списку перечислителя и сразу обращаться к значению пары по её имени.
Двигаемся далее. Следующий наш шаг - по выбранному в ComboBox'е региону сформировать список городов. Назовем этот метод GetCities. Метод будет таким:

function TRegions.GetCities(const ARegion: string; List:TStrings): integer;
{cCityNameID = 0;
 cCitiesArrID = 5;}
var RegionObject, CityObject: ISuperObject;
    ID: integer;
    CityEnum: TSuperEnumerator;
begin
   if (not Assigned(FAvlEnum))or(not Assigned(List)) then Exit;
   List.Clear;
   RegionObject:=GetRegionObject(ARegion,ID);
   if Assigned(RegionObject) then
     begin
        CityObject:=RegionObject.AsArray.O[cCitiesArrID];
        if Assigned(CityObject) then
          begin
            CityEnum:=CityObject.GetEnumerator;
            try
              while CityEnum.MoveNext do
                List.Add(CityEnum.Current.AsArray.S[cCityNameID])
            finally
              CityEnum.Free;
            end;
            Result:=ID;
          end;
     end;
end;

Рассмотрим опять, что здесь происходит и почему. Во-первых, после проверок на существование всех необходимых нам объектов, мы используем наш метод GetRegionObject и получаем объект региона и запоминаем его имя, чтобы потом передать в результате функции:

  RegionObject:=GetRegionObject(ARegion,ID);

Затем, если объект найден, мы запрашиваем объект, содержащий названия городов:

CityObject:=RegionObject.AsArray.O[cCitiesArrID];

Если смотреть по структуре JSON, то с помощью этой строчки кода мы получили вот эту часть:


А дальше мы снова запрашиваем перечислитель, но уже другого типа:

CityEnum: TSuperEnumerator;
....
CityEnum:=CityObject.GetEnumerator;

Почему именно такой перечислитель? Все просто: во-первых, для примера, т.к. никто нам не запрещал воспользоваться уже известным нам TSuperAvlIterator и спокойно пройтись по всем парам объекта, а во-вторых, этот тип перечислителя удобно использовать в том случае, если нас не интересуют имена пар - TSuperEnumerator в свойстве Current возвращает значение пары (Value). Ну и, также, TSuperEnumerator "понимает" как получить значение не только пары из объекта, но и элемента массива, если TSuperEnumerator был запрошен у объекта типа TSuperArray. Итак, получив в распоряжение перечислитель, мы проходимся по всем парам объекта и считываем названия городов:

while CityEnum.MoveNext do
  List.Add(CityEnum.Current.AsArray.S[cCityNameID])

Здесь мы знаем, что перечислитель "убьется" сразу же, после того как все элементы будут просмотрены, поэтому спокойно используем цикл while..do. Ну, а последовательность была чуть по-проще, чем в GetRegions:

CityEnum.Current.AsArray.S[cCityNameID]
//CityEnum.Current - получили ISuperObject
//CityEnum.Current.AsArray - представили объект в виде TSuperArray
//CityEnum.Current.AsArray.S[cCityNameID] - получили строку, содержащую название города.

Теперь можем дописать событие OnChange у первого CoboBox и не забыть при этом сохранить ID региона (оно нам понадобиться далее):

var CurrentRegion:integer;
....
procedure TForm8.ComboBox1Change(Sender: TObject);
begin
  CurrentRegion:=Regions.GetCities(ComboBox1.Items.Strings[ComboBox1.ItemIndex],ComboBox2.Items);
end;

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

Вывод списка городов в регионе

Остался последний штрих - по выбранному в списке городу получить его представление для URL, то есть добраться в объекте вот сюда:

На данный момент наша программка уже "помнит" какой регион мы выбрали, поэтому метод GetCityURL можно сделать таким:

function TRegions.GetCityURL(const ARegionID: integer; ACity: string): string;
var Region: TSuperArray;
    CityEnum: TSuperEnumerator;
begin
  if not Assigned(FJSONObject) then Exit;
  Region:=FJSONObject.A[IntToStr(ARegionID)];
  if Assigned(Region) then
    begin
      CityEnum:=Region.O[5].GetEnumerator;
      try
        while CityEnum.MoveNext do
          begin
            if SameText(CityEnum.Current.AsArray.S[cCityNameID],ACity) then
              begin
                Result:=CityEnum.Current.AsArray.S[cCityURLID];
                break;
              end;
          end;
      finally
         CityEnum.Free;
      end;
    end;
end;

Здесь опять же ради примера, продемострирован ещё один простой способ получение данных из JSON-объекта, а именно:

  Region:=FJSONObject.A[IntToStr(ARegionID)];

Мы ведь не зря запоминали имя пары, когда искали регион :) Вот нам это имя и пригодилось - мы по имени запросили объект и получили его сразу в виде TSuperArray. Ну, а дальше - дело техники: просим вернуть нам перечислитель на пятый элемент массива (это как раз список городов) и проходим по элементам массива и ищем название города и, если город с таким названием найден, то, получаем из массива элемент, содержащий его URL. То есть здесь мы "бегали" вот по этому массиву в JSON-объекте:

Остается только "прицепить" наш новый метод в готовой программе. Пишем обработчик OnChange второго ComboBox:

procedure TForm8.ComboBox2Change(Sender: TObject);
begin
  Label5.Caption:=Regions.GetCityURL(CurrentRegion,ComboBox2.Items.Strings[ComboBox2.ItemIndex])
end;

Результат работы программы представлен на рисунке ниже:

Результат работы программы

Вот, пожалуй, и решение задачи. Что мы смогли узнать нового про SuperObject пока писали нашу программу?

  1. SuperObject имеет кучу методов для получения ISuperObject, начиная от классовых методов у TSuperObject и, заканчивая, вспомогательными методами SO в модуле superobject.pas
  2. Для парсинга JSON в SuperObject имеется сразу два типа перечислителей. Первый тип перечислителей TSuperAvlIterator если в ходе перечисления нам необходимо получить пару целиком. Второй тип перечислителей - TSuperEnumerator использует в работе TSuperAvlIterator и может перечислять только значения пар. Использование второго типа перечислителей оправдано в случае, если нас не интересуют имена пар, а также, в том, случае, если нам необходимо перечислить элементы в масиве TSuperArray.
  3. Для получения значений пар из JSON-объекта можно не использовать вообще перечислители напрямую, если нам известно имя пары - в этом случае мы можем просто воспользоваться одним из методов у ISuperObject и получить необходимое нам значение буквально в одну строку.

И, в заключение, пара слов об исходнике. Всё, что рассмотрено в статье выше разрабатывалось в Delphi XE2 Architect, ОС Windows 7 x64, SuperObject - последняя ревизия на момент публикации статьи . В архиве Вы найдете:

  1. exe-файл программы
  2. txt-файл, содержащий JSON для работы
  3. исходники

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 01.06.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Enterprise Connectors (1 Year term)
Delphi Professional Named User
ABBYY Lingvo x6 Многоязычная Профессиональная версия, электронный ключ
ABBYY Lingvo x6 Английская Профессиональная версия
Advanced Data Export VCL Suite (with sources) + 1 Year Maintenance
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Новости мира 3D-ускорителей
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100