Jump to content
bzwirs

Parsing Json to retrieve nested record

Recommended Posts

I am trying to parse a json response to retrieve a nested record without any success.  Example of the json response is:

 

{
  "status": "1",
  "info": "success",
  "page_info":   {
    "total_page": "1",
    "record_per_page": "1",
    "current_page": "1",
    "total_record": "1"
  },
  "records": [  {
    "machine_id": "25884",
    "imei": "",
    "install_date": "",
    "last_report": "",
    .......
    .......
    "coin_mech":     {
      "coin_mech_id": "12345",
      "cm_model": "",
      "serial_no": "",
      "revision": "",
      "coin_in_tube": "",
      "tubes":       [
                {
          "tube_id": "3469107",
          "tube_no": "4",
          "coin_unit": "100",
          "coin_count": "93",
          "coin_inserted": "0",
          "coin_dispensed": "0",
          "tube_full": "0"
        },
                {
          "tube_id": "3469105",
          "tube_no": "5",
          "coin_unit": "200",
          "coin_count": "9",
          "coin_inserted": "0",
          "coin_dispensed": "0",
          "tube_full": "0"
        }
      ]
    }
  }]
}

 

The info I am after is the records for "tubes" which can contain up to 5 records (2 in the above example) .  Have tried several of the coding examples that I found in stackoverflow without success although I have not found an example with a similar record structure.   

 

Can someone please advise me on how to retrieve those records (would be great if in a dataset).

 

Thanks in advance.

 

Bill Zwirs

Share this post


Link to post

Sorry...normally first thing I add.   FMX application on Delphi 11.3.

 

Bill

Share this post


Link to post

The above JSON has an array "records", where each item contains a "coin_mech" object, which itself contains the "tubes" array. Are you interested in the "tubes" from one specific "records" item or do you want to collect all "tubes" arrays from all "records" items.

Share this post


Link to post

I need to collect all tubes data.  At this stage, specifically the coin_unit and coin_count fields.

 

Bill Zwirs

Share this post


Link to post

You can use a TRESTResponseDataSetAdapter and connect its Response property to your TRESTResponse instance (alternatively use a TRESTResponseJSON instance to provide a TJSONObject). Iterating through the records list and adding each tubes items to your dataset may need some additional coding around, though.

Share this post


Link to post
7 hours ago, bzwirs said:

I need to collect all tubes data.  At this stage, specifically the coin_unit and coin_count fields.

With mORMot it can be solved as follows (all tubes for all records, or tubes for record with index):

uses
  mormot.core.base,
  mormot.core.text,
  mormot.core.os,
  mormot.core.variants,
  mormot.db.rad.ui;

function ShowTubesForRecordIndex(pmGrid: TDBGrid; const pmcDBFileName: TFileName; pmDBRecIdx: Integer): Boolean;
var
  docDB: TDocVariantData;
begin
  Result := False;
  if pmGrid = Nil then Exit; //=>

  FreeAndNil(pmGrid.DataSource);
  if docDB.InitJsonFromFile(pmcDBFileName, JSON_FAST_FLOAT) then
  begin
    pmGrid.DataSource := TDataSource.Create(pmGrid);
    var docPath: PDocVariantData := docDB.A['records']._[pmDBRecIdx].O['coin_mech'].A['tubes'];
    pmGrid.DataSource.DataSet := DocVariantToDataSet(pmGrid.DataSource, Variant(docPath^));
    Result := (pmGrid.DataSource.DataSet <> Nil);
  end;
end;

function ShowTubesOfAllRecords(pmGrid: TDBGrid; const pmcDBFileName: TFileName): Boolean;
var
  docDB: TDocVariantData;
  tubes: TVariantDynArray;
