root/trunk/website/org/sweettweets/JSONUtil.cfc @ 15

Revision 5, 17.8 kB (checked in by DanWilson, 17 years ago)

Initial Commit Of ModelGlue? Website (upgrade to blogcfc 511)

Line 
1<!---
2        JSONUtil.cfc is written and maintained by Nathan Mische,
3        http://jsonutil.riaforge.org/
4       
5        He licenses it under the Apache License, version 2.
6--->
7<cfcomponent displayname="JSONUtil" output="false">
8       
9        <cffunction name="init" output="false">
10                <cfreturn this />
11        </cffunction>
12       
13        <cffunction
14                name="deserialize"
15                access="public"
16                returntype="any"
17                output="false"
18                hint="Converts a JSON (JavaScript Object Notation) string data representation into CFML data, such as a CFML structure or array.">
19                <cfargument
20                        name="JSONVar"
21                        type="string"
22                        required="true"
23                        hint="A string that contains a valid JSON construct, or variable that represents one." />
24                <cfargument
25                        name="strictMapping"
26                        type="boolean"
27                        required="false"
28                        default="true"
29                        hint="A Boolean value that specifies whether to convert the JSON strictly, as follows:
30                                <ul>
31                                        <li><code>true:</code> (Default) Convert the JSON string to ColdFusion data types that correspond directly to the JSON data types.</li>
32                                        <li><code>false:</code> Determine if the JSON string contains representations of ColdFusion queries, and if so, convert them to queries.</li>
33                                </ul>" />       
34               
35                <!--- DECLARE VARIABLES --->
36                <cfset var ar = ArrayNew(1) />
37                <cfset var st = StructNew() />
38                <cfset var dataType = "" />
39                <cfset var inQuotes = false />
40                <cfset var startPos = 1 />
41                <cfset var nestingLevel = 0 />
42                <cfset var dataSize = 0 />
43                <cfset var i = 1 />
44                <cfset var skipIncrement = false />
45                <cfset var j = 0 />
46                <cfset var char = "" />
47                <cfset var dataStr = "" />
48                <cfset var structVal = "" />
49                <cfset var structKey = "" />
50                <cfset var colonPos = "" />
51                <cfset var qRows = 0 />
52                <cfset var qCols = "" />
53                <cfset var qCol = "" />
54                <cfset var qData = "" />
55                <cfset var curCharIndex = "" />
56                <cfset var curChar = "" />
57                <cfset var result = "" />
58                <cfset var unescapeVals = "\\,\"",\/,\b,\t,\n,\f,\r" />
59                <cfset var unescapeToVals = "\,"",/,#Chr(8)#,#Chr(9)#,#Chr(10)#,#Chr(12)#,#Chr(13)#" />
60                <cfset var unescapeVals2 = '\,",/,b,t,n,f,r' />
61                <cfset var unescapetoVals2 = '\,",/,#Chr(8)#,#Chr(9)#,#Chr(10)#,#Chr(12)#,#Chr(13)#' />
62                <cfset var dJSONString = "" />
63               
64                <cfset var _data = Trim(arguments.JSONVar) />
65               
66                <!--- NUMBER --->
67                <cfif IsNumeric(_data)>
68                        <cfreturn Val(_data) />
69               
70                <!--- NULL --->
71                <cfelseif _data EQ "null">
72                        <cfreturn "null" />
73               
74                <!--- BOOLEAN --->
75                <cfelseif ListFindNoCase("true,false", _data)>
76                        <cfreturn _data />
77               
78                <!--- EMPTY STRING --->
79                <cfelseif _data EQ "''" OR _data EQ '""'>
80                        <cfreturn "" />
81               
82                <!--- STRING --->
83                <cfelseif ReFind('^"[^\\"]*(?:\\.[^\\"]*)*"$', _data) EQ 1 OR ReFind("^'[^\\']*(?:\\.[^\\']*)*'$", _data) EQ 1>
84                        <cfset _data = mid(_data, 2, Len(_data)-2) />
85                        <!--- If there are any \b, \t, \n, \f, and \r, do extra processing
86                                (required because ReplaceList() won't work with those) --->
87                        <cfif Find("\b", _data) OR Find("\t", _data) OR Find("\n", _data) OR Find("\f", _data) OR Find("\r", _data)>
88                                <cfset curCharIndex = 0 />
89                                <cfset curChar =  ""/>
90                                <cfset dJSONString = ArrayNew(1) />
91                                <cfloop condition="true">
92                                        <cfset curCharIndex = curCharIndex + 1 />
93                                        <cfif curCharIndex GT len(_data)>
94                                                <cfbreak />
95                                        <cfelse>
96                                                <cfset curChar = mid(_data, curCharIndex, 1) />
97                                                <cfif curChar EQ "\">
98                                                        <cfset curCharIndex = curCharIndex + 1 />
99                                                        <cfset curChar = mid(_data, curCharIndex,1) />
100                                                        <cfset pos = listFind(unescapeVals2, curChar) />
101                                                        <cfif pos>
102                                                                <cfset ArrayAppend(dJSONString,ListGetAt(unescapetoVals2, pos)) />
103                                                        <cfelse>
104                                                                <cfset ArrayAppend(dJSONString,"\" & curChar) />
105                                                        </cfif>
106                                                <cfelse>
107                                                        <cfset ArrayAppend(dJSONString,curChar) />
108                                                </cfif>
109                                        </cfif>
110                                </cfloop>
111                               
112                                <cfreturn ArrayToList(dJSONString,"") />
113                        <cfelse>
114                                <cfreturn ReplaceList(_data, unescapeVals, unescapeToVals) />
115                        </cfif>
116               
117                <!--- ARRAY, STRUCT, OR QUERY --->
118                <cfelseif ( Left(_data, 1) EQ "[" AND Right(_data, 1) EQ "]" )
119                        OR ( Left(_data, 1) EQ "{" AND Right(_data, 1) EQ "}" )>
120                       
121                        <!--- Store the data type we're dealing with --->
122                        <cfif Left(_data, 1) EQ "[" AND Right(_data, 1) EQ "]">
123                                <cfset dataType = "array" />
124                        <cfelseif ReFindNoCase('^\{"ROWCOUNT":[0-9]+,"COLUMNS":\[("[^"]+",?)+\],"DATA":\{("[^"]+":\[[^]]*\],?)+\}\}$', _data, 0) EQ 1 AND NOT arguments.strictMapping>
125                                <cfset dataType = "queryByColumns" />
126                        <cfelseif ReFindNoCase('^\{"COLUMNS":\[("[^"]+",?)+\],"DATA":\[(\[[^]]*\],?)+\]\}$', _data, 0) EQ 1 AND NOT arguments.strictMapping>
127                                <cfset dataType = "query" />
128                        <cfelse>
129                                <cfset dataType = "struct" />
130                        </cfif>
131                       
132                        <!--- Remove the brackets --->
133                        <cfset _data = Trim( Mid(_data, 2, Len(_data)-2) ) />
134                       
135                        <!--- Deal with empty array/struct --->
136                        <cfif Len(_data) EQ 0>
137                                <cfif dataType EQ "array">
138                                        <cfreturn ar />
139                                <cfelse>
140                                        <cfreturn st />
141                                </cfif>
142                        </cfif>
143                       
144                        <!--- Loop through the string characters --->
145                        <cfset dataSize = Len(_data) + 1 />
146                        <cfloop condition="#i# LTE #dataSize#">
147                                <cfset skipIncrement = false />
148                                <!--- Save current character --->
149                                <cfset char = Mid(_data, i, 1) />
150                               
151                                <!--- If char is a quote, switch the quote status --->
152                                <cfif char EQ '"'>
153                                        <cfset inQuotes = NOT inQuotes />
154                                <!--- If char is escape character, skip the next character --->
155                                <cfelseif char EQ "\" AND inQuotes>
156                                        <cfset i = i + 2 />
157                                        <cfset skipIncrement = true />
158                                <!--- If char is a comma and is not in quotes, or if end of string, deal with data --->
159                                <cfelseif (char EQ "," AND NOT inQuotes AND nestingLevel EQ 0) OR i EQ Len(_data)+1>
160                                        <cfset dataStr = Mid(_data, startPos, i-startPos) />
161                                       
162                                        <!--- If data type is array, append data to the array --->
163                                        <cfif dataType EQ "array">
164                                                <cfset arrayappend( ar, this.deserialize(dataStr, arguments.strictMapping) ) />
165                                        <!--- If data type is struct or query or queryByColumns... --->
166                                        <cfelseif dataType EQ "struct" OR dataType EQ "query" OR dataType EQ "queryByColumns">
167                                                <cfset dataStr = Mid(_data, startPos, i-startPos) />
168                                                <cfset colonPos = Find('":', dataStr) />
169                                                <cfif colonPos>
170                                                        <cfset colonPos = colonPos + 1 />       
171                                                <cfelse>
172                                                        <cfset colonPos = Find(":", dataStr) />
173                                                </cfif>
174                                                <cfset structKey = Trim( Mid(dataStr, 1, colonPos-1) ) />
175                                               
176                                                <!--- If needed, remove quotes from keys --->
177                                                <cfif Left(structKey, 1) EQ "'" OR Left(structKey, 1) EQ '"'>
178                                                        <cfset structKey = Mid( structKey, 2, Len(structKey)-2 ) />
179                                                </cfif>
180                                               
181                                                <cfset structVal = Mid( dataStr, colonPos+1, Len(dataStr)-colonPos ) />
182                                               
183                                                <!--- If struct, add to the structure --->
184                                                <cfif dataType EQ "struct">
185                                                        <cfset StructInsert( st, structKey, this.deserialize(structVal, arguments.strictMapping) ) />
186                                               
187                                                <!--- If query, build the query --->
188                                                <cfelseif dataType EQ "queryByColumns">
189                                                        <cfif structKey EQ "rowcount">
190                                                                <cfset qRows = this.deserialize(structVal, arguments.strictMapping) />
191                                                        <cfelseif structKey EQ "columns">                                                               
192                                                                <cfset qCols = this.deserialize(structVal, arguments.strictMapping) />
193                                                                <cfset st = QueryNew(ArrayToList(qCols)) />
194                                                                <cfif qRows>
195                                                                        <cfset QueryAddRow(st, qRows) />
196                                                                </cfif>
197                                                        <cfelseif structKey EQ "data">
198                                                                <cfset qData = this.deserialize(structVal, arguments.strictMapping) />
199                                                                <cfset ar = StructKeyArray(qData) />
200                                                                <cfloop from="1" to="#ArrayLen(ar)#" index="j">
201                                                                        <cfloop from="1" to="#st.recordcount#" index="qRows">
202                                                                                <cfset qCol = ar[j] />
203                                                                                <cfset QuerySetCell(st, qCol, qData[qCol][qRows], qRows) />
204                                                                        </cfloop>
205                                                                </cfloop>
206                                                        </cfif>
207                                                <cfelseif dataType EQ "query">
208                                                        <cfif structKey EQ "columns">
209                                                                <cfset qCols = this.deserialize(structVal, arguments.strictMapping) />
210                                                                <cfset st = QueryNew(ArrayToList(qCols)) />
211                                                        <cfelseif structKey EQ "data">
212                                                                <cfset qData = this.deserialize(structVal, arguments.strictMapping) />
213                                                                <cfloop from="1" to="#ArrayLen(qData)#" index="qRows">
214                                                                        <cfset QueryAddRow(st) />
215                                                                        <cfloop from="1" to="#ArrayLen(qCols)#" index="j">
216                                                                                <cfset qCol = qCols[j] />
217                                                                                <cfset QuerySetCell(st, qCol, qData[qRows][j], qRows) />
218                                                                        </cfloop>
219                                                                </cfloop>
220                                                        </cfif>
221                                                </cfif>
222                                        </cfif>
223                                       
224                                        <cfset startPos = i + 1 />
225                                <!--- If starting a new array or struct, add to nesting level --->
226                                <cfelseif "{[" CONTAINS char AND NOT inQuotes>
227                                        <cfset nestingLevel = nestingLevel + 1 />
228                                <!--- If ending an array or struct, subtract from nesting level --->
229                                <cfelseif "]}" CONTAINS char AND NOT inQuotes>
230                                        <cfset nestingLevel = nestingLevel - 1 />
231                                </cfif>
232                               
233                                <cfif NOT skipIncrement>
234                                        <cfset i = i + 1 />
235                                </cfif>
236                        </cfloop>
237                       
238                        <!--- Return appropriate value based on data type --->
239                        <cfif dataType EQ "array">
240                                <cfreturn ar />
241                        <cfelse>
242                                <cfreturn st />
243                        </cfif>
244               
245                <!--- INVALID JSON --->
246                <cfelse>
247                        <cfthrow message="JSON parsing failure." />
248                </cfif>
249        </cffunction>
250       
251        <cffunction
252                name="serialize"
253                access="public"
254                returntype="string"
255                output="false"
256                hint="Converts ColdFusion data into a JSON (JavaScript Object Notation) representation of the data.">
257                <cfargument
258                        name="var"
259                        type="any"
260                        required="true"
261                        hint="A ColdFusion data value or variable that represents one." />
262                <cfargument
263                        name="serializeQueryByColumns"
264                        type="boolean"
265                        required="false"
266                        default="false"
267                        hint="A Boolean value that specifies how to serialize ColdFusion queries.
268                                <ul>
269                                        <li><code>false</code>: (Default) Creates an object with two entries: an array of column names and an array of row arrays. This format is required by the HTML format cfgrid tag.</li>
270                                        <li><code>true</code>: Creates an object that corresponds to WDDX query format.</li>
271                                </ul>">
272                <cfargument
273                        name="strictMapping"
274                        type="boolean"
275                        required="false"
276                        default="false"
277                        hint="A Boolean value that specifies whether to convert the ColdFusion data strictly, as follows:
278                                <ul>
279                                        <li><code>false:</code> (Default) Convert the ColdFusion data to a JSON string using ColdFusion data types.</li>
280                                        <li><code>true:</code> Convert the ColdFusion data to a JSON string using underlying Java/SQL data types.</li>                                 
281                                </ul>" />
282               
283                <!--- VARIABLE DECLARATION --->
284                <cfset var jsonString = "" />
285                <cfset var tempVal = "" />
286                <cfset var arKeys = "" />
287                <cfset var colPos = 1 />
288                <cfset var md = "" />
289                <cfset var rowDel = "" />
290                <cfset var colDel = "" />
291                <cfset var className = "" />
292                <cfset var i = 1 />
293                <cfset var column = "" />
294                <cfset var datakey = "" />
295                <cfset var recordcountkey = "" />
296                <cfset var columnlist = "" />
297                <cfset var columnlistkey = "" />
298                <cfset var columnJavaTypes = "" />
299                <cfset var dJSONString = "" />
300                <cfset var escapeToVals = "\\,\"",\/,\b,\t,\n,\f,\r" />
301                <cfset var escapeVals = "\,"",/,#Chr(8)#,#Chr(9)#,#Chr(10)#,#Chr(12)#,#Chr(13)#" />
302               
303                <cfset var _data = arguments.var />
304               
305                <cfif arguments.strictMapping>         
306                        <!--- GET THE CLASS NAME --->                   
307                        <cftry>                         
308                                <cfset className = _data.getClass().getName() />                       
309                                <cfcatch type="any">
310                                        <cfset className = "" />
311                                </cfcatch>                     
312                        </cftry>                       
313                </cfif>
314                       
315                <!--- TRY STRICT MAPPING --->
316               
317                <cfif Len(className) AND CompareNoCase(className,"java.lang.String") eq 0>
318                        <cfreturn '"' & ReplaceList(_data, escapeVals, escapeToVals) & '"' />
319               
320                <cfelseif Len(className) AND CompareNoCase(className,"java.lang.Boolean") eq 0>
321                        <cfreturn ReplaceList(ToString(_data), 'YES,NO', 'true,false') />
322               
323                <cfelseif Len(className) AND CompareNoCase(className,"java.lang.Integer") eq 0>
324                        <cfreturn ToString(_data) />
325                       
326                <cfelseif Len(className) AND CompareNoCase(className,"java.lang.Long") eq 0>
327                        <cfreturn ToString(_data) />
328                       
329                <cfelseif Len(className) AND CompareNoCase(className,"java.lang.Float") eq 0>
330                        <cfreturn ToString(_data) />
331                       
332                <cfelseif Len(className) AND CompareNoCase(className,"java.lang.Double") eq 0>
333                        <cfreturn ToString(_data) />                           
334               
335                <!--- BINARY --->
336                <cfelseif IsBinary(_data)>
337                        <cfthrow message="JSON serialization failure: Unable to serialize binary data to JSON." />
338               
339                <!--- BOOLEAN --->
340                <cfelseif IsBoolean(_data) AND NOT IsNumeric(_data)>
341                        <cfreturn ReplaceList(YesNoFormat(_data), 'Yes,No', 'true,false') />                   
342                       
343                <!--- NUMBER --->
344                <cfelseif IsNumeric(_data) AND NOT REFind("^0+[^\.]",_data)>
345                        <cfreturn ToString(_data) />
346               
347                <!--- DATE --->
348                <cfelseif IsDate(_data)>
349                        <cfreturn '"#DateFormat(_data, "mmmm, dd yyyy")# #TimeFormat(_data, "HH:mm:ss")#"' />
350               
351                <!--- STRING --->
352                <cfelseif IsSimpleValue(_data)>
353                        <cfreturn '"' & ReplaceList(_data, escapeVals, escapeToVals) & '"' />
354                       
355                <!--- CUSTOM FUNCTION --->
356                <cfelseif IsCustomFunction(_data)>                     
357                        <cfreturn this.serialize( GetMetadata(_data), arguments.strictMapping) />
358                       
359                <!--- OBJECT --->
360                <cfelseif IsObject(_data)>             
361                        <cfreturn "{}" />               
362               
363                <!--- ARRAY --->
364                <cfelseif IsArray(_data)>
365                        <cfset dJSONString = ArrayNew(1) />
366                        <cfloop from="1" to="#ArrayLen(_data)#" index="i">
367                                <cfset tempVal = this.serialize( _data[i], arguments.serializeQueryByColumns, arguments.strictMapping ) />
368                                <cfset ArrayAppend(dJSONString,tempVal) />
369                        </cfloop>       
370                                       
371                        <cfreturn "[" & ArrayToList(dJSONString,",") & "]" />
372               
373                <!--- STRUCT --->
374                <cfelseif IsStruct(_data)>
375                        <cfset dJSONString = ArrayNew(1) />
376                        <cfset arKeys = StructKeyArray(_data) />
377                        <cfloop from="1" to="#ArrayLen(arKeys)#" index="i">
378                                <cfset tempVal = this.serialize(_data[ arKeys[i] ], arguments.serializeQueryByColumns, arguments.strictMapping ) />
379                                <cfset ArrayAppend(dJSONString,'"' & arKeys[i] & '":' & tempVal) />
380                        </cfloop>
381                                               
382                        <cfreturn "{" & ArrayToList(dJSONString,",") & "}" />
383               
384                <!--- QUERY --->
385                <cfelseif IsQuery(_data)>
386                        <cfset dJSONString = ArrayNew(1) />
387                       
388                        <!--- Add query meta data --->
389                        <cfset recordcountKey = "ROWCOUNT" />
390                        <cfset columnlistKey = "COLUMNS" />
391                        <cfset columnlist = "" />
392                        <cfset dataKey = "DATA" />
393                        <cfset md = GetMetadata(_data) />
394                        <cfset columnJavaTypes = StructNew() />                                 
395                        <cfloop from="1" to="#ArrayLen(md)#" index="column">
396                                <cfset columnlist = ListAppend(columnlist,UCase(md[column].Name),',') />
397                                <cfif StructKeyExists(md[column],"TypeName")>
398                                        <cfset columnJavaTypes[md[column].Name] = getJavaType(md[column].TypeName) />
399                                <cfelse>
400                                        <cfset columnJavaTypes[md[column].Name] = "" />
401                                </cfif>
402                        </cfloop>                               
403                       
404                        <cfif arguments.serializeQueryByColumns>
405                                <cfset ArrayAppend(dJSONString,'"#recordcountKey#":' & _data.recordcount) />
406                                <cfset ArrayAppend(dJSONString,',"#columnlistKey#":[' & ListQualify(columnlist, '"') & ']') />
407                                <cfset ArrayAppend(dJSONString,',"#dataKey#":{') />
408                                <cfset colDel = "">
409                                <cfloop list="#columnlist#" delimiters="," index="column">
410                                        <cfset ArrayAppend(dJSONString,colDel) />
411                                        <cfset ArrayAppend(dJSONString,'"#column#":[') />
412                                        <cfset rowDel = "">     
413                                        <cfloop from="1" to="#_data.recordcount#" index="i">
414                                                <cfset ArrayAppend(dJSONString,rowDel) />
415                                                <cfif arguments.strictMapping AND Len(columnJavaTypes[column])>
416                                                        <cfset tempVal = this.serialize( JavaCast(columnJavaTypes[column],_data[column][i]), arguments.serializeQueryByColumns, arguments.strictMapping ) />
417                                                <cfelse>
418                                                        <cfset tempVal = this.serialize( _data[column][i], arguments.serializeQueryByColumns, arguments.strictMapping ) />
419                                                </cfif>                                                 
420                                                <cfset ArrayAppend(dJSONString,tempVal) />
421                                                <cfset rowDel = ",">   
422                                        </cfloop>
423                                        <cfset ArrayAppend(dJSONString,']') />
424                                        <cfset colDel = ",">
425                                </cfloop>                               
426                                <cfset ArrayAppend(dJSONString,'}') />                 
427                        <cfelse>                       
428                                <cfset ArrayAppend(dJSONString,'"#columnlistKey#":[' & ListQualify(columnlist, '"') & ']') />
429                                <cfset ArrayAppend(dJSONString,',"#dataKey#":[') />                             
430                                <cfset rowDel = "">
431                                <cfloop from="1" to="#_data.recordcount#" index="i">
432                                        <cfset ArrayAppend(dJSONString,rowDel) />
433                                        <cfset ArrayAppend(dJSONString,'[') />
434                                        <cfset colDel = "">                                     
435                                        <cfloop list="#columnlist#" delimiters="," index="column">
436                                                <cfset ArrayAppend(dJSONString,colDel) />
437                                                <cfif arguments.strictMapping AND Len(columnJavaTypes[column])>
438                                                        <cfset tempVal = this.serialize( JavaCast(columnJavaTypes[column],_data[column][i]), arguments.serializeQueryByColumns, arguments.strictMapping ) />
439                                                <cfelse>
440                                                        <cfset tempVal = this.serialize( _data[column][i], arguments.serializeQueryByColumns, arguments.strictMapping ) />
441                                                </cfif>
442                                                <cfset ArrayAppend(dJSONString,tempVal) />
443                                                <cfset colDel=","/>
444                                        </cfloop>                                       
445                                        <cfset ArrayAppend(dJSONString,']') />
446                                        <cfset rowDel = "," />
447                                </cfloop>                               
448                                <cfset ArrayAppend(dJSONString,']') />                 
449                        </cfif>
450                       
451                        <cfreturn "{" & ArrayToList(dJSONString,"") & "}">
452                       
453                <!--- XML --->
454                <cfelseif IsXML(_data)>
455                        <cfreturn '"' & ReplaceList(ToString(_data), escapeVals, escapeToVals) & '"' />
456                                       
457               
458                <!--- UNKNOWN OBJECT TYPE --->
459                <cfelse>
460                        <cfreturn "{}" />
461                </cfif>
462               
463        </cffunction>
464       
465        <cffunction
466                name="getJavaType"
467                access="private"
468                returntype="string"
469                output="false"
470                hint="Maps SQL to Java types. Returns blank string for unhandled SQL types.">
471                <cfargument
472                        name="sqlType"
473                        type="string"
474                        required="true"
475                        hint="A SQL datatype." />                       
476               
477                <cfswitch expression="#arguments.sqlType#">
478                                       
479                        <cfcase value="bit">
480                                <cfreturn "boolean" />
481                        </cfcase>
482                       
483                        <cfcase value="tinyint,smallint,integer">
484                                <cfreturn "int" />
485                        </cfcase>
486                       
487                        <cfcase value="bigint">
488                                <cfreturn "long" />
489                        </cfcase>
490                       
491                        <cfcase value="real,float">
492                                <cfreturn "float" />
493                        </cfcase>
494                       
495                        <cfcase value="double">
496                                <cfreturn "double" />
497                        </cfcase>
498                       
499                        <cfcase value="char,varchar,longvarchar">
500                                <cfreturn "string" />
501                        </cfcase>
502                       
503                        <cfdefaultcase>
504                                <cfreturn "" />
505                        </cfdefaultcase>
506               
507                </cfswitch>
508               
509        </cffunction>
510       
511</cfcomponent>
Note: See TracBrowser for help on using the browser.