|
|
|||||||||||||||||||||||||||||
|
Пример использования перечислителей в SuperObject.Источник: webdelphi webdelphi
Уже давненько собирался подробнее разобраться в работе с SuperObject, да все как-то не было подходящего момента - в последнее время использую сугубо родной DBXJSON, т.к. его возможностей с лихвой хватает для решения моих задач, а чисто ради спортивного интересу вникать во что-то новое сейчас особенного желания нет. Исключение - конкурс по FireMonkey...даже не так - FireMonkey изучается совсем не ради спортивного интереса :). Ну так вот, что касается SuperObject и почему сегодня речь пойдет про эту библиотеку. Где-то в районе майских праздников пришло ко мне на почту письмо с просьбой помочь немного разобраться с одним JSON-объектом, а именно - распарсить его с использованием SuperObject и вывести кое-какие данные из этого объекта в программу. Вот я и решил немного помочь с этой задачкой, т.к. это уже не просто "спортивный интерес" - вдруг да пригодится кому мое решение, если не в 1 в 1 скопированное, то хотя бы какие-то моменты из него, да и, думаю, примерчик окажется полезным для тех, кто решит вникнуть в работу с SuperObject. В двух словах суть работы на сегодня: есть JSON-объект, содержащий информацию по областям РФ и, расположенным в этих областях городах. Наша программка должна выполнять следующие действия: загружать список областей в ComboBox, по выбранной области выводить в другой ComboBox список городов и, наконец, после выбора определенного города выводить его название, используемое в дальнейшем, как я понял, для составления какого-то URL - название города на латинице. Одно из решений задачи смотрим ниже. Во-первых, что из себя представляет наш JSON-объект? Его внешний вид представлен на рисунке ниже: Проанализируем немного этот объект:
Остальная информация нам в программе не пригодится. Теперь посмотрим как можно разобрать такой 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 - имя пары которая содержит необходимый нам объект, чтобы в последствии можно было избегать повторной "пробежки" по всему списку перечислителя и сразу обращаться к значению пары по её имени. 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 пока писали нашу программу?
И, в заключение, пара слов об исходнике. Всё, что рассмотрено в статье выше разрабатывалось в Delphi XE2 Architect, ОС Windows 7 x64, SuperObject - последняя ревизия на момент публикации статьи . В архиве Вы найдете:
Ссылки по теме
|
|