begin
  Result := False;
  if pmGrid = Nil then Exit; //=>

  FreeAndNil(pmGrid.DataSource);
  if docDB.InitJsonFromFile(pmcDBFileName, JSON_FAST_FLOAT) then
  begin
    var docTubesPath: PDocVariantData;
    var docRecordsPath: PDocVariantData := docDB.A['records'];
    for var i: Integer := 0 to docRecordsPath.Count - 1 do
    begin
      docTubesPath := docRecordsPath._[i].O['coin_mech'].A['tubes'];
      for var n: Integer := 0 to docTubesPath.Count - 1 do
      begin
        SetLength(tubes, Length(tubes) + 1);
        tubes[High(tubes)] := Variant(docTubesPath._[n]^);
      end;
    end;

    if Length(tubes) > 0 then
    begin
      pmGrid.DataSource := TDataSource.Create(pmGrid);
      pmGrid.DataSource.DataSet := VariantsToDataSet(pmGrid.DataSource, tubes, Length(tubes), [], []);
      Result := (pmGrid.DataSource.DataSet <> Nil);
    end;
  end;
end;

Used as follows:

var fileName: TFileName := MakePath([Executable.ProgramFilePath, 'DocDB.json']);
ShowTubesForRecordIndex(DBGrid, fileName, 0);
// ShowTubesOfAllRecords(DBGrid, fileName);

I have published an article on topic mORMot DocVariant here (forum Delphi-PRAXIS). Here is the translation into English with Google Translator. The result is not perfect (rather not so good), also some formatting is destroyed, but it is readable.

 

With best regards
Thomas

Share this post


Link to post

Including the checks if a specific branch exists or not, you also can use the built-in System.Json unit:

Var
  jo, coinmech: TJSONObject;
  recenum, tubeenum: TJSONValue;
  records, tubes: TJSONArray;
begin
  jo := TJSONObject(TJSONObject.ParseJSONValue(Memo1.Text));

  If Not Assigned(jo) Then
    Exit;

  Try
    records := jo.GetValue<TJSONArray>('records');

    If Not Assigned(records) Then
      Exit;

    For recenum In records Do
    Begin
      coinmech := recenum.GetValue<TJSONObject>('coin_mech');

      If Not Assigned(coinmech) Then
        Continue;

      tubes := coinmech.GetValue<TJSONArray>('tubes');

      If Not Assigned(tubes) Then
        Continue;

      For tubeenum In tubes Do
      Begin
        WriteLn('Tube found, ID:' + tubeenum.GetValue<String>('tube_id'));
        // ...
        // Add a new record in a MemTable...?
      End;
    End;
  Finally
    FreeAndNil(jo);
  End;

The code can be siplified significantly but this way you can see what is happening, how TJSONObject handling works. The code above produced the following output:

image.thumb.png.840155cde934a6c0f89996b4cd61047f.png

Share this post


Link to post

Thank you to all.  This gives me a lot to try so will let you know at a later date whatever has ended up working for me.

 

Thanks again to everyone.

 

regards

 

Bill Zwirs

Share this post


Link to post

Hi,

 

Ended up using example from aehimself.  That was the easiest to implement in my app and worked straight away.

 

regards

 

Bill

Share this post


Link to post

Good Example But I Cant Find how to obtain recnum y tubeenum values. Can you Help me with this?

This is using example from aehimself.

 

 

For recenum In records Do
    Begin
      coinmech := recenum.GetValue<TJSONObject>('coin_mech');

      If Not Assigned(coinmech) Then
        Continue;

      tubes := coinmech.GetValue<TJSONArray>('tubes');

      If Not Assigned(tubes) Then
        Continue;

      For tubeenum In tubes Do

 

Share this post


Link to post

recenum and tubeenum are initialized automatically by Delphi during the For .. In ... cycle. You don't need to "obtain" them.

 

Similar to:

Var
  stringarray: TArray<String>;
  stringenum: String;
Begin
  [...]
  // stringenum is "uninitialized" at this moment
  For stringenum In stringarray Do
    // stringenum is now initialized and contains the next element in the array
    [...]

In the original snipplet values are being assigned at:

    For recenum In records Do
    Begin
      [...]

      For tubeenum In tubes Do

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

×