Article
· Jan 28, 2019 2m read

Iterate over dynamic object

Here's a sample code to display JSON or dynamic object.

It shows how to iterate over object, get property values and their paths.

Class JSON.Test
{

/// do ##class(JSON.Test).Test()
ClassMethod Test()
{
    set json = "{""a"":1,""b"":2,""c"":{""c1"":3,""c2"":4}, ""d"": [5, {""e_e"":6}, 7]}"
    
    set obj = {}.%FromJSON(json)
    
    do ..Iterate(obj)
}

ClassMethod Iterate(obj As %DynamicAbstractObject, level = 0, path = "obj")
{
    set indent = $j("", level * 4)
    #dim iterator As %Iterator.Array
    set iterator = obj.%GetIterator()
    
    while iterator.%GetNext(.key, .value) {
        set type = obj.%GetTypeOf(key)
        write indent, "Key: ", key, !
        write indent, "Type: ", type, !
        
        if $classname(obj) = "%Library.DynamicArray" {
            set newPath = path _ ".%GetAt(" _ key _ ")"
        } else {
            if $zname(key, 6) = 1 {
                set newPath = path _  "." _ key
            } else {
                set newPath = path _  ".""" _ key _ """"
            }
        }
        
        write indent, "Path: ", newPath, !
        if $isObject(value) {
            write indent, "Value: ", !
            do ..Iterate(value, level + 1, newPath)
        } else {
            write indent, "Value: ", value, !
        }
        write !
    }
}

}

Output from running Test method:

Key: a
Type: number
Path: obj.a
Value: 1
 
Key: b
Type: number
Path: obj.b
Value: 2
 
Key: c
Type: object
Path: obj.c
Value:
    Key: c1
    Type: number
    Path: obj.c.c1
    Value: 3
 
    Key: c2
    Type: number
    Path: obj.c.c2
    Value: 4
 
 
Key: d
Type: array
Path: obj.d
Value:
    Key: 0
    Type: number
    Path: obj.d.%GetAt(0)
    Value: 5
 
    Key: 1
    Type: object
    Path: obj.d.%GetAt(1)
    Value:
        Key: e_e
        Type: number
        Path: obj.d.%GetAt(1)."e_e"
        Value: 6
 
 
    Key: 2
    Type: number
    Path: obj.d.%GetAt(2)
    Value: 7
Discussion (16)7
Log in or sign up to continue

hi, I tried to modify the above ClassMethod iterate using a local variable sessionId inside the while loop.

Doing this I found out, the behavior of this method is not like normal ClassMethods.

The sessionId set in one cycle of the loop in the next cycle is invalid or - if I set  it to a default value before the loop - it has this default value again.

As a temporary solution I then set a global ^SESSIONID and used that inside the loop. In case I kill this global at the end of the method, I have the same effect again. The ^SESSIONID global is undefined inside the loop again.

It looks like my solution is working when I kill the temporary Global outside this method. But this way is needing a lock of the global, which I could avoid if I could use a local variable.

So: Is there a solution for this problem or is this an error in the %DynamicAbstractObject class and I have to live with this behaviour?

    set stream = ##class(%Stream.FileCharacter).%OpenId("/Users/.../data/continents-en.json")

Did you know that you can just open a file as a stream?

And for iterating - this has been in the product for a while now. This is the code for my :pp alias.

ClassMethod pp(set As %AbstractSet, offset As %Integer = 0)
{
#define QUOTE(%val) $zu(144,1,%val)
    try {
        set isLabeled = set."_isLabeled"()
        if (isLabeled) {
            write "{"
            set close = "}"
        } else {
            write "["
            set close = "]"
        }
        set it = set.iterator()
        while it.hasNext() {
            set next = it.next()
            if $isobject(next.value) {
                write !,?(offset+2)
                write:isLabeled $$$QUOTE(next.key),": "
                do ..pp(next.value, offset + 2)
            } else {
                write !,?(offset+2),$select(isLabeled:$$$QUOTE(next.key)_": ",1:""),$$$QUOTE(next.value)
            }
            if it.hasNext() {
                write ","
            }
        }
        write !,?offset,close
    } catch exc {
        write !,"Exception caught: ",exc.AsSQLMessage()
    }
    return
}

This is in %ASQ.SetUtils. My alias, pp - pretty print, is this:

pp        do ##class(%ASQ.SetUtils).pp($*)

 If the Response you get from a REST call is a %DynamicAbstractObject, how do you know how many levels to go through? 

I tried using JSON2Persistant however the naming convention of the tree that it is building it too long for me to upload it into github, so since I have two values that I need to only retrieve I am attempting to iterate through the JSON and just extract those two values

for example, if I only want to pull the pureId and the portalURL from the response object, how can I iterate through the JSON response to get those values only? 

{
  "count": 0,
  "pageInformation": {
    "offset": 0,
    "size": 0
  },
  "items": [
    {
      "pureId": 0,
      "uuid": "196ab1c9-6e60-4000-88cb-4b1795761180",
      "createdBy": "string",
      "createdDate": "1970-01-01T00:00:00.000Z",
      "modifiedBy": "string",
      "modifiedDate": "1970-01-01T00:00:00.000Z",
      "portalUrl": "string",
      "prettyUrlIdentifiers": [
        "string"
      ],
      "previousUuids": [
        "string"
      ],
      "version": "string",
      "startDateAsResearcher": "1970-01-01",
      "affiliationNote": "string",
      "dateOfBirth": "1970-01-01",
      "employeeStartDate": "1970-01-01",
      "employeeEndDate": "1970-01-01",
      "externalPositions": [
        {
          "pureId": 0,
          "appointment": {
            "uri": "string",
            "term": {
              "en_GB": "Some text"
            }
          },
          "appointmentString": {
            "en_GB": "Some text"
          },
          "period": {
            "startDate": {
              "year": 0,
              "month": 1,
              "day": 1
            },
            "endDate": {
              "year": 0,
              "month": 1,
              "day": 1
            }
          },
          "externalOrganization": {
            "uuid": "196ab1c9-6e60-4000-8b89-29269178a480",
            "systemName": "string"
          }
        }
      ],

when I attempted 

    set itr = responseData.%GetIterator()
    while itr.%GetNext(.key, .value) {
      if key = "pureID"{
        set pResponse.pureID = value
      } elseif key = "portalURL" {
        set pResponse.portalURL = value
      }
    }

both the pureID and portalURL came back blank.

You case is simpler, you do know the json structure and all you need is to iterate the "items" array and find all the portalUrl and pureID properties. Note that since there can be many "items", there may be many portalUrl and pureID.
 

	Set itr=responseData.items.%GetIterator()
	while itr.%GetNext(.key, .value) {
		Write value.portalUrl,!
		Write value.pureID,!
	}

P.S.: it's unlikely that the Response you get from a REST call is a %DynamicAbstractObject since, by definition, that's an abstract class and cannot be instantiated, what you actually get is a subclass of %DynamicAbstractObject, a %DynamicObject in this case or a %DynamicArray when the response is an array.

You can still use classes, just add  %JSONIGNOREINVALIDFIELD to ignore unknown properties:

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;

Here's how your example can look like:

Class dc.Item Extends (%RegisteredObject, %JSON.Adaptor)
{

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;

Property pureId As %Integer;

Property portalUrl As %VarString;

}

and the main class:

Class dc.Response Extends (%RegisteredObject, %JSON.Adaptor)
{

Parameter %JSONIGNOREINVALIDFIELD As BOOLEAN = 1;

Property items As list Of Item;

/// do ##class(dc.Response).Test()
ClassMethod Test()
{
	set json = ..Sample()
	set obj = ..%New()
	$$$TOE(sc, obj.%JSONImport(json))
	do obj.DisplayItems()
}

Method DisplayItems()
{
	for i=1:1:..items.Count() {
		set item = ..items.GetAt(i)
		zw item
	}
}

ClassMethod Sample() As %String [ CodeMode = expression ]
{
{
  "count": 0,
  "pageInformation": {
    "offset": 0,
    "size": 0
  },
  "items": [
    {
      "pureId": 0,
      "uuid": "196ab1c9-6e60-4000-88cb-4b1795761180",
      "createdBy": "string",
      "createdDate": "1970-01-01T00:00:00.000Z",
      "modifiedBy": "string",
      "modifiedDate": "1970-01-01T00:00:00.000Z",
      "portalUrl": "string",
      "prettyUrlIdentifiers": [
        "string"
      ],
      "previousUuids": [
        "string"
      ],
      "version": "string",
      "startDateAsResearcher": "1970-01-01",
      "affiliationNote": "string",
      "dateOfBirth": "1970-01-01",
      "employeeStartDate": "1970-01-01",
      "employeeEndDate": "1970-01-01",
      "externalPositions": [
        {
          "pureId": 0,
          "appointment": {
            "uri": "string",
            "term": {
              "en_GB": "Some text"
            }
          },
          "appointmentString": {
            "en_GB": "Some text"
          },
          "period": {
            "startDate": {
              "year": 0,
              "month": 1,
              "day": 1
            },
            "endDate": {
              "year": 0,
              "month": 1,
              "day": 1
            }
          },
          "externalOrganization": {
            "uuid": "196ab1c9-6e60-4000-8b89-29269178a480",
            "systemName": "string"
          }
        }
    ]
    }
	]
}.%ToJSON()
}

}