JSONUtil Now in RIAForge SVN

Just a quick post to let folks know the code and unit tests for JSONUtil are now in SVN at RIAForge.

Introducing JSONUtil

After getting into another discussion last week about the problems with Serialize/DeserializeJSON and ColdFusion's implicit type conversion I decided to try to do something about it. Over the weekend I put together a proof of concept which I've released as JSONUtil. From the RIAForge project site:

The JSONUtil project has two goals: to provide JSON serialization and deserialization methods for CFMX 7 which are compatible with ColdFusion 8's Serialize/DeserializeJSON functions and to provide better mapping between CF/Java/SQL data types and JavaScript data types than ColdFusion 8's SerializeJSON function.

The JSONUtil project currently consists of the JSONUtil component. This component has two public methods, serialize and deserialize, which have method signatures similar to ColdFuison 8's built in Serialize/DeserializeJSON functions. The JSONUtil deserialize method takes the same parameters as DeserializeJSON, JSONVar and strictMapping, and should produce the same results as that function. The JSONUtil serialize method takes the var and serializeQueryByColumns parameters, just as ColdFusion 8's built in SerializeJSON function, however this method also takes an optional third parameter, strictMapping. If the strictMapping parameter is ignored the serialize method should produce the same results as ColdFusion 8's built in SerializeJSON function. If the Boolean strictMapping parameter is set to true then the serialize method attempts to convert the ColdFusion data to a JSON string using underlying Java/SQL data types as opposed to using ColdFusion's implicit type conversion conventions.

The code for the JSONUtil component is based on that of the json.cfc from the CFJSON project, http://www.epiphantastic.com/cfjson. This project probably would not have happened were it not for CFJSON. Thank you to Jehiah Czebotar, Thomas Messier, and everyone who has contributed to the CFJSON project.

To expand on the RIAForge description a bit further, the strictMapping attribute I've added to the serialize method forces the CF data to be serialized based on it's underlying Java data type. For example the following code:

<cfset JSONUtil = CreateObject("component","JSONUtil") />
<cfset x = "123" />
<cfoutput> #JSONUtil.serialize(var=x, strictMapping=true)# </cfoutput>

will produce the following JSON string:

"123"

SerializeJSON or JSONUtil.serialize without the strictMapping attribute will produce the following JSON string:

123

Note the lack of quotes, indicating a JavaScript number as opposed to a string.

The strictMapping attribute also applies to query data as well. If you have a varchar column, its data will always be serialized as string data, even if the string is a numeric or Boolean value in ColdFusion. The strictMapping option also allows you to use JavaCast to help ensure your data values are serialized properly.

There is, however, one caveat to the strictMapping option. In ColdFusion most values are stored as strings. This means that both x and y below:

<cfset x = "123" />
<cfset y = 123 />

will be serialized as:

"123"

If you want numeric values to be serialized as such when using strictMapping you will need to use the Val or JavaCast functions:

<cfset x = Val(123) />
<cfset y = JavaCast("int",123) />

(Note: you do not need to do this for query values as query columns are automatically cast to the appropriate Java type by JSONUtil.)

So head over to RIAForge and check it out, and be sure to report any issues via the bug tracker.

Thanks again to the CFJSON project, and thanks to Todd Sharp for pointing out that you can get column data types from a CF query using GetMetadata().

ColdFire 1.2.95.100 and a CF to JSON Gotcha

A new version of ColdFire is up on RIAForge. This is an extension only update that fixes a bug related to JSON serialization/deserialization of queryparam values.

The problem was reported and fixed by Shane Bradly. He noticed that he was not seeing the SQL for some of his queries in the ColdFire Query tab. His investigation found that passing a numeric value for a text queryparam type such as cf_sql_varchar or cf_sql_char caused the following JavaScript in the ColdFire extension to fail:

[More]

Adobe ColdFusion Inconsistencies

I've run into a couple of ColdFusion quirks over the past few days that I thought I'd share. (I should note that these were observed on Adobe CF 8.)

