Article
· 18 hr ago 13m read

InterSystems IRIS JSON 2

This article is a continuation of the IRIS JSON project and features additional methods and insights.

Let's continue with the instance methods

%GetTypeOf(Key)

This instance method is used to determine the JSON data type of the %DynamicObject or %DynamicArray.

It returns one of the following strings:

"null" - JSON null
"boolean" - Either "true" or "false"
"number" - Any numeric value
"oref" - An %ObjectHandle (an ObjectScript oref)
"object" - A nested object
"array" - A nested array
"string" - Normal text string
"unassigned" - The value is unassigned

USER>Set array = [1,"test",true,12.3256,{"version":($ZVersion)},[1,2,3]]
USER>Write array.%GetTypeOf(2)
boolean
USER>Write array.%GetTypeOf(4)
object
USER>Write array.%GetTypeOf(5)
array

 

%GetIterator()

When working with a JSON object whose structure is unknown at runtime, you can utilize the %GetIterator method to iterate through each key-value pair dynamically. This method returns an instance of %Iterator.Object.

To retrieve each key and its corresponding value, employ the %GetNext(.key, .value,.type) method in a loop.

%GetNext(Key, Value, Type)

This method has three output-type formal parameters:

  1. Key: It returns the key in a JSON object, or the index in a JSON array.
  2. Value: It is a value associated with a key or index.
  3. Type: It is an optional parameter that becomes particularly helpful when handling unknown or dynamic JSON structures.

The Type parameter helps avoid <MAXSTRING> errors when a key’s value exceeds InterSystems IRIS’s maximum string length. In such cases, providing this parameter allows the method to automatically convert the value from a string to a stream, ensuring safe and efficient parsing.

USER>Set json = {"username":"Test", "role":"Admin", "data": ("this data exceeded the maxlength of IRIS")} 
USER>Set jsonIter = json.%GetIterator()
USER>While jsonIter.%GetNext(.key,.value,.type) { Write "key: ",key," value : ",value," and type is :",type,! }
key: username value : Test and type is : string
key: role value : Admin and type is : string
key: data value : 7@%Stream.DynamicCharacter and type is : string

 

This approach is especially beneficial when processing dynamic or unpredictable JSON content in InterSystems ObjectScript.

Enhancements in %DynamicAbstractObject: New Methods in Version 2024.1

As of version 2024.1, several new methods have been introduced to the %DynamicAbstractObject (%DAO) class to enhance usability and support for language interoperability (e.g., Python-style access).

Method Overview:

  • toString() It is a convenience wrapper for the %ToJSON() method. It returns the JSON representation of the object as a string.
  • get(key) It is a Python-style wrapper for the %Get() method. It accepts only one formal parameter (key), unlike %Get, which takes up to three (key, default, type).
  • put(key, value, type) It is a Python-style wrapper for %Set. This method supports all three parameters and can be used to assign values to keys with optional type control.
  • rename(currentkey,newkey) - replace the key of the element identified by currentKey with newKey

putAll(object)
It merges the contents of another %DynamicObject into the current object. This parameter must be a valid %DynamicObject instance.

USER>set json = {}
USER>set json1 = {"username":($USERNAME),"roles":($ROLES)}
USER>zw json
json={}  ; <DYNAMIC OBJECT>
USER>do json.putAll(json1)
USER>zw json
json={"username":"_SYSTEM","roles":"%All"}  ; <DYNAMIC OBJECT>
USER>set json={}
USER>do json.put("name","test")
USER>zw json
json={"name":"test"} ; <DYNAMIC OBJECT>
USER>write json.get("name") ; using python style wrapper method get()
test
LEARN

Embedded Python 

Embedded  Python Functions

  • rename(currentkey,newkey) -replace the key of the element identified by currentKey with newKey
  • toJson() / toString() - JSON serialization functions  
