Learning ColdFusion 8: Javascript Object Notation (JSON) Part I - Data Conversion

Posted June 6, 2007 at 6:17 PM

Tags: ColdFusion

JSON stands for Javascript Object Notation. If you have played around with AJAX you have probably come across this at some point. JSON is light-weight data exchange format that can represent many data types and structures as a single string that can be parsed back into its original data structure. ColdFusion 8 has introduced some very cool JSON functionality not only with basic object conversion, but also with web service return values. For this first part of the ColdFusion 8 JSON series, we will concentrate on basic data conversion.

Let's start by looking at the conversion of some standard ColdFusion data types into JSON data:

 Launch code in new window » Download code as text file »

  • <!--- Convert a string. --->
  • <p>
  • String:
  • #SerializeJSON( "Maria Bello" )#
  • </p>
  •  
  • <!--- Convert a number. --->
  • <p>
  • Number:
  • #SerializeJSON( 1967 )#
  • </p>
  •  
  • <!--- Convert a date. --->
  • <p>
  • Date:
  • #SerializeJSON( "April 18, 1967" )#
  • </p>
  •  
  • <!--- Convert a null value. --->
  • <p>
  • Null:
  • #SerializeJSON( JavaCast( "null", 0 ) )#
  • </p>
  •  
  • <!--- Convert a struct. --->
  • <cfset objActress = {
  • Name = "Maria Bello",
  • Attractive = "Extremely"
  • } />
  •  
  • <p>
  • Struct:
  • #SerializeJSON( objActress )#
  • </p>
  •  
  • <!--- Convert an array. --->
  • <p>
  • Array:
  • #SerializeJSON(
  • ListToArray( "Maria,Bello" )
  • )#
  • </p>
  •  
  • <!--- Convert a function. --->
  • <cffunction name="Test">
  • <cfreturn "Tested!" />
  • </cffunction>
  •  
  • <p>
  • Function:
  • #SerializeJSON( Test )#
  • </p>
  •  
  • <!--- Convert a ColdFusion component. --->
  • <!--- CFC (in other file):
  • <cfcomponent>
  • <cfset THIS.Name = "TestCFC" />
  • <cfset VARIABLES.PrivateName = "TestCFC" />
  • </cfcomponent>
  • --->
  •  
  • <p>
  • CFC:
  • #SerializeJSON(
  • CreateObject( "component", "Test" )
  • )#
  • </p>
  •  
  • <!--- Convert a query. --->
  • <cfset qTest = QueryNew( "" ) />
  • <cfset QueryAddColumn(
  • qTest,
  • "name",
  • "CF_SQL_VARCHAR",
  • ListToArray( "Maria", "Kim" )
  • ) />
  •  
  • <p>
  • Query:
  • #SerializeJSON(
  • qTest
  • )#
  • </p>
  •  
  • <!---
  • Convert the query again, but this time don't
  • create two struct entries. By sending the argument
  • serializeQueryByColumn as true, we are using a
  • WDDX standard query structure.
  • --->
  • <p>
  • Query (true):
  • #SerializeJSON(
  • qTest,
  • true
  • )#
  • </p>
  •  
  • <!--- Convert a java object (String Buffer). --->
  • <p>
  • Java:
  • #SerializeJSON(
  • CreateObject(
  • "java",
  • "java.lang.StringBuffer"
  • ).Init( "" )
  • )#
  • </p>

Notice that the above code contains a ColdFusion user defined function as well as a ColdFusion component (CFC). The CFC is defined in another file, but I have included the structure in the comments. The user defined function is defined right above its use. Not all of these values are considered valid JSON data types. If you look at www.json.org, you will see that JSON is designed to support simple values (numbers, strings, dates) as well as structs and arrays. However, nothing wrong with testing the others (especially since the ColdFusion query object is most definitely not a valid JSON type, but it is most definitely supported).

Running the above code, we get the following output:

String: "Maria Bello"

Number: 1967.0

Date: "April 18, 1967"

Null: null

Struct: {"NAME":"Maria Bello", "ATTRACTIVE":"Extremely"}

Array: ["Maria","Bello"]

Function: {"next":null, "Metadata":{"PARAMETERS":[], "NAME":"Test"}, "PagePath":"C:\\Websites\\127394yy6\\json.cfm", "Access":"public", "MethodAttributes":[], "SuperScope":null}

CFC: {"NAME":"TestCFC"}

Query: {"COLUMNS":["NAME"], "DATA":[["Mar"],["a"]]}

Query (true): {"ROWCOUNT":2, "COLUMNS":["NAME"], "DATA":{"name":["Mar","a"]}}

Java: {"Length":null}

Most of these were just simple ColdFusion data values which were converted quite nicely. As you can see, a NULL value comes across as a true Javascript null - nicely done. A function can be serialized, that's pretty cool (in that it doesn't error - the effectiveness of this will be covered later). The ColdFusion component was serialized, but as you can see from the comments vs. the JSON output, the private variable was not carried over. If you think about it, this makes sense - ColdFusion cannot introspect private data - that's what private data does. But, if you don't think about it when you are coding, you might get some unexpected results. The query was serialized nicely in two different ways for two different standards, but neither sends the column data types. Even a Java object can be serialized, but almost zero of its functionality comes over in the JSON data.

