├── 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 | }
--------------------------------------------------------------------------------