wright 2 Posted December 22, 2022 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. I would like to get the result like that: Expected behavior Share this post Link to post
programmerdelphi2k 237 Posted December 22, 2022 (edited) first you need organize your JSON text: many "arrays with sub-arrays..." your JSON needs some adjusts...: [ {...}, {...}, ... ] Edited December 22, 2022 by programmerdelphi2k Share this post Link to post
wright 2 Posted December 22, 2022 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. 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
programmerdelphi2k 237 Posted December 22, 2022 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
programmerdelphi2k 237 Posted December 23, 2022 (edited) maybe some like this help you 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 December 23, 2022 by programmerdelphi2k Share this post Link to post
aehimself 396 Posted December 24, 2022 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. 1 Share this post Link to post
Fr0sT.Brutal 900 Posted December 26, 2022 TLineReader will help. Or old-school ReadLn 1 Share this post Link to post
wright 2 Posted December 27, 2022 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 ......... ......... Share this post Link to post
wright 2 Posted December 27, 2022 (edited) 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: Edited December 27, 2022 by wright Share this post Link to post