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.

JSONLint - The JSON Validator

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 ;