├── CacheJSON.cls ├── LICENSE.txt ├── README.md └── TestJSON.cls /CacheJSON.cls: -------------------------------------------------------------------------------- 1 | /// Original code from Yonatan http://blog.yonatan.me/2010/03/objectscript-json-decoderencoder.html 2 | Class CacheJSON Extends %String 3 | { 4 | 5 | /* 6 | Original Code By: Yonatan 7 | Date: 03/14/2010 8 | Version: 1.0 9 | 10 | Modified by: Dan McCracken 11 | Date: 10/27/2011 12 | Version 1.1 13 | Description: Added method for encoding a simple cache object, containing simple datatypes. 14 | Added method to return an object as an %ArrayOfDataTypes as a helper when creating a %ListOfDataTypes, 15 | containing an array of objects to be encoded. 16 | Fixed a bug that allowed %String property types to be unescaped when they qualified for $ISVALIDNUM() 17 | Strings containing a period character need to be escaped with quotes. 18 | Added classmethod to return an object loaded with the properties from an encoded JSON string. 19 | 20 | Modified by: Yuval Golan 21 | Date: 12/17/2014 22 | Version 1.2 23 | Description: Fixed a bug that allowed %String property types to be unescaped when they qualified for $ISVALIDNUM() 24 | For example strings containing only numeric characters that start with zero (i.e. "0123") and strings 25 | that start with a plus sign and then numeric characters (i.e. "+123"). 26 | Fixed a bug that returned inner objects as %ArrayOfDataTypes instead of there class type. 27 | */ 28 | 29 | Parameter EscapeChar As COSEXPRESSION = "$LB($LB(""\"",""\\""),$LB($C(13),""\n""),$LB($C(10),""\r""),$LB($C(9),""\t""),$LB("""""""",""\""""""),$LB($C(8),""\b""),$LB($C(12),""\f""))"; 30 | 31 | Parameter UnEscapeChar As COSEXPRESSION = "$LB(""\\"",""\n"",""\r"",""\t"",""\"""""",""\b"",""\f"")"; 32 | 33 | Parameter JSonSlice [ Final, Internal ] = 1; 34 | 35 | Parameter JSonInString [ Final, Internal ] = 2; 36 | 37 | Parameter JSonInArray [ Final, Internal ] = 3; 38 | 39 | Parameter JSonInObject [ Final, Internal ] = 4; 40 | 41 | ClassMethod GetEscapeChars() As %String 42 | { 43 | Quit ..#EscapeChar 44 | } 45 | 46 | ClassMethod SetAux(what As %String, where As %Integer, delim As %String) As %DataType [ Internal ] 47 | { 48 | s aux=##class(%ArrayOfDataTypes).%New() 49 | d aux.SetAt(what,"what") 50 | d aux.SetAt(where,"where") 51 | d aux.SetAt(delim,"delim") 52 | 53 | q aux 54 | } 55 | 56 | /// we know that it's not escaped becase there is _not_ an 57 | /// odd number of backslashes at the end of the string so far 58 | ClassMethod isEscaped(str As %String, c As %String) As %Boolean [ Internal ] 59 | { 60 | s pos=$F(str,c) 61 | q ($L($E(str,1,pos))-$L($REPLACE($E(str,1,pos),"\","")))#2=1 62 | } 63 | 64 | /// Escapes the string. 65 | ClassMethod Escape(str As %String) As %String [ Internal ] 66 | { 67 | for tI=1:1:$LL(..#EscapeChar) { 68 | Set tCharPair=$LG(..#EscapeChar,tI) 69 | Set str=$Replace(str,$LG(tCharPair,1),$LG(tCharPair,2)) 70 | } 71 | Quit str 72 | } 73 | 74 | ClassMethod Unescape(str As %String) As %String [ Internal ] 75 | { 76 | For tI=1:1:$Length(str){ 77 | Set tChar=$ListFind(..#UnEscapeChar,$E(str,tI,tI+1)) 78 | if (tChar>0){ 79 | Set $E(str,tI,tI+1)=$LG($LG(..#EscapeChar,tChar),1) 80 | } 81 | } 82 | Quit str 83 | } 84 | 85 | /// Decode a string JSON. 86 | ClassMethod Decode(str As %String) As %ArrayOfDataTypes 87 | { 88 | #dim stack as %ListOfDataTypes 89 | s matchType=$ZCVT(str,"L") 90 | 91 | q:(matchType="true") "1" 92 | q:(matchType="false") "0" 93 | q:(matchType="null") "" 94 | q:($ISVALIDNUM(matchType)) matchType 95 | q:str?1"""".E1"""" ..Unescape($e(str,2,$l(str)-1)) 96 | //$replace($e(str,2,$l(str)-1),"\""","""") 97 | 98 | // array or object notation 99 | s match=str?1(1"[".E1"]",1"{".E1"}") 100 | s stack=##class(%ListOfDataTypes).%New() 101 | 102 | if match { 103 | if $E(str,1)="[" { 104 | d stack.Insert(..#JSonInArray) 105 | s arr=##class(%ListOfDataTypes).%New() 106 | } 107 | else { 108 | d stack.Insert(..#JSonInObject) 109 | s obj=##class(%ArrayOfDataTypes).%New() 110 | } 111 | 112 | d stack.Insert(..SetAux(..#JSonSlice,1,"false")) 113 | 114 | s chars=$E(str,2,$L(str)-1) 115 | 116 | if chars="" { 117 | if stack.GetAt(1)=..#JSonInArray { 118 | q arr 119 | } 120 | else { 121 | q obj 122 | } 123 | } 124 | 125 | s strlenChars=$L(chars)+1 126 | 127 | s escaped=0 128 | For c=1:1:strlenChars { 129 | s last=stack.Count() 130 | s top=stack.GetAt(last) 131 | 132 | s:(escaped=2) escaped=0 133 | s:(escaped=1) escaped=2 134 | 135 | s substrC2=$E(chars,c-1,c) 136 | if ($E(chars,c,c)="\")&&(escaped=0) s escaped=1 137 | 138 | if $e(chars,c)="" { 139 | s a=22 140 | } 141 | 142 | if (c=strlenChars || ($E(chars,c)=",")) && (top.GetAt("what")=..#JSonSlice) { 143 | // found a comma that is not inside a string, array, etc., 144 | // OR we've reached the end of the character list 145 | s slice = $E(chars, top.GetAt("where"),c-1) 146 | d stack.Insert(..SetAux(..#JSonSlice,c+1,"false")) 147 | if stack.GetAt(1)=..#JSonInArray { 148 | // we are in an array, so just push an element onto the stack 149 | d arr.Insert(..Decode(slice)) 150 | } 151 | elseif stack.GetAt(1)=..#JSonInObject { 152 | // we are in an object, so figure 153 | // out the property name and set an 154 | // element in an associative array, 155 | // for now 156 | 157 | s match=slice?." "1""""1.E1""""." "1":"1.E 158 | if match { 159 | //'name':value par 160 | s key1=$p(slice,":") 161 | s key=..Decode(key1) 162 | 163 | s val=..Decode($P(slice,":",2,$l(slice,":"))) 164 | d obj.SetAt(val, key) 165 | 166 | } 167 | } 168 | } 169 | elseif $E(chars,c)="""" && (top.GetAt("what")'=..#JSonInString) { 170 | // found a quote, and we are not inside a string 171 | d stack.Insert(..SetAux(..#JSonInString,c,$E(chars,c))) 172 | } 173 | elseif $E(chars,c)=top.GetAt("delim") && (top.GetAt("what")=..#JSonInString) && (escaped=0) { 174 | // found a quote, we're in a string, and it's not escaped (look 3 charachters behind, to see the \" is not \\" ) 175 | s last=stack.Count() 176 | s st=stack.RemoveAt(last) 177 | } 178 | elseif ($E(chars,c)="[") && (top.GetAt("what")'=..#JSonInString) && ($CASE(top.GetAt("what"),..#JSonInString:1,..#JSonInArray:1,..#JSonSlice:1,:0)) { 179 | // found a left-bracket, and we are in an array, object, or slice 180 | d stack.Insert(..SetAux(..#JSonInArray,c,"false")) 181 | } 182 | elseif $E(chars,c)="]" && (top.GetAt("what")=..#JSonInArray) { 183 | // found a right-bracket, and we're in an array 184 | s last=stack.Count() 185 | s st=stack.RemoveAt(last) 186 | } 187 | ;modificacio 19/11/08: ..#JSonString -> #JSonInArray 188 | elseif $E(chars,c)="{" && ($CASE(top.GetAt("what"),..#JSonSlice:1,..#JSonInArray:1,..#JSonInObject:1,:0)) { 189 | // found a left-brace, and we are in an array, object, or slice 190 | d stack.Insert(..SetAux(..#JSonInObject,c,"false")) 191 | } 192 | elseif $E(chars,c)="}" && (top.GetAt("what")=..#JSonInObject) { 193 | // found a right-brace, and we're in an object 194 | s last=stack.Count() 195 | s st=stack.RemoveAt(last) 196 | } 197 | 198 | } 199 | 200 | if stack.GetAt(1)=..#JSonInObject { 201 | q obj 202 | } 203 | elseif stack.GetAt(1)=..#JSonInArray { 204 | q arr 205 | } 206 | } 207 | q str 208 | } 209 | 210 | /// Encode a Cache string to a JSON string 211 | ClassMethod Encode(data As %DataType) As %String 212 | { 213 | 214 | if $IsObject(data) { 215 | s key="" 216 | 217 | s typeData=data.%ClassName() 218 | 219 | if typeData="%ArrayOfDataTypes" { 220 | //type object 221 | s key="" 222 | s cad="" 223 | F { 224 | s pData=data.GetNext(.key) 225 | q:key="" 226 | s value=..Encode(pData) 227 | s cad=$S(cad'="":cad_",",1:"")_""""_..Escape(key)_""":"_value 228 | } 229 | q "{"_cad_"}" 230 | } 231 | elseif typeData="%ListOfDataTypes" { 232 | //type array 233 | 234 | s cad="" 235 | f i=1:1:data.Count() { 236 | s tmp=..Encode(data.GetAt(i)) 237 | s cad=$S(i>1:cad_",",1:"")_tmp 238 | } 239 | 240 | s cad="["_cad_"]" 241 | q cad 242 | } 243 | } 244 | elseif ($FIND(data,".")) { 245 | ;; Valid numbers that start with period (ie. .1293394) should be escaped like a string. 246 | 247 | //type string 248 | q """"_..Escape(data)_"""" 249 | } 250 | elseif (+data=data) { 251 | ;; If it's a numeric value then return the value as is. 252 | //type number 253 | q data 254 | 255 | } 256 | else { 257 | //type string 258 | q:data="" "null" 259 | q """"_..Escape(data)_"""" 260 | } 261 | } 262 | 263 | ClassMethod CreateStringPair(pKey As %String, pValue As %String) As %String 264 | { 265 | Quit """"_pKey_""":"""_..Escape(pValue)_"""" 266 | } 267 | 268 | ClassMethod Parse(pStr As CacheJSON) As %ArrayOfDataTypes 269 | { 270 | Quit ##class(CacheJSON).Decode(pStr) 271 | } 272 | 273 | ClassMethod Stringify(pData As %DataType) As CacheJSON 274 | { 275 | Quit ##class(CacheJSON).Encode(pData) 276 | } 277 | 278 | /// Return an encoded JSON string of the object.
279 | /// Uses all object properties that do not start with a % char. 280 | Method GetJSONFromObject() As %String [ CodeMode = objectgenerator ] 281 | { 282 | // Only create this method when compiling other classes this is extended upon 283 | If (%compiledclass.Name '= "Spectrum.Util.CacheJSON") { 284 | 285 | // Wrap the object in an array 286 | Do %code.WriteLine(" Set array = ##class(%ArrayOfDataTypes).%New()") 287 | 288 | // Rip through each property of the class.. that does not start with a % 289 | // Insert each property as a key=>value pair in the array 290 | For i = 1:1:%compiledclass.Properties.Count() { 291 | Set prop = %compiledclass.Properties.GetAt(i).Name 292 | IF ($EXTRACT(prop) '= "%") { 293 | Do %code.WriteLine(" Do array.SetAt(.."_prop_","""_prop_""")") 294 | } 295 | } 296 | 297 | // Return an encoded JSON string of the object 298 | Do %code.WriteLine(" Quit ..Encode(array)") 299 | } 300 | } 301 | 302 | /// Returns the object as an %ArrayOfDataTypes key=>value pair set. 303 | /// This is helpful when trying to return a %ListOfDataTypes in JSON form, by quickly building an array object to Insert. 304 | /// Uses all object properties that do not start with a % char. 305 | Method GetAsArrayOfDataTypes() As %ArrayOfDataTypes [ CodeMode = objectgenerator ] 306 | { 307 | // Only create this method when compiling other classes this is extended upon 308 | If (%compiledclass.Name '= "Spectrum.Util.CacheJSON") { 309 | 310 | // Wrap the object in an array 311 | Do %code.WriteLine(" Set array = ##class(%ArrayOfDataTypes).%New()") 312 | 313 | // Rip through each property of the class.. that does not start with a % 314 | // Insert each property as a key=>value pair in the array 315 | For i = 1:1:%compiledclass.Properties.Count() { 316 | Set prop = %compiledclass.Properties.GetAt(i).Name 317 | IF ($EXTRACT(prop) '= "%") { 318 | Do %code.WriteLine(" Do array.SetAt(.."_prop_","""_prop_""")") 319 | } 320 | } 321 | 322 | // Return an %ArrayOfDataTypes representation of the object 323 | Do %code.WriteLine(" Quit array") 324 | } 325 | } 326 | 327 | /// Returns an OREF populated with the values from the JSON 328 | /// Uses all object properties that do not start with a % char. 329 | ClassMethod GetObjectFromJSON(JSON As %String) As %RegisteredObject [ CodeMode = objectgenerator ] 330 | { 331 | // Only create this method when compiling other classes this is extended upon 332 | If (%compiledclass.Name '= "Spectrum.Util.CacheJSON") { 333 | Do %code.WriteLine(" Set return = ##class("_%compiledclass.Name_").%New()") 334 | 335 | Do %code.WriteLine(" Set myDecodedArray = ..Decode(JSON)") 336 | 337 | // Rip through each property of the class.. that does not start with a % 338 | // Insert each property into the object to return 339 | #dim prop As %Dictionary.CompiledProperty 340 | For i = 1:1:%compiledclass.Properties.Count() { 341 | Set prop = %compiledclass.Properties.GetAt(i) 342 | set propName=prop.Name 343 | IF ($EXTRACT(propName) '= "%") { 344 | set propType=prop.Type 345 | set propTypeClass="" if propType'="" set propTypeClass=##class(%Dictionary.CompiledClass).%OpenId(propType) 346 | set isOBJ=0 if $isobject(propTypeClass),propTypeClass.ClassType'="datatype" set isOBJ=1 347 | //Do %code.WriteLine(" Do array.SetAt(.."_prop_","""_prop_""")") 348 | if (isOBJ) { 349 | Do %code.Write(" set strJSON = ..Encode(myDecodedArray.GetAt("""_propName_"""))") 350 | Do %code.WriteLine(" // As "_propType_" (class)") 351 | Do %code.WriteLine(" Set return."_propName_" = ##class("_propType_").GetObjectFromJSON(strJSON)") 352 | } 353 | else { 354 | Do %code.Write(" Set return."_propName_" = myDecodedArray.GetAt("""_propName_""")") 355 | Do %code.WriteLine(" // As "_propType_" (datatype)") 356 | } 357 | } 358 | } 359 | 360 | Do %code.WriteLine(" Quit return") 361 | } 362 | } 363 | 364 | } 365 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dan McCracken 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CacheJSON 2 | 3 | CacheJSON is a JSON encoder/decoder for [Intersystems Cache](http://www.intersystems.com) based applications and systems. 4 | 5 | It can be used as a stand-alone utility class or extended inside your own custom classes to "JSON Enable" your objects. 6 | 7 | A handy way to validate that the JSON is legal is to copy the string into this site http://json.parser.online.fr/ 8 | 9 | The primary features of CacheJSON are: 10 | 11 | * Decode a single JSON object into an %ArrayOfDataTypes 12 | * Decode nested arrays of JSON objects into a %ListOfObjects containing %ArrayOfDataTypes 13 | * Decode a single JSON object into a custom Cache object 14 | * Encode an %ArrayOfDataTypes to a JSON string 15 | * Encode a custom Cache object class to a JSON string 16 | * Encode a %ListOfObjects containing %ArrayOfDataTypes into a JSON string 17 | * Embed an array as the value of an element 18 | * Convert a Cache object instance into an %ArrayOfDataTypes 19 | 20 | The original code originated from the [Intersystem's Zen Google Group Community](http://groups.google.com/group/intersystems-zen), however, the base for this class was taken from [Yontan's Blog](http://blog.yonatan.me/2010/03/objectscript-json-decoderencoder.html) and added to GitHub by [@mccrackend](http://twitter.com/#!/mccrackend)for proper tracking of enhancements and maintenance. 21 | 22 | Cache does not come with any native JSON support, necessitating a third party utility to translate JSON strings to & from Cache objects for web applications. 23 | 24 | ## Information & Help 25 | 26 | * For information about Intersystems products visit their [website](http://www.intersystems.com). 27 | * Post to the [Intersystems Zen Google Group](http://groups.google.com/group/intersystems-zen) for help or questions. 28 | * See the [GitHub issue tracker](https://github.com/PlanetCache/CacheJSON/issues). 29 | * Visit the Intersystems [online documentation](http://docs.intersystems.com/). 30 | * Send a message to [@PlanetCache](http://twitter.com/#!/PlanetCache) on Twitter. 31 | 32 | ## Installation 33 | 34 | ### Extending an existing %Persistent object 35 | 36 | Import the class into your `namespace` and compile. 37 | 38 | Then simply extend `CacheJSON` on the class you wish to use it with: 39 | 40 | ``` ruby 41 | Class Sample.Person Extends (%Persistent, %Populate, CacheJSON) [ ClassType = persistent, Inheritance = right ] 42 | ```` 43 | 44 | ### Stand-alone utility 45 | 46 | Import the class into your `namespace` and compile. 47 | 48 | Then call the `CacheJSON` class methods from your code. 49 | 50 | ``` ruby 51 | Set encodedList = ##class(CacheJSON).Encode(list) 52 | ```` 53 | 54 | ## Usage 55 | 56 | Below I'll go through some of the common uses and flows you can use with CacheJSON. 57 | 58 | ### Encode an %ArrayOfObjects 59 | 60 | An `%ArrayOfDataTypes` is a simple Key/Value pair dictionary object used in Cache. `CacheJSON` will parse this object into a JSON string with the same key/value pairs contained in this object. Below is sample code to create this array and encode it to a JSON string. 61 | 62 | ``` ruby 63 | Set myArray = ##class(%ArrayOfDataTypes).%New() 64 | Do myArray.SetAt("Dan","FirstName") 65 | Do myArray.SetAt("McCracken","LastName") 66 | Do myArray.SetAt("01/01/1983","DOB") 67 | Set jsonString = ##class(CacheJSON).Encode(myArray) 68 | ```` 69 | 70 | Creates a string like so: 71 | 72 | ``` ruby 73 | {"DOB":"01/01/1983","FirstName":"Dan","LastName":"McCracken"} 74 | ```` 75 | 76 | ### Encode a %ListOfDataTypes containing arrays 77 | 78 | A `%ListOfDataTypes` is a simple array object used in Cache. Insert a bunch of `%ArrayOfDataTypes` into the list, and `CacheJSON` will translate this into a JSON array. Below is sample code using a list: 79 | 80 | ``` ruby 81 | Set arr1 = ##class(%ArrayOfDataTypes).%New() 82 | Do arr1.SetAt("Dan","FirstName") 83 | Do arr1.SetAt("McCracken","LastName") 84 | Do arr1.SetAt("01/01/1983","DOB") 85 | 86 | Set arr2 = ##class(%ArrayOfDataTypes).%New() 87 | Do arr2.SetAt("Ron","FirstName") 88 | Do arr2.SetAt("Sweeney","LastName") 89 | Do arr2.SetAt("12/31/1978","DOB") 90 | 91 | Set list = ##class(%ListOfDataTypes).%New() 92 | Set sc = list.Insert(arr1) 93 | Set sc = list.Insert(arr2) 94 | Set encodedList = ##class(CacheJSON).Encode(list) 95 | ```` 96 | 97 | Creates a string like so: 98 | 99 | ``` ruby 100 | [{"DOB":"01/01/1983","FirstName":"Dan","LastName":"McCracken"},{"DOB":"12/31/1978","FirstName":"Ron","LastName":"Sweeney"}] 101 | ```` 102 | 103 | ### Encode an array as an element value 104 | 105 | Set the value of an item to another `%ArrayOfDataTypes`. Example below: 106 | 107 | ``` ruby 108 | Set message = ##class(%ArrayOfDataTypes).%New() 109 | Do message.SetAt("Posting to Campfire from Cache!","body") 110 | Set payload = ##class(%ArrayOfDataTypes).%New() 111 | Do payload.SetAt(message,"message") 112 | Set jsonPost = ##class(CacheJSON).Encode(payload) 113 | ```` 114 | 115 | Creates a string like so: 116 | 117 | ``` ruby 118 | {"message":{"body":"Posting to Campfire from Cache!"}} 119 | ```` 120 | 121 | ### Encode a %Persistent object 122 | 123 | After you extend the persistent class with the `CacheJSON` class (see instructions above), you can call a method to simply project the object as a JSON string. Example below: 124 | 125 | ``` ruby 126 | Set obj = ##class(Sample.Person).%OpenId(1) 127 | Set jsonString = obj.GetJSONFromObject() 128 | ```` 129 | 130 | Creates a string like so: 131 | 132 | ``` ruby 133 | {"DOB":40434,"MyBool":null,"Name":"Bolt Usain","SSN":"722-81-1666"} 134 | ```` 135 | 136 | ### Decode a single JSON object 137 | 138 | Given a JSON string representing a single object: 139 | 140 | ``` ruby 141 | {"DOB":57311,"Name":"Dan McCracken","SSN":"192-20-3003"} 142 | ```` 143 | 144 | `CacheJSON` creates an `%ArrayOfDataTypes` object containing all the properties using the Decode() method. 145 | 146 | ``` ruby 147 | Set myDecodedArray = ##class(CacheJSON).Decode(encodedString) 148 | Do $System.OBJ.Dump(myDecodedArray) 149 | 150 | +----------------- general information --------------- 151 | | oref value: 3 152 | | class name: %Library.ArrayOfDataTypes 153 | | reference count: 1 154 | +----------------- attribute values ------------------ 155 | | Data("DOB") = 57311 156 | | Data("Name") = "Dan McCracken" 157 | | Data("SSN") = "192-20-3003" 158 | | ElementType = "%String" 159 | ```` 160 | 161 | ### Decode an array of JSON objects 162 | 163 | Given a JSON string representing an array of JSON objects: 164 | 165 | ``` ruby 166 | [{"DOB":33997,"Name":"Koivu,Phyllis Z.","SSN":"676-82-4467"},{"DOB":61685,"Name":"Kelvin,Kristen S.","SSN":"546-95-9170"},{"DOB":53364,"Name":"DeLillo,Alexandra O.","SSN":"566-60-9488"}] 167 | ```` 168 | 169 | `CacheJSON` will decode this array into a `%ListOfDataTypes` with nodes containing `%ArrayOfDataTypes` that represent each decoded object individually. 170 | 171 | ``` ruby 172 | Set myDecodedArray = ##class(User.Util.CacheJSON).Decode(arrString) 173 | Do $System.OBJ.Dump(myDecodedArray) 174 | +----------------- general information --------------- 175 | | oref value: 7 176 | | class name: %Library.ListOfDataTypes 177 | | reference count: 1 178 | +----------------- attribute values ------------------ 179 | | Data(1) = "12@%Library.ArrayOfDataTypes" 180 | | Data(2) = "14@%Library.ArrayOfDataTypes" 181 | | Data(3) = "16@%Library.ArrayOfDataTypes" 182 | | ElementType = "" 183 | | Size = 3 184 | 185 | Do $System.OBJ.Dump(myDecodedArray.GetAt(1)) 186 | +----------------- general information --------------- 187 | | oref value: 12 188 | | class name: %Library.ArrayOfDataTypes 189 | | reference count: 1 190 | +----------------- attribute values ------------------ 191 | | Data("DOB") = 33997 192 | | Data("Name") = "Koivu,Phyllis Z." 193 | | Data("SSN") = "676-82-4467" 194 | | ElementType = "%String" 195 | ```` 196 | 197 | ### Decode a single JSON object into a custom Cache object 198 | 199 | Given a JSON string with keys that map to a custom Cache object: 200 | 201 | ``` ruby 202 | {"DOB":57311,"Name":"Dan McCracken","SSN":"192-20-3003"} 203 | ```` 204 | 205 | When `CacheJSON` is extended on the object you're trying to map the JSON string to, you can create a new object containing the JSON values as properties using the GetObjectFromJSON() method. 206 | 207 | ``` ruby 208 | USER> Set newPerson = ##class(Sample.Person).GetObjectFromJSON(encodedJSON) 209 | USER> Write newPerson.Name 210 | Dan McCracken 211 | ```` 212 | 213 | ### Convert a Cache object into an %ArrayOfDataTypes 214 | 215 | This is a helper method that will translate your object into an `%ArrayOfDataTypes`, allowing you to quickly build up a `%ListOfDataTypes` to Encode() as a return value for your methods. Using this method inside a quick loop generates a desired list: 216 | 217 | ``` ruby 218 | Set list = ##class(%ListOfDataTypes).%New() 219 | For x=1:1:3 { 220 | Set obj = ##class(Sample.Person).%OpenId(x) 221 | Set sc = list.Insert(obj.GetAsArrayOfDataTypes()) 222 | } 223 | Set encodedList = ##class(Sample.Person).Encode(list) 224 | ```` 225 | 226 | This code generates an array of 3 JSON encoded Person objects that look like this: 227 | 228 | ``` ruby 229 | [{"DOB":33997,"Name":"Koivu,Phyllis Z.","SSN":"676-82-4467","Spouse":null},{"DOB":61685,"Name":"Kelvin,Kristen S.","SSN":"546-95-9170","Spouse":null},{"DOB":53364,"Name":"DeLillo,Alexandra O.","SSN":"566-60-9488","Spouse":null}] 230 | ```` 231 | 232 | ## Tests 233 | 234 | There is a set of tests included in the repository that originated from [Yontan's Blog](http://blog.yonatan.me/2010/03/objectscript-json-decoderencoder.html) that is a standard Cache `%UnitTest.TestCase`. 235 | 236 | To run the test, use a command like: 237 | 238 | ``` ruby 239 | Do ##class(%UnitTest.Manager).RunTest("TestJSON:TestJSON","/noload/norecursive/nodelete") 240 | ```` 241 | 242 | ## Contributing 243 | 244 | If you find something that looks like a bug: 245 | 246 | 1. Check the [GitHub issue tracker](http://github.com/PlanetCache/CacheJSON/issues) to see if it's a known issue. 247 | 2. If you don't see anything, create an issue with information on how to reproduce it. 248 | 249 | If you want to contribute an enhancement or a fix: 250 | 251 | 1. Fork the project on GitHub. 252 | 2. Make your changes and (optionally) update the test class. 253 | 3. Commit the changes to your forked repo. 254 | 4. Send a pull request. -------------------------------------------------------------------------------- /TestJSON.cls: -------------------------------------------------------------------------------- 1 | Class JSON.TestJSON Extends %UnitTest.TestCase 2 | { 3 | 4 | Method TestJSON() 5 | { 6 | #Dim tException As %Exception.AbstractException 7 | Try { 8 | Set tEscapeChars=##class(RSA.Data.DT.Simple.JSON).GetEscapeChars() 9 | Set tEscapeChars=tEscapeChars_$LB($LB("[","["),$LB("]","]"),$LB("{","{"),$LB("}","}"),$LB(":",":")) 10 | for tI=1:1:$LL(tEscapeChars){ 11 | For tJ=1:1:$LL(tEscapeChars){ 12 | for tK=1:1:$LL(tEscapeChars){ 13 | 14 | Set tChar1=$LG(tEscapeChars,tI) 15 | Set tChar2=$LG(tEscapeChars,tJ) 16 | Set tChar3=$LG(tEscapeChars,tK) 17 | 18 | Set tJSON="{""name"":"""_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)_" is char""}" 19 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 20 | Set tName=tObj.GetAt("name") 21 | Do $$$AssertEquals(tName,$LG(tChar1,1)_$LG(tChar2,1)_$LG(tChar3,1)_" is char","Escaping for "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)) 22 | 23 | Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)_" is char""}" 24 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 25 | Set tName=tObj.GetAt("name") 26 | Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1)_$LG(tChar3,1)_" is char","Escaping middle for "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)) 27 | 28 | Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)_"""}" 29 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 30 | Set tName=tObj.GetAt("name") 31 | Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1)_$LG(tChar3,1),"Escaping end for "_$LG(tChar1,2)_$LG(tChar2,2)_$LG(tChar3,2)) 32 | 33 | 34 | if (tK=1){ 35 | if (tI=1){ 36 | Set tJSON="{""name"":"""_$LG(tChar1,2)_" is char""}" 37 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 38 | Set tName=tObj.GetAt("name") 39 | Do $$$AssertEquals(tName,$LG(tChar1,1)_" is char","Escaping for "_$LG(tChar1,2)) 40 | 41 | Set tJSON="{""name"":""the "_$LG(tChar1,2)_" is char""}" 42 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 43 | Set tName=tObj.GetAt("name") 44 | Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_" is char","Escaping middle for "_$LG(tChar1,2)) 45 | 46 | Set tJSON="{""name"":""the "_$LG(tChar1,2)_"""}" 47 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 48 | Set tName=tObj.GetAt("name") 49 | Do $$$AssertEquals(tName,"the "_$LG(tChar1,1),"Escaping end for "_$LG(tChar1,2)) 50 | 51 | for tC=97:1:122{ 52 | Set tJSON="{""name"":"""_$LG(tChar1,2)_$C(tC)_"""}" 53 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 54 | Set tName=tObj.GetAt("name") 55 | Do $$$AssertEquals(tName,$LG(tChar1,1)_$C(tC),"Escaping for "_$LG(tChar1,2)_$C(tC)) 56 | } 57 | } 58 | 59 | Set tJSON="{""name"":"""_$LG(tChar1,2)_$LG(tChar2,2)_" is char""}" 60 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 61 | Set tName=tObj.GetAt("name") 62 | Do $$$AssertEquals(tName,$LG(tChar1,1)_$LG(tChar2,1)_" is char","Escaping for "_$LG(tChar1,2)_$LG(tChar2,2)) 63 | 64 | Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_" is char""}" 65 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 66 | Set tName=tObj.GetAt("name") 67 | Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1)_" is char","Escaping middle for "_$LG(tChar1,2)_$LG(tChar2,2)) 68 | 69 | Set tJSON="{""name"":""the "_$LG(tChar1,2)_$LG(tChar2,2)_"""}" 70 | Set tObj=##class(RSA.Data.DT.Simple.JSON).Parse(tJSON) 71 | Set tName=tObj.GetAt("name") 72 | Do $$$AssertEquals(tName,"the "_$LG(tChar1,1)_$LG(tChar2,1),"Escaping end for "_$LG(tChar1,2)_$LG(tChar2,2)) 73 | } 74 | } 75 | } 76 | } 77 | } catch tException { 78 | Do $$$AssertEquals(1,0,"Exception thrown - " _ tException.Code_ ": " _ tException.Name _ " " _ tException.Data _ " " _ tException.Location) 79 | } 80 | } 81 | Method TestLeadingZerosValuesAreEscaped() 82 | { 83 | Set input = "000123" 84 | set output = ##class(App.API.Helper.CacheJSON).Encode(input) 85 | set expected = """"_input_"""" 86 | Do $$$AssertEquals(output, expected, "Checking that a number with leading zeros is escaped") 87 | } 88 | 89 | Method TestZeroValueIsNotEscaped() 90 | { 91 | Set input = "0" 92 | set output = ##class(App.API.Helper.CacheJSON).Encode(input) 93 | set expected = input 94 | Do $$$AssertEquals(output, expected, "Checking that a zero value is not escaped") 95 | } 96 | 97 | Method TestZeroFollowedByDecimalIsNotQuoted() 98 | { 99 | Set input = "0.1" 100 | set output = ##class(App.API.Helper.CacheJSON).Encode(input) 101 | set expected = """"_input_"""" 102 | Do $$$AssertEquals(output, expected, "Checking that a zero value followed by a decimal is not escaped") 103 | } 104 | 105 | Method TestMultipleZerosFollowedByDecimalIsQuoted() 106 | { 107 | Set input = "00.1" 108 | set output = ##class(App.API.Helper.CacheJSON).Encode(input) 109 | set expected = """"_input_"""" 110 | Do $$$AssertEquals(output, expected, "Checking that a zero value followed by a decimal is not escaped") 111 | } 112 | } --------------------------------------------------------------------------------