JavaScript ObjectScript Notation (JSON) Builder
Introduction
This article will provide an example of how a Caché local or global array can be serialised into a 'JavaScript ObjectScript Notation (JSON)' string. It has been written with Caché 5.0, but can be used on any later version. For more information on JSON, follow the hyperlinks below.
JSON for the masses
JSON - Wikipedia, the free encyclopedia
JSON - Introduction by Microsoft
Example
The following is an example of how to build an array in Caché that can subsequently be used to create a JSON string.
Set cachewiki("firstName")="John"
Set cachewiki("lastName")="Smith"
Set cachewiki("address","streetAddress")="21 2nd Street"
Set cachewiki("address","city")="New York"
Set cachewiki("address","state")="NY"
Set cachewiki("address","postalCode")="10021"
Set cachewiki("phoneNumbers",1)="212 555-1234"
Set cachewiki("phoneNumbers",2)="646 555-4567"
It is important to note the function that converts this array to a JSON string ignores nodes with both data and children attached (i.e. a subscript level with children cannot contain both data and object members/array elements). Furthermore, all subscript values at the next level must be of the same type (i.e. either numbers or strings), they cannot be mixed. Lastly, the first element in an array should have a subscript value of '1'.
Once you have the array defined, simply pass the array name (with subscript level, if required) into the 'fJSON' routine, see example below.
>Write $$FromArray^fJSON("cachewiki",1)
{
"address": {
"city": "New York",
"postalCode": 10021,
"state": "NY",
"streetAddress": "21 2nd Street"
},
"firstName": "John",
"lastName": "Smith",
"phoneNumbers": [
"212 555-1234",
"646 555-4567"
]
}
The second parameter ('indent') is optional and if not defined all whitespace is removed, see example below. This reduces the amount of space required if transferring to another machine.
>Write $$FromArray^fJSON("cachewiki")
{ "address": { "city": "New York", "postalCode": 10021, "state": "NY", "streetAddress": "21 2nd Street" },
"firstName": "John", "lastName": "Smith", "phoneNumbers": [ "212 555-1234", "646 555-4567" ] }
You can use the following web tool to validate this output.
When I get the time, I will expand this routine to include a function to take a JSON string and populate a local or global array. Any suggestions or comments would be appreciated.
Code
The code to serialise a Caché array is shown below.
fJSON ;;JAVASCRIPT OBJECT NOTATION BUILDER;;V1.00;;NOV2008;;TSG;;
; SET OF FUNCTIONS TO BUILD JSON STRINGS
;
; JSON (JavaScript Object Notation) is a lightweight data-interchange format. JSON is built on two structures:
; - An object is an unordered set of name/value pairs.
; - An array is an ordered collection of values.
;
; Usage:
;
Quit
;
FromArray(array,indent) ;
New qstr,qi,qarray,qnode,qdeep,qstk,qcurr,qtype,qdata,qstop,qtemp
;
; perform some basic validation
If $Get(array)="" Quit ""
If array'?1A.AN.1(1"("1.ANP1")") Quit ""
;
; set some initial values
Set qstr="" If $Get(indent) Set qi=0
;
; open root object or array
Set qnode=array,qstop=1
Set qdeep=$QLength(qnode)
Set qarray=$Query(@qnode)
Do QNode
;
; navigate array
Set qarray=array
For Set qarray=$Query(@qarray) Quit:qarray="" Do QArray Quit:qarray=""
;
; close any open objects and arrays
Set qdeep=-1
Do QClose
;
; basic json string validation
If $Get(qi) ZTrap "ERR"
;
; return json string
Quit qstr
;
StartObject(json,ind) ;
Quit $$Start(json,.ind,"{")
;
NewMember(json,ind,str) ;
Quit $$New(json,.ind,str)
;
EndObject(json,ind) ;
Quit $$End(json,.ind,"}")
;
StartArray(json,ind) ;
Quit $$Start(json,.ind,"[")
;
NewElement(json,ind) ;
Quit $$New(json,.ind)
;
EndArray(json,ind) ;
Quit $$End(json,.ind,"]")
;
Boolean(json,bool) ;
Quit json_$Select(bool:"true",1:"false")
;
Number(json,num) ;
Quit json_+num
;
String(json,str) ;
If str="" Quit json_"null"
Quit json_$Char(34)_$$Escape(str)_$Char(34)
;
; internal function - do not use
Start(json,ind,str) ;
New char
If '$Data(ind) Quit json_str_" "
Set char=$$Back(json,1)
If $Length(char),'(": "[char) Set json=$$Pad(json,ind,$Char(13,10))
Set ind=ind+2
Quit json_str_" "
;
; internal function - do not use
New(json,ind,str) ;
New char
Set char=$$Back(json,1)
If $Length(char),'("{["[char) Set json=json_", "
If $Data(ind) Set json=$$Pad(json,ind,$Char(13,10))
If $Data(str) Quit $$String(json,str)_": "
Quit json
;
; internal function - do not use
End(json,ind,str) ;
If $Data(ind) Set json=json_$Char(13,10),ind=ind-2
Quit $$Pad(json,$Get(ind,1),"",str)
;
; internal function - do not use
Back(str,pos) ;
Quit $Extract(str,$Length(str)-$Get(pos))
;
; internal function - do not use
Pad(str,ind,pre,suf) ;
If $Get(pre)'="" Set str=str_pre
If $Get(ind) Set str=str_$Justify("",ind)
If $Get(suf)'="" Set str=str_suf
Quit str
;
; internal function - do not use
Escape(val) ;
; http://www.json.org/
;
; " - quotation mark (34)
; b - backspace (8)
; f - formfeed (12)
; n - newline (10)
; r - carriage return (13)
; t - horizontal tab (9)
;
New list
For list="\\","//",$C(34)_$C(34),$C(8)_"b",$C(12)_"f",$C(10)_"n",$C(13)_"r",$C(9)_"t" Do
. For Quit:val="" Quit:val'[$E(list) Set val=$P(val,$E(list))_$C(2,3)_$P(val,$E(list),2,999)
. If val[$C(3) Set val=$TR(val,$C(2,3),"\"_$E(list,2))
. Quit
;
Quit val
;
; internal function - do not use
IsNum(value) ;
Quit value=+value
;
; internal function - do not use
QArray ;
; get current subscript level
Set qdeep=$QLength(qarray)
;
; close any open objects and arrays
Do QClose
If qstk="" Set qarray="" Quit
;
; now work through each subscript level
Set qnode=qarray,qstop=0
For qdeep=qstk:1:qdeep Do
. Set qnode=$Piece(qarray,",",0,qdeep)
. If $Extract(qnode,$Length(qnode))'=")" Set qnode=qnode_")"
. Do QNode
. Quit
;
Quit
;
; internal function - do not use
QClose ;
; close any open objects and arrays
Set qstk="",qstop=0
For Set qstk=$Order(qstk(qstk),-1) Quit:qstk="" Do Quit:qstop
. If qstk'>qdeep,$Piece(qstk(qstk),"->")=$QSubscript(qarray,qstk) Set qstk=qstk+1,qstop=1 Quit
. If $Piece(qstk(qstk),"->",2)="object" Set qstr=$$End(qstr,.qi,"}")
. If $Piece(qstk(qstk),"->",2)="array" Set qstr=$$End(qstr,.qi,"]")
. Kill qstk(qstk)
. Quit
;
Quit
;
; internal function - do not use
QNode ;
; get current subscript value and type
Set qcurr=$QSubscript(qarray,qdeep)
Set qtype=$Select($$IsNum(qcurr):"array",1:"object")
;
; if object member
If qtype="object",'qstop Set qstr=$$New(qstr,.qi,qcurr)
;
; if array element
If qtype="array",'qstop Set qstr=$$New(qstr,.qi)
;
; do we need to open an object or array?
If $Data(@qnode)\10 Do Quit
. Set qtype=$Select($$IsNum($QSubscript(qarray,qdeep+1)):"array",1:"object")
. If qtype="object" Set qstr=$$Start(qstr,.qi,"{")
. If qtype="array" Set qstr=$$Start(qstr,.qi,"[")
. Set qstk(qdeep)=qcurr_"->"_qtype
. Quit
;
; ensure array gaps are shown as empty elements
If qtype="array",qcurr'>999 Do
. Set qtemp=$Order(@qarray,-1)+1
. For qtemp=qtemp:1:qcurr-1 Set qstr=$$New(qstr_$Char(34,34),.qi)
. Quit
;
; add data to string
Set qdata=@qarray
If $$IsNum(qdata) Set qstr=$$Number(qstr,qdata) Quit
If (qdata="true")!(qdata="false") Set qstr=qstr_qdata Quit
Set qstr=$$String(qstr,qdata)
;
Quit
;