Most of the time, when we use jQuery to send data over to the server, it gets delivered as either a query string value or a form value. And, most of the time, this is exactly what we want. Occasionally, however, we might have data that doesn't quite conform to a simple set of name-value pairs. In such a situation - as I learned from Marak Squires at my Node.js training class - we can actually use jQuery to post a complex JSON packet as the HTTP request body. This JSON packet can then be parsed into native ColdFusion objects on the server-side.
When taking this kind of approach, there's a few things that we have to think about. The first is the content-type of the request. By default, jQuery makes requests with a content-type of "application/x-www-form-urlencoded". This HTTP header provides the server with the information it needs in order to properly parse the incoming request. When we send a JSON packet, however, we're not sending this kind of URL-encoded form data; rather, we're sending "json" data. As such, when sending a JSON packet to the server, we have to explicitly set the content-type to be:
If we don't do this, the ColdFusion server will attempt to parse the incoming HTTP request body as name-value pairs which will raise the following ColdFusion exception:
JRun Servlet Error
In addition to the content-type, we also have to think about the content value itself; or rather, the "data" value of the jQuery.ajax() invocation. If we set the data value to an Object, jQuery will serialize the object into name-value query-string pairs which will then be appending the URL. To prevent this from happening, we have to manually serialize our post data and pass it in as a string. If the data value is already a string, jQuery will not apply any additional processing.
In order to serialize our data object, we'll be using JSON.stringify() to turn the object into a JSON representation.
To see this in action, let's take a look at the following jQuery demo:
When we post this JSON packet to the server, it is being processed by the ColdFusion page, api.cfm:
<!--- Get the HTTP request body content. NOTE: We have to use toString() as an intermediary method call since the JSON packet comes across as a byte array (binary data) which needs to be turned back into a string before ColdFusion can parse it as a JSON value. ---> <cfset requestBody = toString( getHttpRequestData().content ) /> <!--- Double-check to make sure it's a JSON value. ---> <cfif isJSON( requestBody )> <!--- Echo back POST data. ---> <cfdump var="#deserializeJSON( requestBody )#" label="HTTP Body" /> </cfif>
As you can see, this API page simply extracts the incoming HTTP request body, deserializes it (the JSON packet) into native, server-side objects, and then echos them back to the client. When we run the above code, we get the following output:
As you can see, the ColdFusion server was able to parse the incoming JSON request body and dump it back to the client.
Most of the time, a standard jQuery.ajax() request is all that we need in order to provide effective client-server interaction. But sometimes, the data we need to post is more complex. In situations, such as JSON-RPC (JSON Remote Procedure Calls), nested objects can leave us with data that isn't easily serialized into a collection of name-value pairs. In such cases, we can still use jQuery to easily post JSON representations directly to the server where they can be parsed and processed more efficiently.
Want to use code from this post? Check out the license.
My friend, if my photos anger you, I would definitely suggest trying to avoid my site :P
You're probably right :)
@Sam - kick rocks dude. Your tone and comments are uncalled for and quite frankly you're making yourself look like an ass.
@Ben: Interesting post. Can you elaborate on how you might use this over just passing the data as a form var? Or was it more of just 'cool, it works...' which is totally cool too.
Hey Ben, awesome stuff! Never would have occurred to me to try this. I'm gonna have to give it a shot in php, see how well/complex it can handle.
PS - Your celeb photos are in fact fun.
Another great post. Useful - like always - even for non Coldfusioner.
CF represents the URL and Form scopes as structs because, really, that's all they are (a bunch of name/value pairs). What JSON gives you is the ability to pass more complex objects than that. This includes one-to-many relationships, such as favoriteMovies in the example.
Sure, but what's stopping you from passing the serialized JSON string as a form variable?
If that's all you're doing, yeah, there would be little benefit to using AJAX over the form submission and building a JSON string in the onsubmit. I believe Ben uses AJAX to submit these comments as a countermeasure against spammers. That's one example of a reason to use AJAX instead. But better examples exist outside the traditional form/submit paradigm:
In offline web apps, one often stores database updates as condensed transaction objects (action code and data objects). That way, when the app is back online, it can sync up to the server by uploading all transactions since the last sync-up. For the purposes of modularizing code, it's usually better to do that in an "ononline" event handler. That way, the onsubmit can focus on form-to-browser-database logic, including building the condensed transaction object. The onsubmit doesn't need to know anything about talking to the server. Then the ononline contains only the transaction upload logic and doesn't need to know anything about data in the form.
Just a suggestion.
In the project that I'm working on now, we use something very similar to perform on-page object validation. Basically, we have a page with shopping cart items that need additional information, so we use jQuery UI to bring up a dialog "box" (actually a sequence of boxes), and jQuery and JSON to pass the form data to CFCs and return success/failure/other content for the form.
That way, the user can complete items in the cart without leaving it, and we're not constantly reloading the page ... and there are certain properties that can change when other objects in the cart are changed, so using JSON to pass objects back and forth makes that possible. (This app is built on CF 9 and ColdBox 3.0.)
As Todd suggests, unless there's a very specific use case were you need to do this, I think you better off passing the JSON string as a form field and just stick to using a normal POST operation.
You're not really gaining anything using this method, other than potentially saving a few bytes.
NOTE: These types of exercises are great, but just because you can do something doesn't mean it's good a idea. Posting data to the server using a non-standard content type is one of those types of things that could end up causing some unforseen problem in the future that's tough to track down.
Maybe you should arm-wrestle Sam.
Based on our very lovely friend who has racked up cumulative points under 20, I've formed a mental picture of what he (am I right as to the gender?) might look like as he arm wrestles Ben. However, my mental picture isn't very complimentary, so I'll refrain from describing it, for the sake of maintaining peace. ;-)
I've always been able to get buy with the basic URL/FORM name-value pair approach. I only learned that this was possible like a month ago. So, I can't say that it will be something I apply any time soon. The only thing that I can think of where it might make sense is for a JSON-RPC environment where the server-side API dictates that this kind of approach be used.
Now, whether or not that is a *wise* move for something like this, I have no idea :) I'll defer to @Dan on this as I think he thinks through this kind of stuff more deeply than I do.
... that said, there was a good amount of "oh cool, this works" involved :D
I don't know how PHP handles the raw request data vs. the parsed URL / Form scope stuff. I assume they have some access to it.
It's funny you mention the database synching concept as a possible use-case. I had that thought floating around in my head. And, if you look at something like the JSON-RPC standard, they actually allow for multiple "actions" to be passed through in an array (ie. a "batch" operation). So, you could post a large array of objects (all stringified) as an update.
FYI, here's the JSON-RPC (v2) proposal that I had to code against one time (which is my only experience with JSON-RPC):
No doubt Luis has some juicy JSON functionality packed into ColdBox. That framework has everything it seems.
I definitely agree - I wouldn't see the need to switch to this approach unless I happen to come across a situation that really dictated it. Most of the time, I'm dealing with a server-side API that wouldn't be able to handle it anyway.
Ha ha :) That reminds me, I have to start training for my cf.Objective() show down against Jason Dean!
A good way to start your training Ben - watch "Over The Top" with Stallone [again?]:
This is great post! I've been using a jQuery plug-in to achieve this very same affect. This is going to cut down on JS overhead when I do my argument collection for form submission to CF.
Really interesting step in the jquery section. I didn't event know there was a "then" method that can be attached to ajax request. Nice post!
Awesome Ben ..
This I was looking for long back, I didn't expect that I can play with Request Content Type using JQuery like what we can do with Response using ColdFusion.
After re-reading my comment note, I don't think it was clear as I wanted.
I think these types of exercises are great, because they help you think of ways to solve problems outside the box. There's obviously a danger that you can end up overthinking a problem and over archetecturing it as well, but the practice of trying new things is always good.
What I always worry about when I see stuff like this, is the danger it poses to developers who don't fully understand what's going on, but use it as a basis to solve a problem. It's not really your problem, but when I see stuff that I think may end up causing issues in the long term for a developer w/out the experience in the technology being used, I try to at least throw out a warning.
Ha ha, I actually just watched that movie on Netflix a few months ago. The movie itself is so over the top, it's awesome.
The .then() stuff is part of the relatively new Deferred addition to the library. It allows you to more easily attache multiple event handlers as well as invoke / attach event handlers at a later time. If you are curious about them, here is my first exploration of them:
They're pretty cool!
I'm glad you found this stuff interesting. It's a fun exploration.
I was also been thinking a lot about your comment last night. Specifically, I was thinking about the idea of causing problems down the line with certain implementations. I think I was quick to agree; but, in hindsight, I wasn't sure if it was any more problematic than anything else. For example, if someone expects GET-based params to be used, and the server suddenly starts using Form variables, things will break. The fact that URL and FORM variables are more common doesn't necessarily mean code is more stable.
... which I think brings us back to the point that you just clarified - that it comes back to understanding. People need to understand why something would be done in one way vs. another way. Anything used without understanding is bound to cause problems at some point.
To be completely transparent, a good deal of the stuff that I write on my blog never actually makes it into production. A lot definitely does; but, there are many explorations that simply started with the thought, "Would it break if I did...." or, "I know this doesn't make any sense, but what if....".
The problem is, as you say, *I* know that these are explorations and experiments, but other people might not read them that way. I do try to be clear about certainly things. For example, I try to include phrases like:
Most of the time, when we use jQuery to send data over to the server, it gets delivered as either a query string value or a form value. And, most of the time, this is exactly what we want.
In some blog posts, I'll even go so far as to admit that I can't think of any "practical" use for the given information.
I say all this, not to come off as being defensive, but as being sympathetic. I don't want to lead people down the wrong path. But, at the same time, I also don't want to stop exploring. Digging into the minutia of behaviors, seeing what breaks, seeing what works [unexpectedly] is a load of fun :D
So, I guess what I'm really saying is, I agree... but I don't have a better solution.
Thanks for the post. I think part of becoming a better developer is exploring interesting ways of doing things, even if they have little or no practical application--besides, 100% pragmatic coding is just boring!
Time after time, I've found that trying out interesting ideas ends up leading to a practical solution down the road when one is really needed...even if the solution isn't precisely a 1:1 of the original experiment.
Thanks, as always, for posting this kind of stuff. Production-worthy or not, it's definitely interesting and a great resource for those times when something like this is precisely what is needed :)
I guess I missed the whole deferred announcement with jQuery 1.5. I'll have to go back and rewrite my example Ajax code (again!).
I think I would put most of the parameters in a $.AjaxSetup() - even the url to a ColdFusion component. That way it can be seen as your Ajax gateway, and within it are the methods you call via data.method.
I have been using the technique from a while and I am not using .toString()function in CF and doing deserializeJSON(postVar).
I think the only difference id you are using getHttpRequestData().content to read the content and I am looking for a Var holding the jason packet.
@Sam - please learn appreciating good work rather than pointing out things which doesn't matter in Tech world.
@Ben - Nice photos, gives chance to see our active community members.
Awesome man, I really appreciate that. And, I completely agree - it could be that months later, I find myself in some situation where I'm stuck and then - blam!! - I remember that one experiment I did months ago that would be perfect for the problem.
A lot of my Application.cfc exploration has actually led to that. Just seeing if doing this or that will break the App.cfc workflow has led me to some interesting and real-world problem solving approaches.
The Deferred stuff is pretty. I've only played around with it a bit but am liking it. The one thing that I like about it is that it allows me to break the code up a little bit. I can, on one line, "launch" the AJAX request; then, on a completely different line, bind success/error handlers. I think adds nicely to readability.
As @Dan mentioned, using the form-field approach (as you do) is probably a bit more practical. That allows ColdFusion to process the form as it would typically. Are there particular types of AJAX requests that you are using this for?
Hi I have tried your code to post JSOn data to a REST url(.svc webservice file) but am getting an 405 method not allowed error.
Would be great if some one can help.
This does not directly relate to the topic covered and for this I apologise...
The question I have is that I am building an error capture routine into my companies CF applications utilising serializeJson() and deserializeJSON()...
When I capture the error structure I get a series of \n\t\r characters in the json string...
When reading this back out using deserialiseJSON() I am getting a JSON parsing failure: Unexpected end of JSON string...
Any ideas on how to handle this would be great...
Thanks in advance and once again sorry for being a little off track...
Hi. How can I deserialize the json string without using that deserializedjson function. I'm using CF 7 and it throws an error everytime I use that. Thanks
Try http://jsonutil.riaforge.org/ it was created with the view of achieving JSON functionality on coldfusion mx7 servers...
Searching deserialisejson in cfmx7 will give you plenty of direction on how to achieve this...
Does anyone know if there is reason why getHttpRequestData() doesn't capture the content of HTTP GET requests? I've noticed on incoming SOAP 1.2 requests to our server that they are using GET as opposed to POST and now the content of the request captured is blank.
Thanks a lot Ben....more help full this...
I believe it's part of the HTTP specification - I don't think GET requests can have a request body. I think the entire request has to be defined in the URL and headers.
Thanks Ben for bringing this post back up again! I decided to pick apart the return value in API.cfm and it proved to be illuminating.
I hope if I wrap this in a code block it will format correctly:
<cfset HTTPRequestData = GetHTTPRequestData()> <cfoutput> <ul> <li>HTTPRequestData.Method: #HTTPRequestData.method#</li> <li>HTTPRequestData.Protocol: #HTTPRequestData.protocol#</li> <cfloop collection="#HTTPRequestData.headers#" item="FieldName"> <cfset FieldValue = HTTPRequestData.headers[FieldName]> <li>HTTPRequestData.headers.#FieldName#: #FieldValue#</li> </cfloop> <cfset strContent = toString(HTTPRequestData.Content)> <cfif isJSON(strContent)> <cfset stcContent = deserializeJSON(strContent)> <cfif IsStruct(stcContent)> <li><strong>AND FINALLY:</strong></li> <cfloop collection="#stcContent#" item="FieldName"> <cfset FieldValue = stcContent[FieldName]> <cfif IsArray(FieldValue)> <cfloop from="1" to="#ArrayLen(FieldValue)#" index="I"> <li>HTTPRequestData.Content.#FieldName#[#I#]: #FieldValue[I]#</li> </cfloop> <cfelseif isSimpleValue(FieldValue)> <li>HTTPRequestData.Content.#FieldName#: #FieldValue#</li> </cfif> </cfloop> <cfelse> <li>stcContent is not a structure.</li> </cfif> <cfelse> <li>strContent is not in JSON format</li> </cfif> </ul> </cfoutput>
I've started to use JSON as my communication approach for much of my API-based interactions. AngularJS makes this fairly easy, posting JSON requests bodies. It's been really nice to work with. One thing that I am trying to get used to is the idea of being able to post nested, complex data points.
For example, in AngularJS, it's quite easy to post an actual array to the sever, nested inside of a struct. And yet, half the time, I end up posting a comma-delimited list. Example:
ids: someArray.join( "," )
... when, in reality, I could just post the raw array and it will deserialize quite naturally in ColdFusion. Some habits just die hard :)
i searched one day to figure out how can i post json to cf and handle the request. But soon i realized that you already did this very clear. Perfect.