|
|
|||||||||||||||||||||||||||||
|
Пример использования перечислителей в 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 - последняя ревизия на момент публикации статьи . В архиве Вы найдете:
Ссылки по теме
|
|
|||||||