-
Notifications
You must be signed in to change notification settings - Fork 15
/
CacheJSON.cls
364 lines (307 loc) · 12.1 KB
/
CacheJSON.cls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/// Original code from Yonatan http://blog.yonatan.me/2010/03/objectscript-json-decoderencoder.html
Class CacheJSON Extends %String
{
/*
Original Code By: Yonatan
Date: 03/14/2010
Version: 1.0
Modified by: Dan McCracken
Date: 10/27/2011
Version 1.1
Description: Added method for encoding a simple cache object, containing simple datatypes.
Added method to return an object as an %ArrayOfDataTypes as a helper when creating a %ListOfDataTypes,
containing an array of objects to be encoded.
Fixed a bug that allowed %String property types to be unescaped when they qualified for $ISVALIDNUM()
Strings containing a period character need to be escaped with quotes.
Added classmethod to return an object loaded with the properties from an encoded JSON string.
Modified by: Yuval Golan
Date: 12/17/2014
Version 1.2
Description: Fixed a bug that allowed %String property types to be unescaped when they qualified for $ISVALIDNUM()
For example strings containing only numeric characters that start with zero (i.e. "0123") and strings
that start with a plus sign and then numeric characters (i.e. "+123").
Fixed a bug that returned inner objects as %ArrayOfDataTypes instead of there class type.
*/
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""))";
Parameter UnEscapeChar As COSEXPRESSION = "$LB(""\\"",""\n"",""\r"",""\t"",""\"""""",""\b"",""\f"")";
Parameter JSonSlice [ Final, Internal ] = 1;
Parameter JSonInString [ Final, Internal ] = 2;
Parameter JSonInArray [ Final, Internal ] = 3;
Parameter JSonInObject [ Final, Internal ] = 4;
ClassMethod GetEscapeChars() As %String
{
Quit ..#EscapeChar
}
ClassMethod SetAux(what As %String, where As %Integer, delim As %String) As %DataType [ Internal ]
{
s aux=##class(%ArrayOfDataTypes).%New()
d aux.SetAt(what,"what")
d aux.SetAt(where,"where")
d aux.SetAt(delim,"delim")
q aux
}
/// we know that it's not escaped becase there is _not_ an
/// odd number of backslashes at the end of the string so far
ClassMethod isEscaped(str As %String, c As %String) As %Boolean [ Internal ]
{
s pos=$F(str,c)
q ($L($E(str,1,pos))-$L($REPLACE($E(str,1,pos),"\","")))#2=1
}
/// Escapes the string.
ClassMethod Escape(str As %String) As %String [ Internal ]
{
for tI=1:1:$LL(..#EscapeChar) {
Set tCharPair=$LG(..#EscapeChar,tI)
Set str=$Replace(str,$LG(tCharPair,1),$LG(tCharPair,2))
}
Quit str
}
ClassMethod Unescape(str As %String) As %String [ Internal ]
{
For tI=1:1:$Length(str){
Set tChar=$ListFind(..#UnEscapeChar,$E(str,tI,tI+1))
if (tChar>0){
Set $E(str,tI,tI+1)=$LG($LG(..#EscapeChar,tChar),1)
}
}
Quit str
}
/// Decode a string JSON.
ClassMethod Decode(str As %String) As %ArrayOfDataTypes
{
#dim stack as %ListOfDataTypes
s matchType=$ZCVT(str,"L")
q:(matchType="true") "1"
q:(matchType="false") "0"
q:(matchType="null") ""
q:($ISVALIDNUM(matchType)) matchType
q:str?1"""".E1"""" ..Unescape($e(str,2,$l(str)-1))
//$replace($e(str,2,$l(str)-1),"\""","""")
// array or object notation
s match=str?1(1"[".E1"]",1"{".E1"}")
s stack=##class(%ListOfDataTypes).%New()
if match {
if $E(str,1)="[" {
d stack.Insert(..#JSonInArray)
s arr=##class(%ListOfDataTypes).%New()
}
else {
d stack.Insert(..#JSonInObject)
s obj=##class(%ArrayOfDataTypes).%New()
}
d stack.Insert(..SetAux(..#JSonSlice,1,"false"))
s chars=$E(str,2,$L(str)-1)
if chars="" {
if stack.GetAt(1)=..#JSonInArray {
q arr
}
else {
q obj
}
}
s strlenChars=$L(chars)+1
s escaped=0
For c=1:1:strlenChars {
s last=stack.Count()
s top=stack.GetAt(last)
s:(escaped=2) escaped=0
s:(escaped=1) escaped=2
s substrC2=$E(chars,c-1,c)
if ($E(chars,c,c)="\")&&(escaped=0) s escaped=1
if $e(chars,c)="" {
s a=22
}
if (c=strlenChars || ($E(chars,c)=",")) && (top.GetAt("what")=..#JSonSlice) {
// found a comma that is not inside a string, array, etc.,
// OR we've reached the end of the character list
s slice = $E(chars, top.GetAt("where"),c-1)
d stack.Insert(..SetAux(..#JSonSlice,c+1,"false"))
if stack.GetAt(1)=..#JSonInArray {
// we are in an array, so just push an element onto the stack
d arr.Insert(..Decode(slice))
}
elseif stack.GetAt(1)=..#JSonInObject {
// we are in an object, so figure
// out the property name and set an
// element in an associative array,
// for now
s match=slice?." "1""""1.E1""""." "1":"1.E
if match {
//'name':value par
s key1=$p(slice,":")
s key=..Decode(key1)
s val=..Decode($P(slice,":",2,$l(slice,":")))
d obj.SetAt(val, key)
}
}
}
elseif $E(chars,c)="""" && (top.GetAt("what")'=..#JSonInString) {
// found a quote, and we are not inside a string
d stack.Insert(..SetAux(..#JSonInString,c,$E(chars,c)))
}
elseif $E(chars,c)=top.GetAt("delim") && (top.GetAt("what")=..#JSonInString) && (escaped=0) {
// found a quote, we're in a string, and it's not escaped (look 3 charachters behind, to see the \" is not \\" )
s last=stack.Count()
s st=stack.RemoveAt(last)
}
elseif ($E(chars,c)="[") && (top.GetAt("what")'=..#JSonInString) && ($CASE(top.GetAt("what"),..#JSonInString:1,..#JSonInArray:1,..#JSonSlice:1,:0)) {
// found a left-bracket, and we are in an array, object, or slice
d stack.Insert(..SetAux(..#JSonInArray,c,"false"))
}
elseif $E(chars,c)="]" && (top.GetAt("what")=..#JSonInArray) {
// found a right-bracket, and we're in an array
s last=stack.Count()
s st=stack.RemoveAt(last)
}
;modificacio 19/11/08: ..#JSonString -> #JSonInArray
elseif $E(chars,c)="{" && ($CASE(top.GetAt("what"),..#JSonSlice:1,..#JSonInArray:1,..#JSonInObject:1,:0)) {
// found a left-brace, and we are in an array, object, or slice
d stack.Insert(..SetAux(..#JSonInObject,c,"false"))
}
elseif $E(chars,c)="}" && (top.GetAt("what")=..#JSonInObject) {
// found a right-brace, and we're in an object
s last=stack.Count()
s st=stack.RemoveAt(last)
}
}
if stack.GetAt(1)=..#JSonInObject {
q obj
}
elseif stack.GetAt(1)=..#JSonInArray {
q arr
}
}
q str
}
/// Encode a Cache string to a JSON string
ClassMethod Encode(data As %DataType) As %String
{
if $IsObject(data) {
s key=""
s typeData=data.%ClassName()
if typeData="%ArrayOfDataTypes" {
//type object
s key=""
s cad=""
F {
s pData=data.GetNext(.key)
q:key=""
s value=..Encode(pData)
s cad=$S(cad'="":cad_",",1:"")_""""_..Escape(key)_""":"_value
}
q "{"_cad_"}"
}
elseif typeData="%ListOfDataTypes" {
//type array
s cad=""
f i=1:1:data.Count() {
s tmp=..Encode(data.GetAt(i))
s cad=$S(i>1:cad_",",1:"")_tmp
}
s cad="["_cad_"]"
q cad
}
}
elseif ($FIND(data,".")) {
;; Valid numbers that start with period (ie. .1293394) should be escaped like a string.
//type string
q """"_..Escape(data)_""""
}
elseif (+data=data) {
;; If it's a numeric value then return the value as is.
//type number
q data
}
else {
//type string
q:data="" "null"
q """"_..Escape(data)_""""
}
}
ClassMethod CreateStringPair(pKey As %String, pValue As %String) As %String
{
Quit """"_pKey_""":"""_..Escape(pValue)_""""
}
ClassMethod Parse(pStr As CacheJSON) As %ArrayOfDataTypes
{
Quit ##class(CacheJSON).Decode(pStr)
}
ClassMethod Stringify(pData As %DataType) As CacheJSON
{
Quit ##class(CacheJSON).Encode(pData)
}
/// Return an encoded JSON string of the object.<br>
/// Uses all object properties that do not start with a % char.
Method GetJSONFromObject() As %String [ CodeMode = objectgenerator ]
{
// Only create this method when compiling other classes this is extended upon
If (%compiledclass.Name '= "Spectrum.Util.CacheJSON") {
// Wrap the object in an array
Do %code.WriteLine(" Set array = ##class(%ArrayOfDataTypes).%New()")
// Rip through each property of the class.. that does not start with a %
// Insert each property as a key=>value pair in the array
For i = 1:1:%compiledclass.Properties.Count() {
Set prop = %compiledclass.Properties.GetAt(i).Name
IF ($EXTRACT(prop) '= "%") {
Do %code.WriteLine(" Do array.SetAt(.."_prop_","""_prop_""")")
}
}
// Return an encoded JSON string of the object
Do %code.WriteLine(" Quit ..Encode(array)")
}
}
/// Returns the object as an %ArrayOfDataTypes key=>value pair set.
/// This is helpful when trying to return a %ListOfDataTypes in JSON form, by quickly building an array object to Insert.
/// Uses all object properties that do not start with a % char.
Method GetAsArrayOfDataTypes() As %ArrayOfDataTypes [ CodeMode = objectgenerator ]
{
// Only create this method when compiling other classes this is extended upon
If (%compiledclass.Name '= "Spectrum.Util.CacheJSON") {
// Wrap the object in an array
Do %code.WriteLine(" Set array = ##class(%ArrayOfDataTypes).%New()")
// Rip through each property of the class.. that does not start with a %
// Insert each property as a key=>value pair in the array
For i = 1:1:%compiledclass.Properties.Count() {
Set prop = %compiledclass.Properties.GetAt(i).Name
IF ($EXTRACT(prop) '= "%") {
Do %code.WriteLine(" Do array.SetAt(.."_prop_","""_prop_""")")
}
}
// Return an %ArrayOfDataTypes representation of the object
Do %code.WriteLine(" Quit array")
}
}
/// Returns an OREF populated with the values from the JSON
/// Uses all object properties that do not start with a % char.
ClassMethod GetObjectFromJSON(JSON As %String) As %RegisteredObject [ CodeMode = objectgenerator ]
{
// Only create this method when compiling other classes this is extended upon
If (%compiledclass.Name '= "Spectrum.Util.CacheJSON") {
Do %code.WriteLine(" Set return = ##class("_%compiledclass.Name_").%New()")
Do %code.WriteLine(" Set myDecodedArray = ..Decode(JSON)")
// Rip through each property of the class.. that does not start with a %
// Insert each property into the object to return
#dim prop As %Dictionary.CompiledProperty
For i = 1:1:%compiledclass.Properties.Count() {
Set prop = %compiledclass.Properties.GetAt(i)
set propName=prop.Name
IF ($EXTRACT(propName) '= "%") {
set propType=prop.Type
set propTypeClass="" if propType'="" set propTypeClass=##class(%Dictionary.CompiledClass).%OpenId(propType)
set isOBJ=0 if $isobject(propTypeClass),propTypeClass.ClassType'="datatype" set isOBJ=1
//Do %code.WriteLine(" Do array.SetAt(.."_prop_","""_prop_""")")
if (isOBJ) {
Do %code.Write(" set strJSON = ..Encode(myDecodedArray.GetAt("""_propName_"""))")
Do %code.WriteLine(" // As "_propType_" (class)")
Do %code.WriteLine(" Set return."_propName_" = ##class("_propType_").GetObjectFromJSON(strJSON)")
}
else {
Do %code.Write(" Set return."_propName_" = myDecodedArray.GetAt("""_propName_""")")
Do %code.WriteLine(" // As "_propType_" (datatype)")
}
}
}
Do %code.WriteLine(" Quit return")
}
}
}