Home > Software engineering >  Test existence of nested JSON element (with SuperObject)
Test existence of nested JSON element (with SuperObject)

Time:01-05

You can skip straight to the code, if you want to. The leading text is explanation, but not necessary.

I am trying to use SuperObject to parse Delphi.

I was just using the plain JSON classes that come with Delphi 10.4, but had difficulty with some slightly complex functionality.

E.g stuff, such as does a.b.c exist, or a.b.c.d - where d is an array - or a.b.c.d[3].

And how to iterate over a.b.c.d. And to assign a value to a.b.c.d.e, updating it if it exists, creating it if not; and, if only part of the path exists, create it all e.g only a.b exists and I want to assign a value to a.b.c.d[3].e.

If anyone can point me to some examples of that sort of thing, I would be grateful.

Now, to my question. It seemed from various posts that SuperObject would be the answer, but I am failing at my first basic step - to test the existence of a nested JSN element.

Here's my code (is there an online JSON Fiddle site?)

unit fMainForm;
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses System.IOUtils,
     SuperObject;

procedure TForm1.FormCreate(Sender: TObject);
  var json: ISuperObject;
      jsonString : String;
begin    
  // Example JSON taken from https://stackoverflow.com/questions/10539797/complex-json-nesting-of-objects-and-arrays
  jsonString := '{'  
                '"problems": [{'  
                '    "Diabetes":[{'  
                '        "medications":[{'  
                '            "medicationsClasses":[{'  
                '                "className":[{'  
                '                    "associatedDrug":[{'  
                '                        "name":"asprin",'  
                '                        "dose":"",'  
                '                        "strength":"500 mg"'  
                '                    }],'  
                '                    "associatedDrug#2":[{'  
                '                        "name":"somethingElse",'  
                '                        "dose":"",'  
                '                        "strength":"500 mg"'  
                '                    }]'  
                '                }],'  
                '                "className2":[{'  
                '                    "associatedDrug":[{'  
                '                        "name":"asprin",'  
                '                        "dose":"",'  
                '                        "strength":"500 mg"'  
                '                    }],'  
                '                    "associatedDrug#2":[{'  
                '                        "name":"somethingElse",'  
                '                        "dose":"",'  
                '                        "strength":"500 mg"'  
                '                    }]'  
                '                }]'  
                '            }]'  
                '        }],'  
                '        "labs":[{'  
                '            "missing_field": "missing_value"'  
                '        }]'  
                '    }],'  
                '    "Asthma":[{}]'  
                '}]}';

  json := SO(jsonString);

  if  json.AsObject.Exists('problems') then
    Memo1.Lines.Add('"problems" found')
  else
    Memo1.Lines.Add('"problems" not found');

  if  json.AsObject.Exists('problems.Diabetes') then
    Memo1.Lines.Add('"problems.Diabetes" found')
  else
    Memo1.Lines.Add('"problems.Diabetes" not found');
end;

end.

When I run it, the memo contains

"problems" found
"problems.Diabetes" not found

How do I test for the existence of problems.Diabetes?

If I can crack that, I will try to update the strength of an associatedDrug, then insert a new key/value pair into the associatedDrug, then extend an existing key by adding several depths.

CodePudding user response:

You have an array problems with an other array Diabetes. You can simply do :

  if Assigned(json['problems[0].Diabetes[0]']) then
    Memo1.Lines.Add('problems found   Diabetes found');

Or loop inside both array :

  if Assigned(json['problems']) then
  begin
    for i := 0 to json['problems'].AsArray.Length - 1 do
    begin
      Memo1.Lines.Add('"problems" found');

      if Assigned(json['problems'].AsArray[i]['Diabetes']) then
      begin
        for j := 0 to json['problems'].AsArray[i]['Diabetes'].AsArray.Length - 1 do
          Memo1.Lines.Add('"problems.Diabetes" found')
      end
      else
        Memo1.Lines.Add('"problems.Diabetes" not found');
    end;
  end
  else
    Memo1.Lines.Add('"problems" not found');

If you are not familiar with Delphi / JSON, use System.JSON is easier to use than this external lib

CodePudding user response:

You've picked one of the worst JSON examples, because its structure needlessly puts everything into arrays (always [{ and ]}) when array elements would be enough already. You should have grabbed an easier example and start with that, to get familiar with what an object is and what an array is. Likewise I restructured and shortened the JSON data to make it tinier and more clear. If you managed to understand that you can go on and reach your strength property:

uses
  SuperObject;

var
  json, next: ISuperObject;
  text: String;
  sts: TSuperTableString;
  sa: TSuperArray;
  i: Integer;
begin
  text:=
  '{'                          // {Object} starts
  '  "problems": ['            //   1st "property" of object, value is an [array]
  '    {'                      //     1st [element] of array is an {object}
  '      "Diabetes": "yes"'    //       1st "property" of object is a "text"
  '    }'                      //     {Object} ends after just 1 "property"
  '  ],'                       //   [Array] ends after just 1 [element]
  '  "Asthma": ['              //   2nd "property" of object, value is an [array] again
  '    {}'                     //     1st [element] of array is an (empty) {object}
  '  ]'                        //   [Array] ends after just 1 [element]
  '}';                         // {Object} ends after 2 "properties"

  json:= SO( text );

  if json.IsType( stObject ) then begin  // Is this even JSON (which must be an object)?
    sts:= json.AsObject;
    if sts.Find( 'problems', next ) then begin  // Does a property "problems" exist?
      if next.IsType( stArray ) then begin  // Is its value an array?
        sa:= next.AsArray;
        for i:= 0 to sa.Length- 1 do begin  // Iterate all array elements
          next:= sa[i];
          if next.IsType( stObject ) then begin  // Is this element an object?
            sts:= next.AsObject;

            sts.S['key']:= 'value';  // Adding our own property "key" to that object

            if sts.Find( {'Diabetes'} 'key', next ) then begin  // Does a property "key" exist?
              Caption:= 'Found it!';
              if next.IsType( stString ) then begin  // Is the property of type "text"?
                Caption:= Caption  ' Value='  next.AsString;  // Should be "value"
              end;
              break;  // Leave array loop
            end;
          end;
        end;
      end;
    end;
  end;
end;

Single step through it with the debugger to see how every check is necessary to finally even reach the property Diabetes and to then check if its value is actually a text (and not another object/array).

A better format of that initial JSON data would be this, freed from needless arrays and using also numbers as datatypes instead of texts only (the syntax is valid, it may look odd for you but I think this is a more logical display):

{ "problems": 
  { "Diabetes": 
    { "medications":
      [
        [
          { "name": "Asprin"
          , "strength_mg": 500
          }
        ,
          { "name": "Arsen"
          , "strength_mg": 20
          }
        ]
      ,
        [
          { "name": "Toracin"
          , "strength_ml": 10
          }
        ,
          { "name": "Ambroxol"
          , "strength_mg": 350
          }
        ]
      ]
    , "labs": 
      { "test_chamber": 3
      }
    }
  , "Asthma": null
  }
, "is_insured": false
}
  • Related