/// Embedded Python
Class Sample.DAO.EmpeddedPY Extends %RegisteredObject
{
ClassMethod DynObjSet()
{
	Set object = {}
	Do ..DynObjSetPY(object)
	Write object.%ToJSON()
}

ClassMethod DynObjSetPY(dynamicObject) [ Language = python ]
{
	import iris
	do = iris.cls("%DynamicObject")._New()
	do.put("id",1212)
	do.put("int",12)
	dynamicObject.putAll(do)
}
}

 

Output
USER>do ##class(Sample.DAO.EmpeddedPY).DynObjSet()
object={"id":1212,"int":12}

 

  • addAll(array): This method is employed to iterate over a %DynamicArray and push its elements into the current object. The first parameter value must be a %DynamicArray. This method is chainable, allowing multiple addAll() calls in a single expression.

USER>ZWrite [1,2,3].addAll([4,5,6])

[1,2,3,4,5,6]  ; <DYNAMIC ARRAY>

USER>ZWrite [1,2,3].addAll([4,5,6]).addAll([7,8,9])

[1,2,3,4,5,6,7,8,9]  ; <DYNAMIC ARRAY>

Class Sample.DAO.EmpeddedPY Extends %RegisteredObject
{
ClassMethod DynArraySet()
{
	Set array = []
	Do ..DynArraySetPY(array)
	zw array
}

ClassMethod DynArraySetPY(dynamicArray) [ Language = python ]
{
	import iris
	da = iris.cls("%DynamicArray")._New()
	# used a new add method
	da.add(1)
	da.add(3)
	# use %Push method
	da._Push(2)
	dynamicArray.addAll(da)
}
}

 

Output
USER>do ##class(Sample.DAO.EmpeddedPY).DynArraySet()
array=[1,3,2]  ; <DYNAMIC ARRAY>

 

Deserialization of JSON from Streams and Strings

If an element of a JSON array or JSON object contains more characters than an ObjectScript string can support, the way to manipulate that element is to send it to a %File or a %Stream. 

%FromJSON(string)

The ##class(%DynamicAbstractObject).%FromJSON(input) class method accepts an "input" arguments of the following types:

  • A JSON string containing a JSON array or object.
  • A %Stream object containing a JSON array or object.
  • A %File object.

If the above-mentioned input is successfully parsed, it returns an object reference to either a %DynamicArray or %DynamicObject class. If the parsing is unsuccessful, it will signal an error.

Creating JSON from a File

The %FromJSONFile(file) method does not take a %File object as an argument. It takes a string containing the file name instead. Consider the following replacement:

 

ClassMethod CreateFromFile(FileName As %String)
{
    Set newObject = ##Class(%DynamicAbstractObject).%FromJSONFile(FileName)
    Set newObject = {}.%FromJSON(file)
}

 JSON Serialization

This section will cover converting a %DynamicObject or %DynamicArray into a JSON-formatted string.

%ToJSON(stream)

The %ToJSON(output) method converts the contents of a %DynamicObject or %DynamicArray into a JSON-formatted string. The output can be directed to such various destinations as a device, a file, or a %Stream object. This parameter is optional.

Without Arguments – Output to Current Device

When %ToJSON() is called without any arguments, the resulting JSON is written directly to the current output device (e.g., the terminal):

With a %Stream Object – Output to Stream

When a stream object is passed as the argument, the JSON text is written to the stream instead of the output device.It is practical for storing or transmitting the JSON data programmatically.

 

ClassMethod CreateStreamFromJSON()
{ 
	Set json = {"SomeNumber":42,"SomeString":"a string","SomeArray":["an array element",123]}
	Set stream =##class(%Stream.TmpCharacter).%New()
	Do json.%ToJSON(stream) ; set into stream object
	Write stream.Read()
}

 

Output
USER> Do ##class(Sample.DAO.EmpeddedPY).CreateStreamFromJSON()
{"SomeNumber":42,"SomeString":"a string","SomeArray":["an array element",123]}

 

Abstract Set Query (ASQ)

Abstract Set Query (ASQ) is a superset of the JSON Path Language (JPL), designed for iterating over and querying dynamic objects and arrays. It provides powerful capabilities for expressive and precise querying of hierarchical and structured data.