NOTE: Just so we are all on the same page here, when I say that some of these values "can be serialized," that does not mean they are being serialized in any meaningful way (ex. Java object). When I say that it can be serialized, I just mean that ColdFusion did not throw any exceptions.

The above is fairly straight forward, but if you look at the function and query serializations, you will see that one of the struct key values is a nested struct. JSON data can be as simple and as complex as you can get with data structures you start with. That's the beauty of JSON - it's a very simple yet very powerful data exchange standard.

The ColdFusion 8 SerializeJSON() method takes two arguments: the first is the data structure that we are going to convert, the second is a boolean flag which pertains to query conversions only. If the flag is set to false (the default value), the query gets converted into an object that has an array of column names (named COLUMNS) and an array or row-arrays (named DATA). For each row-array, the order of the values corresponds to the order of columns in the columns array.

If the SerializeJSON() flag is set to true, the query gets converted into an object that follows the WDDX compliant query standard. In this method, the resultant object contains a struct that is more in line with the way ColdFusion treats query objects internally. The resultant struct has the row count (named ROWCOUNT), the column list array (named COLUMNS), and column-name-indexed struct (named DATA), where each column name points to an array of column values.

The ColdFusion 8 documentation states that because ColdFusion is case insensitive and Javascript is case sensitive, ColdFusion converts all Structure keys to upper case. This way, there is no confusion about the resultant JSON value. Therefore, your JSON-consuming Javascript should expect all upper case object-keys in its resultant objects. However, if you look at the returned values above, you will see that this really only holds true for top-level structs (the actual single Struct conversion). Maybe this is accurate, the documentation is unclear to me. But, if you look at the all the other structs and nested structs, you will see that key values are NOT upper cased. So, just be careful.

Now that we see how to go from ColdFusion to JSON, let's play around with going from JSON to ColdFusion. For consistency, we are going to deserialized the values we serialized above. This will give us a very easy way to see what kind of data gets lost in the JSON conversion process. In order to really do this, we will first start by storing all of our resultant JSON values into a structure for easy reference. If we did not do that, we would have to wrapping the JSON data in quotes and then escaping the internal JSON quotes:

 Launch code in new window » Download code as text file »

  • <!---
  • So that we don't have to deal with strange string
  • conversion, we are going to store our serialized
  • data into a struct. We will then reference this
  • struct for the deserialization.
  • --->
  • <cfset objJSON = {
  • String = SerializeJSON( "Maria Bello" ),
  • Number = SerializeJSON( 1967 ),
  • Date = SerializeJSON( "April 18, 1967" ),
  • Null = SerializeJSON( JavaCast( "null", 0 ) ),
  • Struct = SerializeJSON( objActress ),
  • Array = SerializeJSON(
  • ListToArray( "Maria,Bello" )
  • ),
  • Function = SerializeJSON( Test ),
  • CFC = SerializeJSON(
  • CreateObject( "component", "Test" )
  • ),
  • Query = SerializeJSON( qTest ),
  • QueryTrue = SerializeJSON( qTest, true ),
  • Java = SerializeJSON(
  • CreateObject(
  • "java",
  • "java.lang.StringBuffer"
  • ).Init( "" )
  • )
  • } />
  •  
  •  
  • <!---
  • Now that we have the same JSON data values stored
  • in our JSON struct (same data we converted before),
  • we will convert it back into ColdFusion and dump out
  • the resultant object.
  • --->
  •  
  •  
  • <!--- Convert a string. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.String )#"
  • label="JSON String"
  • />
  •  
  • <!--- Convert a number. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Number )#"
  • label="JSON Number"
  • />
  •  
  • <!--- Convert a date. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Date )#"
  • label="JSON Date"
  • />
  •  
  • <!--- Convert a null. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Null )#"
  • label="JSON Null"
  • />
  •  
  • <!--- Convert a struct. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Struct )#"
  • label="JSON Struct"
  • />
  •  
  • <!--- Convert an array. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Array )#"
  • label="JSON Array"
  • />
  •  
  • <!--- Convert a function. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Function )#"
  • label="JSON Function"
  • />
  •  
  • <!--- Convert a CFC. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.CFC )#"
  • label="JSON CFC"
  • />
  •  
  • <!--- Convert a query. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Query, false )#"
  • label="JSON Query"
  • />
  •  
  • <!--- Convert a query. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.QueryTrue, false )#"
  • label="JSON Query(True)"
  • />
  •  
  • <!--- Convert a java object. --->
  • <cfdump
  • var="#DeserializeJSON( objJSON.Java )#"
  • label="JSON Java"
  • />

Running the above code, the first four value come back as simple values (strings):

Maria Bello
1967
April 18, 1967
null

