1

Scenario

I use the JsonDataObjects unit by Andreas Hausladen in various places in my Delphi 2009 project, and now I have a requirement to parse the simple JSON returned by an API call to cpanel on a remote server.

Most of it I can do, but the JSON returned in this case appears, at least at the moment, to have an empty object {} (not one that is null), and I would appreciate some help (a) testing for an empty object, and (b) obtaining some values from the object if it is not empty.

The data

The cpanel API documentation here says that the returns JSON has the form below where metadata is empty.

{
   "apiversion": 3,
   "func": "add_host",
   "module": "Mysql",
   "result": {
      "data": null,
      "errors": null,
      "messages": null,
      "metadata": { },
      "status": 1,
      "warnings": null
   }
}

In fact, the documentation is wrong and the data I get back has this form, i.e. there is no results object. However, the metadata object is still empty, as in the documentation. I don't know what it will look like if the metadata object is ever not empty. The documentation doesn't say.

{                                   
   "data":"MyData",                    
   "errors":["The first error text"],  
   "warnings":null,                    
   "status":0,                         
   "messages":null,                    
   "metadata":{}                       
}                                   

The questions

  1. How do I test for the object MainObj['metadata'] being empty? It isn't null as it contain {}, so If MainObj['metadata'].isnull ... returns false, and the code drops into the else part.

  2. Once I have tested for it being empty, how do I extract the object's data (which presumably is null) when it doesn't have a name?

The problem code

This is a small test procedure to demonstrate my problem parsing this data. Most lines produce the expected result. The issue is with the object MainObj['metadata'], which is (currently) empty in the JSON.

procedure TForm1.btn1Click(Sender: TObject);
const 
  THEJSON =  ''
    +' {                                   '
    +' "data":"MyData",                    '
    +' "errors":["The first error text"],  '
    +' "warnings":null,                    '
    +' "status":0,                         '
    +' "messages":null,                    '
    +' "metadata":{}                       '
    +' }                                   ';    
var
  MainObj, MetaDataObject: TJsonObject;
  i : Integer;
begin
  MainObj := TJsonObject.Parse(THEJSON) as TJsonObject;
  try
    if MainObj['data'].IsNull then
      Memo2.Lines.Add('data = NULL')
    else
      Memo2.Lines.Add('data = ' + MainObj['data'].value);
    if MainObj['warnings'].IsNull then
      Memo2.Lines.Add('warnings = NULL')
    else
      Memo2.Lines.Add('warnings = ' + MainObj['warnings'].value);
    if MainObj['status'].IsNull then
      Memo2.Lines.Add('status = NULL')
    else
      Memo2.Lines.Add('status = ' + MainObj['status'].value);
    if MainObj['messages'].IsNull then
      Memo2.Lines.Add('messages = NULL')
    else
      Memo2.Lines.Add('messages = ' + MainObj['messages'].value);
    if MainObj['errors'].IsNull then
      Memo2.Lines.Add('errors = NULL')
    else
      for I := 0 to MainObj['errors'].count - 1 do
        Memo2.Lines.Add('Error' + IntToStr(i) + ' = ' + MainObj['errors'].items[i]) ;
    
    if MainObj['metadata'].isnull then
      Memo2.Lines.Add('metadata = No object')
    else
    begin
      //this is where it goes pear shaped!
      MetaDataObject := TJsonObject.Parse(MainObj['metadata']) as TJsonObject;   //cannot cast object to string???
      try
        Memo2.Lines.Add('metadata text is ' + MetaDataObject.ToString);
        if MetaDataObject['????].isnull then   / if MetaDataObject['????] is empty then   //what goes here?
          Memo2.Lines.Add('metadata = NULL')
        else                    '
          Memo2.Lines.Add('metadata = ' + MetaDataObject.Value);
      finally
        MetaDataObject.Free;
      end;
    end;
  finally
    MainObj.Free;
  end;
end;

Expected result

data = MyData
warnings = NULL
status = 0
messages = NULL
Error0 = The first error text

followed by

metadata text is {}
metadata = NULL

Or, if there is data there, followed by

metadata text is {"meta_1":"mymeta"}
metadata = mymeta

Actual results

data = MyData
warnings = NULL
status = 0
messages = NULL
Error0 = The first error text

followed by a runtime error 'cannot cast object to string'.

What I have tried

if not Assigned(MetaDataObject) then ...
if not Assigned(MainObj['metadata']) then ...
if Assigned (TJsonObject(MainObj['metadata'])) then
  MetaDataObject := TJsonObject.Parse(MainObj['metadata']) as TJsonObject;

Where I have looked

JSON specification for empty objects

Empty JSON Object check

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
user2834566
  • 775
  • 9
  • 22

1 Answers1

1

You already know how to access child values from a JSON object.

The TJsonObject.Count property will be 0 when a JSON object is non-null but has no child values (ie, is empty). So, first check if the desired value is null, and if not then access the value as a TJsonObject and check if its Count is 0, and act accordingly.

For example:

if MainObj.IsNull('metadata') then
  Memo2.Lines.Add('metadata = No object')
else
begin
  MetaDataObject := MainObj.O['metadata'];
  if MetaDataObject.Count = 0 then
    Memo2.Lines.Add('metadata = empty')
  else
    Memo2.Lines.Add('metadata text is ' + MetaDataObject.ToString);
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Still have problems. Using `if MainObj['metadata'].isnull then Memo2.Lines.Add('metadata = No object') else begin if MainObj['metadata'].count = 0 then Memo2.Lines.Add('metadata is empty') else Memo2.Lines.Add('metadata = ' + MainObj['metadata'].Value); end;` gives 'cannot cast object into array' on the line `if MainObj['metadata'].count = 0 ` – user2834566 Mar 14 '23 at 19:37
  • You need to cast `MainObj['metadata']` to `TJsonObject` before accessing its `count`. The `TJsonObject.Values[]` property (which `MainObj['...']` is calling) returns a `TJsonDataValueHelper`, and `TJsonDataValueHelper.count` only works for arrays, not objects (design issue?), so you need to access `TJsonObject.Count` instead. – Remy Lebeau Mar 14 '23 at 19:38
  • Ah, you beat me to it Remy! I posted my comment and then thought 'I bet I need to cast that first' Got it working just as I saw your comment. So, who is going to post a fully correct answer? – user2834566 Mar 14 '23 at 19:41
  • I already stated as much in my answer above: "*if [not null] then access the value **as a `TJsonObject`** and check if its `Count` is 0...*" But I will add an example for you... – Remy Lebeau Mar 14 '23 at 19:42
  • Opps, Sorry Remy, I didn't notice the answer was from you. - too keen to try out the suggestion. Thank you. Accepted – user2834566 Mar 14 '23 at 19:43