In ObjectScript, ASQ queries can be executed with the help of the apply() method.

ASQ extends the functionality of JSON Path by supporting the following:

  • Richer and more flexible query expressions
  • Support for data types and metadata
  • Advanced navigation of structured objects

Below you can see a sample JSON array that can be utilized to demonstrate ASQ (Abstract Set Query).Assign this to a dynamic object variable called JSON in ObjectScript:

[{"id":"123","name":"John Doe","gender":"male","visits":[{"date":"2024-12-01","reason":"checkup"},{"date":"2025-02-15","reason":"flu"}]},{"id":"124","name":"Alice Smith","gender":"female","visits":[{"date":"2024-11-10","reason":"allergy"},{"date":"2025-01-20","reason":"annual physical"}]},{"id":"125","name":"Michael Johnson","gender":"male","visits":[{"date":"2025-03-12","reason":"sprained ankle"}]},{"id":"126","name":"Fatima Noor","gender":"female","visits":[{"date":"2024-09-18","reason":"checkup"},{"date":"2024-12-05","reason":"vaccination"},{"date":"2025-06-01","reason":"fever"}]},{"id":"127","name":"Carlos Martinez","gender":"male","visits":[]},{"id":"128","name":"Emily Chen","gender":"female","visits":[{"date":"2025-04-22","reason":"eye exam"},{"date":"2025-05-19","reason":"back pain"}]}]

Example ASQ Queries in ObjectScript

Member Access

  • .fieldName: Accesses a property or member field.
  • .*: Wildcard access to all fields at that level.
  • .field[0]: Accesses an array element inside a named field.

Retrieve all ID values:

Write json.apply("$.id").%ToJSON()

 

Output
["123", "124", "125", "126", "127", "128"]


Get names that start with "J":

Write json.apply("$[*]?(@.name STARTS WITH 'J').name") //["John Doe"]

Array Slicing

  • [0 to 3]: Selects elements at indices 0, 1, 2, 3.
  • [*]: Choose all elements in the array.
  • [last]: Picks the last element in the array.
  • [1, 3, 5]: Selects elements at specified indices.

Retrieve the first visit’s entry for each person:

Write json.apply("$.visits[0]").%ToJSON()

 

Output
[{"date":"2024-12-01","reason":"checkup"},{"date":"2024-11-10","reason":"allergy"},{"date":"2025-03-12","reason":"sprained ankle"},{"date":"2024-09-18","reason":"checkup"},{"date":"2025-04-22","reason":"eye exam"}]

Get the last object in the array: 

Write json.apply("$[last]").%ToJSON()


Get both the first and last objects in the array:

Write json.apply("$[last]").%ToJSON()
//[{"id":"128","name":"Emily Chen","gender":"female","visits":[{"date":"2025-04-22","reason":"eye exam"},{"date":"2025-05-19","reason":"back pain"}]}]

Get both the first and last objects in the array:

Write json.apply("$[last,0]").%ToJSON()
//[{"id":"128","name":"Emily Chen","gender":"female","visits":[{"date":"2025-04-22","reason":"eye exam"},{"date":"2025-05-19","reason":"back pain"}]},{"id":"123","name":"John Doe","gender":"male","visits":[{"date":"2024-12-01","reason":"checkup"},{"date":"2025-02-15","reason":"flu"}]}]

 

JSON SQL Functions

InterSystems IRIS offers a simple and flexible way to create and manipulate JSON directly within SQL, without the need to write any ObjectScript code. Using built-in JSON functions, you can generate and work with JSON data effortlessly.

JSON_OBJECT

It is a conversion function that returns data as a JSON-formatted string. It accepts one or more key–value pairs, where the following conditions apply:

  • Each key is a user-defined literal string enclosed in single quotes.
  • Each value can be a column, expression, or literal.
  • Multiple key–value pairs are separated by commas.
  • The syntax follows standard JSON structure within parentheses.

Example:

JSON_OBJECT('key1' : "value1", 'key2' : "value2", 'key3' : "value3")

You can convert the resulting JSON string into a dynamic object using the %FromJSON() method:

Set json = ##class(%Library.DynamicAbstractObject).%FromJSON(jsonStr)

Note: JSON_OBJECT does not support the asterisk (*) wildcard to include all fields from a table.

SELECT JSON_OBJECT('name':Name,'Id':Id) FROM SAMPLE.PERSON WHERE ID=1

Add null into the JSON

SELECT JSON_OBJECT('name': Name,'Id': Id,'CreateDate': (CASE WHEN CreateDate IS NOT NULL THEN CreateDate ELSE NULL	END),'SSN':(SSN)) FROM Sample.Person WHERE Id = 1

Embedded JSON object inside a JSON object

SELECT JSON_OBJECT('name': Name,'Id': Id,'CreateDate':CreateDate,'gender':JSON_OBJECT('code':gender,'description':'gender info'))  FROM Sample.Person WHERE Id = 1

JSON_ARRAY

It is a conversion function that returns data as a JSON array. JSON_ARRAY does not support the asterisk (*) syntax to specify all fields in a table.

SELECT JSON_ARRAY(name,SSN, ,City ABSENT ON NULL)  FROM Sample.Person WHERE Id = 1

JSON_ARRAYAGG

It is an aggregate function utilized to aggregate values into a JSON array. You can aggregate strings, numbers, and even large objects.

SELECT JSON_ARRAYAGG(State) FROM Sample.Person 

This SQL aggregates the JSON_OBJECT into a JSON array.

SELECT JSON_ARRAYAGG(JSON_OBJECT('id':Id,'Name':Name,'Gender':Gender,'IsActive':IsActive,'default':'available')) FROM SAMPLE.PERSON

 

JSON_TABLE

The JSON_TABLE function is a Table-Valued Function (TVF) employed in SQL to map JSON data into a relational table format. It enables direct querying of JSON content (whether stored as a literal string, in a column, or returned from a function) by extracting values based on JSON Path Language (JPL) expressions.

JSON_TABLE can process the following JSON sources:

  1. Literal JSON strings embedded directly in SQL.
  2. String properties (e.g., VARCHAR columns) that contain valid JSON.
  3. Functions that return JSON, e.g., %Net.GetJSON(<JSON-URL>, <SSL-config>).

Parsing a Literal JSON String

The following example extracts the number and status fields from a JSON array:

SELECT number,status FROM JSON_TABLE(
  '[{"number":12,"status":false}, {"string":"test"}, {"status":true,"number":100.0}]',
  '$'
  COLUMNS ( number varchar(20) PATH '$.number',
             status BIT     PATH '$.status'
    )
)

Parsing JSON from a Column

Assume the class Sample.Users has a VARCHAR(String) property named AdditionalInfo, which stores JSON like: {"id":"123","name":"John Doe","gender":"male","visits":[{"date":"2024-12-01","reason":"checkup"},{"date":"2025-02-15","reason":"flu"}]}

You can use JSON_TABLE to extract the visit and reason information from this “additionalInfo” column. This query flattens the visits array, so each visit becomes a row, showing its date and reason.

select visitDate,reason FROM Sample.Users, JSON_TABLE(AdditionalInfo, '$.visits[*]'
  COLUMNS(
    visitDate DATE PATH '$.date',
    reason    VARCHAR(100) PATH '$.reason'
  )
)

Cloning JSON Objects and Arrays

Unlike other registered or persistent objects, %DynamicObject and %DynamicArray do not support direct cloning via a method like %ConstructClone because such a method is not available for dynamic entities.

To clone a dynamic object or array, you must do the following:

  1. Serialize it to a JSON string or stream using %ToJSON().
  2. Reconstruct the clone with %FromJSON().

Example (utilizing a string):