Even the Javascript null value is represented by the ColdFusion string, "null." This is probably a good thing since ColdFusion will destroy variables who's value is set to null. The rest of JSON data values get converted to more complex data types:


 
 
 

 
ColdFusion DeserializeJSON() With Struct  
 
 
 

 
 
 

 
ColdFusion DeserializeJSON() With Array  
 
 
 

 
 
 

 
ColdFusion DeserializeJSON() With Function  
 
 
 

 
 
 

 
ColdFusion DeserializeJSON() With ColdFusion Component (CFC)  
 
 
 

 
 
 

 
ColdFusion DeserializeJSON() With Query  
 
 
 

 
 
 

 
ColdFusion DeserializeJSON() With Query  
 
 
 

 
 
 

 
ColdFusion DeserializeJSON() With Java  
 
 
 

The ColdFusion array and struct values converted quite nicely. The simple values (string, number, dates, null) also converted nicely back into their ColdFusion counterparts. The null value could be debated, but I am ok with this (as long as this is what you expect). Both queries were converted back into ColdFusion query objects. The rest of the JSON data values converted back into ColdFusion structs. If you take a look at the JSON data values, none of this should be too much of a surprise.

The only real caveat here is the deserialization of the query objects. ColdFusion 8's DeserializeJSON() function takes two arguments: the JSON data value and the strict mapping flag. The strict mapping flag is optional and defaults to true. If you look at the JSON data for the query (either type), you will see that it is just struct notation. If we pass this strict mapping flag in as true (or omit it) then ColdFusion will convert the JSON data back into a struct since this is what the JSON data is defining from a "strict" point of view. However, if we pass in the strict mapping flag as false, we are giving ColdFusion the freedom to actually check to see if the JSON data can be converted to a ColdFusion query, and if so, that is should do that.

Queries have more than just their data values to be considered - queries also have data about their data. By dumping out the GetMetaData() for this query, we can see what else the query object has to tell us:

 Launch code in new window » Download code as text file »

  • <!---
  • Now that we have deserialized the query object
  • (back from JSON), let's dump out the meta data
  • to see what comes back.
  • --->
  • <cfdump
  • var="#GetMetaData( DeserializeJSON( objJSON.QueryTrue, false ) )#"
  • label="Deserialized JSON Query"
  • />

This gives us the following CDump output:


 
 
 

 
ColdFusion DeserializeJSON() With Query Meta Data  
 
 
 

It appears that even though our original query had explicit data types, no query column data types can survive the JSON conversion. Again, looking at the resultant JSON created from the SerializeJSON() function, this should not be too much of a surprise.

Overall, the serializing and deserializing of JSON data works very well for simple values, structs, and arrays. Queries, while not technically proper JSON data types are handled well and leave you the option to use them as structs or queries. Other data types in ColdFusion (Java values, CFC, user defined functions) can be serialized into JSON data, but only in reflection of the fact that they have struct-like properties.

In addition to the conversion methods, ColdFusion also supplies the IsJSON() method which takes a JSON string and determines if it is a valid JSON data string (that you could then pass to DeserializeJSON()).

Download Code Snippet ZIP File

Comments (5)  |  Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page


Related Blog Entries




Reader Comments

@Ben: I think you've stumbled across a pretty big bug. The keys should always return in the same case. The fact that only some of keys are coming back as non-uppercase is a bug that will cause problems down the road.

Have you reported this to Adobe? If not, you should file a bug with them.

Posted by Dan G. Switzer, II on Jun 7, 2007 at 12:10 PM


@Dan,

I am always hesitant to submit bugs because I am never sure what is planned and what is not. I really appreciate you taking the time, not only to actually read through my wordier entries, but also on making these suggestions.

It might have something to do with what they consider a "Struct". For instance, the keys that come back in the Function serialization are not for a "Struct", they are for a Function. It just so happens that most things in ColdFusion have struct-like interaction. So, just like "strict mapping" of the DeserializeJSON(), maybe they are being "strict" in their interpretation.

That being said, I agree, they should all come back upper case to be consistent.

Posted by Ben Nadel on Jun 7, 2007 at 1:38 PM


I have submitted it.

Posted by Ben Nadel on Jun 7, 2007 at 1:40 PM


@Ben:

Glad you submitted this as a bug. It definitely is one.

In order for this to work correctly, they really need to make sure all keys are the same case. Otherwise, it can cause all sorts of issues trying to access keys.

I see this as a pretty severe bug as well. If this makes it in to CF8, then what happens is if they change the behavior later, then all of your JS code could break.

They actually had a similar issue with WDDX a while back. They made some case changes between versions in the way that CFWDDX tag generated code when converting from CFML2JS which made JS stop working.

Posted by Dan G. Switzer, II on Jun 7, 2007 at 2:13 PM


Good to know. I will keep my fingers crossed.

Posted by Ben Nadel on Jun 7, 2007 at 3:11 PM


Post Comment  |  Ask Ben


Home   |   Web Log   |   ColdFusion   |   Projects   |   Resume   |   Job Form   |   Search   |   Contact
Epicenter Consulting - Custom Software Solutions for Business Evolution HostMySite.com - The Leader In ColdFusion Hosting