Using JSON in Caché
Working with Datatypes
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   

ObjectScript has no distinct constants equivalent to JSON true, false, and null, and JSON has no concept of array elements with undefined values. This chapter discusses these mismatches and describes the tools that Caché provides to deal with them.

Discovering the Datatype of a Value with %GetTypeOf()
You can use the %GetTypeOf() method to get the datatype of a dynamic entity member. A dynamic object property or array element can have any one of following datatypes:
Using %GetTypeOf with objects
When you use this method with an object, the argument is the name of the property. For example:
   set dynobj={"prop1":123,"prop2":[7,8,9],"prop3":{"a":1,"b":2}}
   set iter = dynobj.%GetIterator()
   while iter.%GetNext(.name) {write !,"Datatype of "_name_" is "_(dynobj.%GetTypeOf(name))}

Datatype of prop1 is number
Datatype of prop2 is array
Datatype of prop3 is object
Using %GetTypeOf with arrays
When you use this method with an array, the argument is the index of the element. The following example examines a sparse array, where element 2 does not have an assigned value. The example uses a for loop because %GetNext() would skip the unassigned element:
   set dynarray = [12,34]
   set dynarray."3" = "final"
   write dynarray.%ToJSON()

   for index = 0:1:3 {write !,"Datatype of "_index_" is "_(dynarray.%GetTypeOf(index))}
Datatype of 0 is number
Datatype of 1 is number
Datatype of 2 is unassigned
Datatype of 3 is string
Distinguishing between array or object and oref
The datatype of a dynamic entity will be either array or object. A Caché object that is not a dynamic entity will be datatype oref. In the following example, each property of object dyn is one of these three datatypes. Property dynobject is class %DynamicObject, property dynarray is %DynamicArray, and property streamobj is %Stream.GlobalCharacter:
   set dyn={"dynobject":{"a":1,"b":2},"dynarray":[3,4],"streamobj":(##class(%Stream.GlobalCharacter).%New())}
   set iterator=dyn.%GetIterator()
   while iterator.%GetNext(.key,.val) { write !, "Datatype of "_key_" is: "_dyn.%GetTypeOf(key) }

Datatype of dynobject is: object
Datatype of dynarray is: array
Datatype of streamobj is: oref
Overriding a Default Datatype with %Set() or %Push()
By default, the system automatically interprets a %Set() or %Push() value argument as an object datatype (object, array, or oref) or a Caché literal datatype (string or number). You can not directly pass JSON literals null, true or false as values because the argument is interpreted as a Caché literal or expression. For example, the following code throws an error because Caché interprets true as a variable name:
   do o.%Set("prop3",true)

DO o.%Set("prop3",true)
Caché uses "" (an empty string) for null, 0 for boolean false, and a non-zero number for boolean true. To deal with this problem, %Set() and %Push() take an optional third argument to specify the datatype of the value. The third argument can be JSON boolean or null. For example:
   write {}.%Set("a",(2-4)).%Set("b",0).%Set("c","").%ToJSON()

   write {}.%Set("a",(2-4),"boolean").%Set("b",0,"boolean").%Set("c","","null").%ToJSON()
The third argument can also be string or number if the value could be interpreted as a number:
   write [].%Push("023"_"04").%Push(5*5).%ToJSON()

   write [].%Push(("023"_"04"),"number").%Push((5*5),"string").%ToJSON()
Resolving JSON Null and Boolean Values
In JSON syntax, the values true, false, and null are distinct from values 1, 0, and "" (empty string), but ObjectScript does not make this distinction. When JSON values are retrieved from an element or property, they are always cast to ObjectScript-compatible values. This means that JSON true is always returned as 1, false as 0, and null as "". In most cases this will be the desired result, since the return value can be used in a Cache expression without first converting it from JSON format. The dynamic entity retains the original JSON or Caché value internally, so you can use %GetTypeOf() to identify the actual datatype if necessary.
In the following example, the dynamic array constructor specifies JSON true, false, and null values, numeric and string literal values, and ObjectScript dynamic expressions (which evaluate to Caché boolean values 1 and 0):
   set test = [true,1,(1=1),false,0,(1=2),"",null]
   write test.%ToJSON()
As you can see above, the values assigned in the constructor have been preserved in the resulting dynamic array, and are displayed properly when serialized as a JSON string.
The following example retrieves and displays the array values. As expected, JSON values true, false, and null are cast to Caché-compatible values 1, 0, and "":
   set iter = test.%GetIterator()
   while iter.%GetNext(.key,.val){write "/"_val_"/ "}
/1/ /1/ /1/ /0/ /0/ /0/ // //
This example uses %GetNext(), but you would get the same results if you retrieved values with %Get(), %Pop(), or dot syntax.
When necessary, you can use the %GetTypeOf() method to discover the original datatype of the value. For example:
   set iter = test.%GetIterator()
   while iter.%GetNext(.key,.val) {write !,key_": /"_test.%Get(key)_"/ = "_test.%GetTypeOf(key)}
0: /1/ = boolean
1: /1/ = number
2: /1/ = number
3: /0/ = boolean
4: /0/ = number
5: /0/ = number
6: // = string
7: // = null
Datatypes in Dynamic Objects
Although this chapter concentrates on dynamic arrays, the same datatype conversions apply to dynamic object values. The examples in this section will work exactly the same if dynamic array test is defined as a dynamic object:
   set test = {"0":true,"1":1,"2":(1=1),"3":false,"4":0,"5":(1=2),"6":"","7":null}
Except for this line, none of the example code has to be changed. The property names in this object are numeric strings corresponding to the index numbers of the original array, so even the output will be identical.
Resolving Null, Empty String, and Unassigned Values
Although you can assign a JSON null value to an element or property, the value will always be returned as "" (Caché empty string). An empty string will also be returned if you attempt to get the value of an unassigned element. You can use %GetTypeOf() to identify the actual datatype in each case.
This example will test a sparse array containing a JSON null value and an empty string. Although array element 2 has no assigned value, it will be represented in the JSON string by a null:
   set array = [null,""]
   do array.%Set(3,"last")
   write array.%ToJSON()
In most cases you would use %GetNext() to retrieve array values, but this example uses a for loop to return unassigned values that %GetNext() would skip. The index number of the last element is array.%Size()-1, but the loop counter is deliberately set to go past the end of the array:
   for i=0:1:(array.%Size()) {write !,i_". value="""_array.%Get(i)_""" type="_array.%GetTypeOf(i)}
0. value="" type=null
1. value="" type=string
2. value="" type=unassigned
3. value="last" type=string
4. value="" type=unassigned
In this example, %Get() returns an empty string in four different cases:
  1. element 0 is a JSON null value, which %GetTypeOf() identifies as datatype null.
  2. element 1 is an empty string, which is identified as datatype string.
  3. Element 2 has no value, and is identified as datatype unassigned.
  4. Although element 3 is the last one in the array, the example attempts to get a datatype for non-existent element 4, which is also identified as datatype unassigned. Valid array index numbers will always be less than array.%Size().
The distinction between null and unassigned is Caché metadata that will not be preserved when a dynamic entity is serialized to a JSON string. All unassigned elements will be serialized as null values. See Understanding Sparse Arrays and Unassigned Values for details.