(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
Quest Software. SQL Navigator for Oracle
ABBYY Lingvo x6 Английская Профессиональная версия
SmartBear AQtime Pro - Node-Locked License (Includes 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 и виртуальная реальность. Все о Macromedia Flash MX.
Проект mic-hard - все об XP - новости, статьи, советы
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100