1

Can someone please help me with the correct syntax to access a string inside a nested object using JSONDataObjects from Andreas Hausladen

I have a JSON string returned from an API that contains an array of customers followed by a cursor pagination object (meta) eg

  {
  "customers": [
    {
      "id": "CU000DWEQWMRN4",
      "email": "[email protected]",
      "metadata": {
                         "member_name": "BLOGGS jim",
                         "membership_id": "4088"
                       }
    },
    {
      "id": "CU000DVEKR579S",
      "email": "[email protected]",
      "metadata": {
                        "membership_id": "5647"
                         }
    }
  ],
  "meta": {
              "cursors": {
                             "before": null,
                             "after": "ID456"
                             },
                  "limit": 50
               }
}

I an trying to get the values of before and after inside the meta object at the end

What I have tried

var  Obj: TJsonObject;
       AfterID : string;
  
  begin
  Obj := TJsonObject.Parse(theJSON) as TJsonObject;
  AfterID :=    Obj['meta']['cursors']['after']  ; 

Problem I get the error message cannot convert object to string

I also tried

  var  
     Obj: TJsonObject;
     AfterID : string;
   begin   
   obj :=  Obj['meta'];
   Obj := Obj['cursors'];
   AfterID := Obj['after'];

Problem I get the error message cannot convert object to string

I've also tried many other combinations of syntax but I won't clutter the question with all of those!

I am puzzled as I my syntax is correct when doing the same thing with a slightly different JSON retuned from another API that has a simpler cursor pagination structure eg

{
  "items": [
    {
      "event": "accepted",
      "id": "G3wOhh",
      "user-variables": {},
      "log-level": "info",
      "method": "smtp"
    },
    {
      "event": "accepted",
      "id": "KLT56",
      "user-variables": {},
      "log-level": "info",
      "method": "smtp"
    }
  ],
  "paging": {
    "previous": "line 4",
    "first": "line 1",
    "last": "line 12",
    "next": "line 6"
  }
}

with this JSON, using

var
      Obj : TJsonObject;
      nextID : string;
begin
 Obj := TJsonObject.Parse(TheReturnedJSON) as TJDOJsonObject; 
 nextID := Obj['paging']['next'];

I get the correct value returned

2 Answers 2

1

TJsonObject's default [] property is its Values[] property, which returns a TJsonDataValueHelper:

property Values[const Name: string]: TJsonDataValueHelper read GetValue write SetValue; default;

So, reading Obj['cursors'] returns a TJsonDataValueHelper, not a TJsonObject.

TJsonDataValueHelper's default [] property is its O[] property, which returns a TJsonDataValueHelper representing a JSON object, not a JSON string:

property O[const Name: string]: TJsonDataValueHelper read {$IFDEF BCB}GetObj{$ELSE}GetObject{$ENDIF} write SetObject; default;

TJsonDataValueHelper has an S[] property to read a string value:

property S[const Name: string]: string read GetObjectString write SetObjectString;        // returns '' if property doesn't exist, auto type-cast except for array/object

So, try this instead:

AfterID := Obj['meta']['cursors'].S['after']; 
nextID := Obj['paging'].S['next']; 

Alternatively, don't use TJsonDataValueHelper at all. TJsonObject has an O[] property that returns a TJsonObject, and an S[] property for reading a string:

property S[const Name: string]: string read GetString write SetString;        // returns '' if property doesn't exist, auto type-cast except for array/object
...
property O[const Name: string]: TJsonObject read {$IFDEF BCB}GetObj{$ELSE}GetObject{$ENDIF} write SetObject;   // auto creates object on first access

For example:

AfterID := Obj.O['meta'].O['cursors'].S['after']; 
nextID := Obj.O['paging'].S['next']; 
Sign up to request clarification or add additional context in comments.

3 Comments

Ok, Thank you. I have marked this as correct but can I ask where you found this out? The only 'documentation' I can find, here, github.com/ahausladen/JsonDataObjects is very brief, makes no mention of the .O or .S and only has a very simple example. Is there better documentation somewhere?
@user2834566 I got this information by simply looking at the JSONDataObject's source code itself.
Ah OK, that's what I've been trying to do as well but its a bit of a shame such a useful class doesn't have documented somewhere at least a sentence saying what properties and methods do what, or even a quick comment against each one in the source. I spent two or three days off and on trying to work out how to use this class before finally posting here in trepidation of getting shot down by certain other SO users. Not including you Remy, I'm very grateful for your help
1

Incidentally, I'll add to Remy's answer to say that since the value of 'before' in my sample JSON was null, I found I had to use the following code to avoid getting another typecast error when reading that value as Obj.Values['meta'].O['cursors'].S['before'];

var
  temp : variant;
  BeforeID : string;
begin
  //read the value into a variant by using .V just in case it's null. 
  //if it is then reading a string using .S gives an error
  temp := Obj.Values['meta'].O['cursors'].V['before']; 
  if not VarIsNull(temp) then  // it's not a null so we can convert to a string
    BeforeID := VarToStr(temp)
  else                         // it is null so assign whatever string is appropriate  
    BeforeID := '';  

But using .V was just a guess. I'd love to know where all this is documented!

5 Comments

FYI, VarToStr() handles nulls to return a blank string, so you don't need the VarIsNull() check. But I would avoid using Variant when possible. TJsonDataValueHelper has a Typ property to tell you whether a value is a string vs a null vs ..., etc, as well as an IsNull method. Though I'm not sure the best way to get a TJsonDataValueHelper for a non-object/array.
I would probably just say to not use TJsonDataValueHelper at all, use TJsonObject as-is instead, which also has IsNull, eg: var cursors: TJsonObject; BeforeID : string; begin cursors := Obj.O['meta'].O['cursors']; if not cursors.IsNull('before') then BeforeID := cursors.S['before']; Or, I think TJsonObject.S[] handles null for you: BeforeID := Obj.O['meta'].O['cursors'].S['before'];
You are right, this is a bit confusing without proper documentation!
Brilliant, thank you Remy. All that information goes a long way to writing some sensible and readable code using this class. (Must admit though, rather shamefaced, that quite a big front end to a remote database that I've written for my mountaineering association has documentation that is a year or two out of date! One day I'll get around to doing the documentation instead of the development but whilst it's still just me doing the programming its more fun creating than documenting) .
just for anyone else reading this. Remy you are correct that cursors := Obj.O['meta'].O['cursors']; if not cursors.IsNull('before') then... does work OK but Obj.O['meta'].O['cursors'].S['before']` doesn't seem to handle nulls. I'd hoped it would return an empty string but it just gives a conversion error. Never mind, the ` if not cursors.IsNull()` construct is very handy though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.