SerializeJSON and Boolean Strings

If you try to serialize the strings "Yes","No","True" or "False" using SerializeJSON, ColdFusion will convert these strings to boolean values. (i.e. "Yes" becomes true, "False" becomes false). This is because ColdFusion is weakly typed and uses some pretty liberal implicit conversion rules when it comes to boolean evaluation. I see this as an issue for the purposes of searializing and deserializing JSON for two reasons. First, there is the potential to lose data. If you serialize the string "Yes" and then deserialize the JSON that ColdFusion generates you are left with true, which is obviously a totally different value than what you started with. Second, it is not really consistent behavior because SerialzeJSON does not convert 1 or 0 (also ColdFusion booleans) to the boolean values true and false. I think the better approach here would be to serialize all strings as strings and only serialize "real" boolean values as booleans.

IsXML()

The other inconsistency I ran into was with IsXML. If you pass something other than a string to IsXML the function throws an error. This is different than the documented behavior which states:

Returns

True, if the function parameter is a string that contains well-formed XML text; False, otherwise.

This is also different than the other CFML decision functions which simply return false if passed a function parameter they can't handle. (See IsXmlAttribute for example.)

I think the IsXML issue could definitely be consider a bug, and while the SerializeJSON issue is a little fuzzy, I've gone ahead and reported both as bugs.

ColdFire 1.2 Released

The new beta for Firebug was released over the weekend so I'm happy to say I'm releasing ColdFire 1.2 into the wild. Head over to the RIA Forge site to check it out.

[More]

ColdFire SVN

As you may have heard Ray Camden and Rob Gonda recently made some updates to RIAForge which allow project owners to share SVN access. After this update Ray was kind enough to give me access to the ColdFire repository and since then I've done some reorganizing. So, what will you find in the ColdFire SVN repo?

[More]

ColdFire Security Update

A little over a week ago I became aware a 0-day security exploit in ColdFire. I finally have a working fix which Ray has just released over at RIAForge. I strongly recommend that all ColdFire users download and install the latest version (1.003) as soon as possible.

Spry AutoSuggest With Multiple Parameters

When loading datasets from the server for for the Spry AutoSugget widget, Spry passes the value of the search field as a parameter to the dataset URL. This works fine, but what if the suggestions you want to provide are based on a combination of form fields?