ClassMethod ConstructJSONClone(JSON)
{
	Set cloneJSON = ##class(%DynamicAbstractObject).%FromJSON(JSON.%ToJSON())
	Return cloneJSON
}
USER>Set json = {"userName": ($UserName)}
USER>Set cloneJson = ##class(%DynamicAbstractObject).%FromJSON(json.%ToJSON())
USER>Write json.%ToJSON()
{"userName":"_SYSTEM"}
USER>Write json.%ToJSON()
{"userName":"_SYSTEM"}

 

Handling Large JSON (avoiding <MAXSTRING> error):

If the JSON structure is large and exceeds IRIS’s maximum string length, serialization using a string will result in a <MAXSTRING> error.

Solution: Employ a stream instead:

ClassMethod ConstructCloneFromLargeJSON(JSON) 
{
	Set stream = ##class(%Stream.TmpCharacter).%New()
	Do JSON.%ToJSON(stream)
	Set cloneJSON = ##class(%DynamicAbstractObject).%FromJSON(stream)
	Return cloneJSON
}

 This approach safely handles large JSON content and creates a deep copy of the original dynamic object or array.

JSON Formatter

The %JSON.Formatter class is utilized to format a %DynamicArray or %DynamicObject and display it in a more human-readable format.

ClassMethod JSONFormatter()
{
	Set json = {"SomeNumber":42,"SomeString":"a string","SomeArray":["an array element",123]}
	Set formatter = ##class(%JSON.Formatter).%New()
	Set formatter.Indent = 51
	Do formatter.Format(json)
}

Common JSON Parsing Errors in InterSystems IRIS

Below you can find some frequent errors that may occur while parsing JSON with the help of the %DynamicObject or %DynamicArray:

  • <MAXSTRING> It occurs when attempting to convert a string that exceeds the maximum string length allowed in IRIS (maximum length of 3,641,144 characters). To handle large strings safely, use the "stream" or "stream>base64" type when retrieving values.
  • <STRINGSTACK> It is raised when the JSON expression is too long or deeply nested for the system to evaluate.
  • <INVALID OREF> It indicates that the reference is not a valid instance of %DynamicObject or %DynamicArray.
  • <THROW>%FromJSON+22^%Library.DynamicAbstractObject.1 *%Exception.General Parsing error 3 Line 1 Offset 1 This error appears when the input to %FromJSON is not a valid JSON object or array.

 

Limitations of Dunder Methods with %DynamicObject in Embedded Python

The %DAO class in InterSystems IRIS stores data as dynamic key-value pairs rather than predefined properties. Since DAO objects are not native Python objects—but proxy objects returned by the iris module to wrap IRIS objects. Python’s dunder methods like __setattr__() and __getattribute__() do not work for setting or retrieving these dynamic properties. Attempting to use these methods results in an AttributeError, as the keys are not recognized as actual Python attributes. Instead, %DynamicObject requires the use of its .set() and .get() methods to manipulate data. Therefore, attribute-style access via dunder methods is not supported due to the dynamic, non-attribute-based structure of %DynamicObject.

Class Sample.EmbeddedPy.DunderMethod Extends %RegisteredObject
{

Property Key As %String;
Property Value As %String;
ClassMethod DunderMethodOnRegObj() [ Language = python ]
{
	import iris
	obj = iris.cls(__name__)._New()
	obj.__setattr__("Key","date")
	obj.__setattr__("Value","01011991")
	#
	print(obj.__getattribute__("Key")) #ouput : date
	print(obj.__getattribute__("Value"))  #ouput : 01011991
}

ClassMethod DunderMethodOnDynamicObj() [ Language = python ]
{
	import iris
	obj = iris.cls("%Library.DynamicObject")._New()
	obj.__setattr__("Key","date")
	obj.__setattr__("Value","01011991")

	print(obj.__getattribute__("Key")) 
	"""<THROW>DebugStub+40^%Debugger.System.1 *%Exception.PythonException <PYTHON EXCEPTION> 
	246 <class 'AttributeError'>: Property Key not found in object of type iris.%Library.DynamicObject"""
	return
}

}
Discussion (0)1
Log in or sign up to continue