A few weeks back Ben Nadel released a nice utility that does JSON serialization with ColdFusion data. As you know, ColdFusion has this feature baked in, but it doesn't always serialize your data in the form you need. Ben's library gives you greater control over JSON serialization and can be helpful in cases where the built-in support isn't working right for you. I wanted to share a quick example of how you could possibly use this in a real CFC.
First, let's consider a simple CFC built to return an array of data in JSON:
Nothing too fancy here, right? I've got an array of structs where each element in the array is a structure with three keys. There's a name, an age, and a favorite color key. You would typically retrieve this data by calling the CFC and passing the method and returnformat URL keys:
http://localhost/test.cfc?method=demo&returnformat=json
And here is the result:
[{"FAVCOLOR":333333,"NAME":"Ray 1","AGE":29}, {"FAVCOLOR":333333,"NAME":"Ray 2","AGE":48}, {"FAVCOLOR":333333,"NAME":"Ray 3","AGE":41}, {"FAVCOLOR":333333,"NAME":"Ray 4","AGE":68}, {"FAVCOLOR":333333,"NAME":"Ray 5","AGE":21}, {"FAVCOLOR":333333,"NAME":"Ray 6","AGE":31}, {"FAVCOLOR":333333,"NAME":"Ray 7","AGE":32}, {"FAVCOLOR":333333,"NAME":"Ray 8","AGE":78}, {"FAVCOLOR":333333,"NAME":"Ray 9","AGE":94}]
There are two problems with this result. First, the keys are uppercased. That isn't too difficult to fix nor is it too difficult to handle on the client-side. Secondly, the "favcolor" value was transformed into a number. This is definitely not desirable. Unfortunately you can't fix this easily unless you mess with your data a bit.
So how would you fix this with Ben's utility? Consider this updated version:
Let's go over the changes here one by one.
- First, note we have to change the return type of the method from array to string. While we are still working with an array, the actual return now will be a JSON string, not the original array.
- The next change isn't necessarily required, but it helps. We override the return format value and set it to plain. The value "plain" tells ColdFusion to not do anything with the result. I.e., leave it be and don't touch it. In theory the end user could just pass this in the URL, but we can make things even easier by just setting the format in the CFC.
- After the existing business logic, we then simply instantiate Ben's CFC and serialize the data. We can then return that string.
Now we can call our logic like so:
http://localhost/test.cfc?method=demo2
And get this in response:
[{"favcolor":"333333","name":"Ray 1","age":"37"}, {"favcolor":"333333","name":"Ray 2","age":"88"}, {"favcolor":"333333","name":"Ray 3","age":"86"},{"favcolor":"333333","name":"Ray 4","age":"25"}, {"favcolor":"333333","name":"Ray 5","age":"72"}, {"favcolor":"333333","name":"Ray 6","age":"97"}, {"favcolor":"333333","name":"Ray 7","age":"32"}, {"favcolor":"333333","name":"Ray 8","age":"56"}, {"favcolor":"333333","name":"Ray 9","age":"57"}]
Note that by default the keys were lowercased, which is pretty darn cool. Also note that age was transformed into a string. If you don't like this, you can fix it like so:
var serializer = new JsonSerializer().asString("favcolor").asInteger("age");
Archived Comments
Neato Bandito!!! Nice going Ben, I sure will be giving this a try this week!!
Ahem, Ray wrote this blog entry. ;) (Ok, Ben gets the credit, but you don't want to hurt my fragile ego, do you???)
Ok Ok Mr Ray :) Great job both of you guys!!!!
You know you're the man Ray :)
Heh, thank you, ego saved. :) Personally - I still think folks get confused by returnformat and CFC calls. So this was a great opportunity to show an example of it.
"the keys were lowercased".
Q: What if it were FavColor instead of favcolor? Are you saying that using url.returnformat = "plain" leaves the case alone?
"Q: What if it were FavColor instead of favcolor?"
Doesn't change. I believe Ben's code forces lowercase. Just checked his code - and it does. To me, this is good.
"Are you saying that using url.returnformat = "plain" leaves the case alone?"
No. When you use returnformat=plain, ColdFusion does nothing to the result. When you use returnformat=wddx or json, CF does serialization. If you use returnformat=plain, you MUST return a simple result (string, date, number, you get the idea).
So basically my point is - if you don't like the built-in serialization and want to use your own (or Ben's), use the plain format and handle it yourself.
Woot!! Thank you Ray :D Now, this is what I call a good Hump day!
@Phillip, Re: casing of keys, it will use what ever you pass into the asXYZ() methods. So, if you pass .asString( "FavColor" ), the keys will come through as "FavColor". This holds true for all the .as() methods.
By default, it will lowercase all the keys that it doesn't recognize. So, if it reaches part of data structure that was not explicitly defined, it will just lowercase the keys.
@Ben: Oh slick - that is well done. (I'll be honest - I skim/read. ;)
No worries :)
You may also want/need to explicitly content type to application/json:
<cfcontent type="application/json" />
Good point there. Interestingly enough you can't do it in cfscript. I'm assuming you would need to do it with getPageContext, which is workable, but not acceptable. Checking the bug tracker.
ER is here - folks should vote for it. I'm going to do so now:
https://bugbase.adobe.com/i...
I dunno if I'm being overly dogmatic about encapsulation, but is there no better way of dealing with URL.returnformat than accessing it directly within one's method? For a remote call with returnFormat on the URL, it should be passed into the arguments scope, shouldn't it?
(the above is very much a question, not a statement dressed up as one)
--
Adam
Good question Adam. Obviously it works (hard coding it), but is it "The Right Thing" (and I know that is not something folks agree on all the time).
In my opinion - returnformat is a handy way to allow a service to dynamically return JSON or WDDX or plain text for a remote client.
But in *practical* usage, I'm willing to bet that 99% of your use of this CFC will be for AJAX, and therefore, json.
So by adding a url.returnformat to the method, I make it easier for the consumer which to me is a Good Thing as well. Now I can just call soandso.cfc?method=foo and not worry about asking for JSON when JSON is the only thing that would ever make sense.
My 2 cents.
Sorry Ray, you missed my point. What I meant was that rather than using this:
url.returnformat = "plain";
Can you not use:
arguments.returnformat = "plain";
For remote calls, aren't all URL params passed into the method as arguments?
--
Adam
It should be. I haven't tried it that way.