Jump to content
wright

How to fill TListViewItem using TJsonIterator ?

Recommended Posts

Hi there, i tried to parse the selected elements (JSON items), and then display the results using the [TListView](http://docwiki.embarcadero.com/Libraries/en/Vcl.ComCtrls.TListView) component and using using the [TJSONIterator.Next](http://docwiki.embarcadero.com/Libraries/en/System.JSON.Builders.TJSONIterator.Next), [TJSONIterator.Recurse](http://docwiki.embarcadero.com/Libraries/en/System.JSON.Builders.TJSONIterator.Recurse), and [TJSONIterator.Return](http://docwiki.embarcadero.com/Libraries/en/System.JSON.Builders.TJSONIterator.Return) methods.

Code data:

{"address": {"building": "1007", "coord": [-73.856077, 40.848447], "street": "Morris Park Ave", "zipcode": "10462"}, "borough": "Bronx", "cuisine": "Bakery", "grades": [{"date": {"$date": 1393804800000}, "grade": "A", "score": 2}, {"date": {"$date": 1378857600000}, "grade": "A", "score": 6}, {"date": {"$date": 1358985600000}, "grade": "A", "score": 10}, {"date": {"$date": 1322006400000}, "grade": "A", "score": 9}, {"date": {"$date": 1299715200000}, "grade": "B", "score": 14}], "name": "Morris Park Bake Shop", "restaurant_id": "30075445"}
{"address": {"building": "469", "coord": [-73.961704, 40.662942], "street": "Flatbush Avenue", "zipcode": "11225"}, "borough": "Brooklyn", "cuisine": "Hamburgers", "grades": [{"date": {"$date": 1419897600000}, "grade": "A", "score": 8}, {"date": {"$date": 1404172800000}, "grade": "B", "score": 23}, {"date": {"$date": 1367280000000}, "grade": "A", "score": 12}, {"date": {"$date": 1336435200000}, "grade": "A", "score": 12}], "name": "Wendy'S", "restaurant_id": "30112340"}
{"address": {"building": "351", "coord": [-73.98513559999999, 40.7676919], "street": "West   57 Street", "zipcode": "10019"}, "borough": "Manhattan", "cuisine": "Irish", "grades": [{"date": {"$date": 1409961600000}, "grade": "A", "score": 2}, {"date": {"$date": 1374451200000}, "grade": "A", "score": 11}, {"date": {"$date": 1343692800000}, "grade": "A", "score": 12}, {"date": {"$date": 1325116800000}, "grade": "A", "score": 12}], "name": "Dj Reynolds Pub And Restaurant", "restaurant_id": "30191841"}
{"address": {"building": "2780", "coord": [-73.98241999999999, 40.579505], "street": "Stillwell Avenue", "zipcode": "11224"}, "borough": "Brooklyn", "cuisine": "American ", "grades": [{"date": {"$date": 1402358400000}, "grade": "A", "score": 5}, {"date": {"$date": 1370390400000}, "grade": "A", "score": 7}, {"date": {"$date": 1334275200000}, "grade": "A", "score": 12}, {"date": {"$date": 1318377600000}, "grade": "A", "score": 12}], "name": "Riviera Caterer", "restaurant_id": "40356018"}
{"address": {"building": "97-22", "coord": [-73.8601152, 40.7311739], "street": "63 Road", "zipcode": "11374"}, "borough": "Queens", "cuisine": "Jewish/Kosher", "grades": [{"date": {"$date": 1416787200000}, "grade": "Z", "score": 20}, {"date": {"$date": 1358380800000}, "grade": "A", "score": 13}, {"date": {"$date": 1343865600000}, "grade": "A", "score": 13}, {"date": {"$date": 1323907200000}, "grade": "B", "score": 25}], "name": "Tov Kosher Kitchen", "restaurant_id": "40356068"}
procedure TForm2.AddColumn(const AName: String);
var
  oCol: TListColumn;
begin
  oCol := ListView1.Columns.Add;
  oCol.Width := -1;
  oCol.Caption := AName;
end;

procedure TForm2.Button3Click(Sender: TObject);
begin
  ParseObject;
end;

procedure TForm2.ParseObject;
var
  oIter: TJSONIterator;
  LJsonTextReader: TJsonTextReader;
  LStringReader: TStreamReader;
  oItem: TListItem;
  I: Integer;
begin

//  oIter := TJSONIterator.Create(LJsonTextReader);
//  NObjJSON := oIter.AsInteger;

  ListView1.Items.Clear;
  ListView1.Columns.Clear;
  ListView1.ViewStyle := vsReport;
  AddColumn('Name');
  AddColumn('Cuisine');
  AddColumn('Street');
  AddColumn('Building');
  AddColumn('Borough');
  ListView1.Items.BeginUpdate;
  try
    oItem := ListView1.Items.Add;
    for i := 1 to ListView1.Columns.Count - 1 do
      oItem.SubItems.Add('');

    LStringReader := TStreamReader.Create('../../resto.json', TEncoding.UTF8, True);
    LJsonTextReader := TJsonTextReader.Create(LStringReader);
    oIter := TJSONIterator.Create(LJsonTextReader);

    try
      while True do
      begin
        while oIter.Next do
          if oIter.&Type in [TJsonToken.StartObject, TJsonToken.StartArray] then
            oIter.Recurse
          else if oIter.Path = 'name' then
            oItem.Caption := oIter.AsString
          else if oIter.Path = 'cuisine' then
            oItem.SubItems[0] := oIter.AsString
          else if oIter.Path = 'address.street' then
            oItem.SubItems[1] := oIter.AsString
          else if oIter.Path = 'address.building' then
            oItem.SubItems[2] := oIter.AsString
          else if oIter.Path = 'borough' then
            oItem.SubItems[3] := oIter.AsString;
        if oIter.InRecurse then
          oIter.Return
        else
          Break;
      end;
    finally
      oIter.Free;
      LJsonTextReader.Free;
      LStringReader.Free;
    end;
  finally
    ListView1.Items.EndUpdate;
  end;
end;

As Result: i only got one line filled.

image.thumb.png.e2df45f6ba631fb0226209f0e76ea712.png

 

I would like to get the result like that:

Expected behavior

image.thumb.png.9a7293d8e3bcf58810ff06f4f8b2b6ee.png

Share this post


Link to post

first you need organize your JSON text: many "arrays with sub-arrays..."

  • your JSON needs some adjusts...:  [  {...}, {...}, ... ]

 

image.thumb.png.18df768cdf31c6409ed1e0c9ea9decaa.png

Edited by programmerdelphi2k

Share this post


Link to post
1 hour ago, programmerdelphi2k said:

first you need organize your JSON text: many "arrays with sub-arrays..."

Yes! i did it before i posted but the result was ennoying and shown 0 lines. that's the reason i used the Json data as default.

image.thumb.png.6865f9e71cfeee33854d5f4d5df7e4d1.png

 

NB: i forgot to tell that the json data i used is a piece of code from the restaurant project inside Rad Studio samples which uses MongoDB as database: 

* navigate to: Object Pascal\Database\FireDAC\Samples\DBMS Specific\MongoDB\Restaurants
* original Json: Object Pascal\Database\FireDAC\DB\Data\restaurants.json

 

Share this post


Link to post

try this StackOverFlow: some like this, maybe a "recursive" function ....

https://stackoverflow.com/questions/46534011/delphi-parse-json-array-or-array

function getData(JsonString: String; User: String; Field: String): String;
var
  JSonValue: TJSonValue;
  JsonArray: TJSONArray;
  ArrayElement: TJSonValue;
  FoundValue: TJSonValue;
begin
  Result :='';

  // create TJSonObject from string
  JsonValue := TJSonObject.ParseJSONValue(JsonString);

  // get the array
  JsonArray := JsonValue as TJSONArray;

  // iterate the array
  for ArrayElement in JsonArray do begin
      FoundValue := ArrayElement.FindValue(User);
      if FoundValue <> nil then begin
        Result := ArrayElement.GetValue<string>(User + '.' + Field);
        break;
      end;
  end;
end;

 

Share this post


Link to post

maybe some like this help you

 

image.thumb.png.33fbfc3b6b64a016056cc97b70816d82.png

uses
  System.Generics.Collections,
  System.JSON;

(*
  [
  {"address": {"building": "1007", "coord": [-73.856077, 40.848447], "street": "Morris Park Ave", "zipcode": "10462"},
  "borough": "Bronx",
  "cuisine": "Bakery",
  "grades": [....],      // many sub-array here!
  "name": "Morris Park Bake Shop",
  "restaurant_id": "30075445"},
  ...
  ]
*)

procedure TForm1.Button1Click(Sender: TObject);
  function MyFindValues(const AValue: TJSONObject; const AKey: string): string;
  var
    LJSONValue: TJSONValue;
  begin
    result := '';
    //
    if (AValue <> nil) then
      begin
        LJSONValue := AValue.FindValue(AKey);
        //
        if (LJSONValue <> nil) then
          result := AKey + '=' + LJSONValue.ToString; // if another array... needs other approach!
      end;
  end;

var
  LJSONValue: TJSONValue;
  LJSONArr  : TJSONArray;
begin
  Memo1.Lines.LoadFromFile('..\..\MyJSON.txt');
  Memo2.Text := '';
  //
  LJSONValue := nil;
  //
  try
    try
      LJSONValue := TJSONObject.ParseJSONValue(Memo1.Text); // or TJSONArray directly!
      //
      if (LJSONValue <> nil) then
        begin
          LJSONArr := TJSONArray(LJSONValue);
          //
          if (LJSONArr <> nil) then
            for var A in LJSONArr do
              begin
                Memo2.Lines.Add(MyFindValues(TJSONObject(A), 'restaurant_id'));
                Memo2.Lines.Add(MyFindValues(TJSONObject(A), 'name'));
                Memo2.Lines.Add(MyFindValues(TJSONObject(A), 'cuisine'));
                Memo2.Lines.Add(MyFindValues(TJSONObject(A), 'borough'));
                Memo2.Lines.Add(MyFindValues(TJSONObject(A), 'address')); // array = sub-table = needs more iterations to extract items...
                Memo2.Lines.Add(MyFindValues(TJSONObject(A), 'grades'));  // array = sub-table
              end;
        end;
    except
      on E: Exception do // generic
        ShowMessage(E.Message);
    end;
  finally
    LJSONValue.Free;
  end;
end;

initialization

ReportMemoryLeaksOnShutdown := true;

end.

 

Edited by programmerdelphi2k

Share this post


Link to post

So, the sample data you mentioned is not a valid JSON document, but one separate JSON object per line as far as I can see. Maybe this is why TJSONTextReader fails - I don't know, I never used it.

If the data will always arrive in this format and efficiency / optimization is no concern, you can do something like...

Var
  line: String;
  jo: TJSONObject;
//  jv: TJSONValue;
Begin
  For line In TFile.ReadAlltext('C:\[...]\restaurants.json') Do
  Begin
    jo := TJSONObject(TJSONObject.ParseJSONValue(line));

    If Not Assigned(jo) Then
      Continue; // invalid JSON code in one line

    Try
      // ShowMessage(jo.GetValue('borough').Value);
      // For jv In jo.GetValue('grades') As TJSONArray Do
      // Begin
      //   ShowMessage((jv As TJSONObject).GetValue('grade').Value);
      //   ((jv As TJSONObject).GetValue('score') As TJSONNumber).AsInt
      // End;
    Finally
      jo.Free;
    End;
  End;
End;

The bad thing about this logic is that it reads the whole contents of the file in the memory, which will stay there until processing is complete. With 12 Mb this is not an issue, but you might hit a barrier if the data is in the GBs range.

In that case you can use a TFileStream, read into a String until sLineBreak (or .Position = .Size) and build each JSON object from this. The processing logic can stay the same.

  • Like 1

Share this post


Link to post

It seems that TJSONIterator.Path doesn't read the values at all! 

i tried this and i still figure issues:

cuisine doens't load.

............
ListView1.Items.BeginUpdate;
  try
    LStreamReader := TStreamReader.Create('../../resto.json', TEncoding.UTF8, True);
    LJsonTextReader := TJsonTextReader.Create(LStreamReader);
    oIter := TJSONIterator.Create(LJsonTextReader);

    while oIter.Next() do
    begin
      oItem := ListView1.Items.Add;
      for i := 1 to ListView1.Columns.Count - 1 do
        oItem.SubItems.Add('');

      if oIter.&Type in [TJsonToken.StartObject, TJsonToken.StartArray] then
      begin
        oIter.Recurse;
        oIter.Next('name');
        oItem.Caption := oIter.AsValue.ToString;
        oIter.Next('cuisine');
        oItem.SubItems[0] := oIter.AsValue.ToString;
      end
      .........
      .........

 

Screenshot 2022-12-27 105039.png

Share this post


Link to post

Got it Works but in a different way than the official sample. I'm not satisfied with my procedure, i think there is a better way to do iteration  without MongoDB.

what i changed:

 

...
ListView1.Items.BeginUpdate;
  try
    LStreamReader := TStreamReader.Create('../../resto.json', TEncoding.UTF8, True);
    LJsonTextReader := TJsonTextReader.Create(LStreamReader);
    oIter := TJSONIterator.Create(LJsonTextReader);

    while oIter.Next() do
    begin
      oItem := ListView1.Items.Add;
      for i := 1 to ListView1.Columns.Count - 1 do
        oItem.SubItems.Add('');

      if oIter.&Type in [TJsonToken.StartObject, TJsonToken.StartArray] then
      begin
        oIter.Recurse;
        if oIter.Next('address') then
        begin
          oIter.Recurse;
          oIter.Next('building');
          oItem.SubItems[2] := oIter.AsString;
          oIter.Next('street');
          oItem.SubItems[1] := oIter.AsString;
          oIter.Return
        end;
        oIter.Next('borough');
        oItem.SubItems[3] := oIter.AsValue.ToString;
        oIter.Next('cuisine');
        oItem.SubItems[0] := oIter.AsValue.ToString;
        oIter.Next('name');
        oItem.Caption := oIter.AsValue.ToString;
      end;

      if oIter.InRecurse then
        oIter.Return
      else
        Break;
    end;
  finally
    ListView1.Items.EndUpdate;
  end;
...

Result:

 

Screenshot 2022-12-27 105039.png

Edited by wright

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×