To give you a better of idea of what I mean, here is an example of a search form I was working on earlier today: (Note: this form doesn't work.)

Find users within of the following :

I wanted to provide suggestions for the search term, but I wanted to limit those suggestions to only the country (U.S. or Canada) and type (City or Postal Code) selected. So if a user selects U.S. Cities they should only get U.S cities as suggestions, no Canadian cities or postal codes of any kind. This turned out to be fairly easy but I though I'd share my approach in case others are interested.

Basically what I needed to do is append multiple parameters to the dataset URL. To accomplish this I added a new function to the Spry AugoSuggest widget named addReplaceParams:

Spry.Widget.AutoSuggest.prototype.addReplaceParams = function(params)
{
   
   var ds = this.dataset;
   ds.cancelLoadData();
   ds.useCache = false;
   
   this.setValue('');
   this.showSuggestions(false);
      
   var url = ds.url;
   
   for(var i=0; i < params.length; i++) {   
      url = Spry.Widget.Utils.addReplaceParam(url, params[i].param, params[i].value);   
   }      
   
   ds.setURL(url);
   ds.loadData();
   
};
In a nutshell this function clears the search term, hides suggestions and updates the AutoSuggest's dataset URL using an array of parameters passed to the function. The parameters are defined as simple JavaScript objects with two keys: param which is the name of the URL parameter, and value which is the value of the URL parameter. It does the actual parameter additon/replacement using the Spry.Widget.Utils.addReplaceParam() function. To update the URL using this function I wrote another function which I use as the onChange event handler for the country and type select elements.
function updateDsURL(f){
   
   var country = f.radiusCountry[f.radiusCountry.selectedIndex].value;
   var type = f.radiusType[f.radiusType.selectedIndex].value;
   
   var params = [];
   params[0] = {param:'radiusCountry',value:country};
   params[1] = {param:'radiusType',value:type};
   
   theSuggest.addReplaceParams(params);   
};

Putting it all together this is what it looks like:

<html>
<head>
<script language="JavaScript" type="text/javascript" src="includes/xpath.js"></script>
<script language="JavaScript" type="text/javascript" src="includes/SpryData.js"></script>
<script language="JavaScript" type="text/javascript" src="includes/SpryAutoSuggest.js"></script>
<link href="includes/SpryAutoSuggest.css" rel="stylesheet" type="text/css" />
<script language="JavaScript" type="text/javascript">
var ds1 = new Spry.Data.XMLDataSet("/suggestions.cfm","radiusvalues/radiusvalue");

function updateDsURL(f){
   
   var country = f.cboRadiusCountry[f.cboRadiusCountry.selectedIndex].value;
   var type = f.cboRadiusType[f.cboRadiusType.selectedIndex].value;
   
   var params = [];
   params[0] = {param:'radiusCountry',value:country};
   params[1] = {param:'radiusType',value:type};
   
   theSuggest.addReplaceParams(params);   
};


Spry.Widget.AutoSuggest.prototype.addReplaceParams = function(params)
{
   
   var ds = this.dataset;
   ds.cancelLoadData();
   ds.useCache = false;
   
   this.setValue('');
   this.showSuggestions(false);
      
   var url = ds.url;
   
   for(var i=0; i < params.length; i++) {   
      url = Spry.Widget.Utils.addReplaceParam(url, params[i].param, params[i].value);   
   }      
   
   ds.setURL(url);
   ds.loadData();
   
};
</script>
</head>

<body>
<form>
<p>
Find candidates within
<select name="radius">
   <option value="0" selected>Any Distance</option>
   <option value="1">1 mile</option>
   <option value="2">2 miles</option>
   <option value="3">3 miles</option>
   <option value="4">4 miles</option>
   <option value="5">5 miles</option>
   <option value="10">10 miles</option>
</select>
of the following
<select name="radiusCountry" onChange="updateDsURL(this.form);">
   <option value="U" selected>U.S.</option>
   <option value="C">Canadian</option>
   <option value="B">U.S. or Canadian</option>
</select>
<select name="radiusType" onChange="updateDsURL(this.form);">
   <option value="City"selected>City</option>
   <option value="Zip">Postal Code</option>
</select>:
</p>

<div id="mySuggest">
<input type="text" name="radiusValue"/>
<div id="resultsDIV" spry:region="ds1">
<ul>
<li spry:repeat="ds1" spry:suggest="{radiusvalue}">{radiusvalue}</li>
</ul>
</div>
</div>
<script type="text/javascript">
   var theSuggest = new Spry.Widget.AutoSuggest("mySuggest","resultsDIV", "ds1","name",{minCharsType:3, loadFromServer:true, urlParam:'filter'});
</script>

</form>
</body>
</html>

You may ask why two functions? Well, I thought this is something I may want to use again so I made the Spry.Widget.AutoSuggest.addReplaceParams() function fairly generic. For this example I included the function in the inline script block, but if I use it enough I'll probably end up putting it in my AutoSuggest.js file.

OK, that's one down...

ColdFire 1.002

Today Ray and I released ColdFire 1.002 which fixes a few minor issues with JSON encoding. If you were having problems with the variables feature of ColdFire this update may fix your issue. Be sure to update both the Firefox extension and coldfire.cfm debugging template. You can get both via the download at RIAForge.

We have also been discussing upcoming features so if there is something you would really like to see be sure to let us know via the RIAForge site.

Troubleshooting ColdFire

A few people have reported that they are unable to see debugging in the ColdFire Firebug panel. I've been unable to reproduce these issues so I thought I put together a quick post on how ColdFire works in hopes that it may help people debug their issues.

[More]

More Entries

BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.