JSON for jQuery

Michael Geary | Wed, 2006-01-25 00:10

Update 2007-09-13: As of version 1.2, the jQuery core now supports cross-domain JSONP downloads as part of the native Ajax support. I suggest you use this support instead of the plugin.

jQuery is a nifty new JavaScript library by John Resig. It features a $() function like the one in Prototype.js, but beefed up with CSS and XPath selectors, and with the ability to chain methods to do interesting things with concise code.

Unlike Prototype, jQuery doesn’t mess around with built-in JavaScript objects. It’s new—too new to have a version number!—but I’ve been writing some code with it and enjoying it.

jQuery provides an easy way to write plugin methods to extend the $ function. For you JSON fans out there, here is a JSON plugin for jQuery which lets you write code like this:

function doJson( json ) {
  // handle the json object here
}

$('#test').json( 'http://example.com/json-test?jsonp={callback}', doJson );

You can of course use an anonymous function if you prefer:

var url = 'http://example.com/json-test?jsonp={callback}';
$('#test').json( url, function(json) {
  // handle the json object here
});

Or, using jQuery’s method chaining, you can combine calls like this code which displays a “Loading…” message when it starts loading the JSON resource:

$('#test').html( 'Loading...' ).json( 'http://example.com/json-test?jsonp={callback}', doJson );

To install the plugin, simply paste this code into a .js file and load it after loading jquery.js:

// JSON for jQuery by Michael Geary
// See http://mg.to/2006/01/25/json-for-jquery
// Free beer and free speech. Enjoy!

$.json = { callbacks: {} };

$.fn.json = function( url, callback ) {
    var _$_ = this;
    load( url.replace( /{callback}/, name(callback) ) );
    return this;

    function name( callback ) {
        var id = (new Date).getTime();
        var name = 'json_' + id;

        var cb = $.json.callbacks[id] = function( json ) {
            delete $.json.callbacks[id];
            eval( 'delete ' + name );
            _$_.each( function() { callback(json); } );
        };

        eval( name + ' = cb' );
        return name;
    }

    function load( url ) {
        var script = document.createElement( 'script' );
        script.type = 'text/javascript';
        script.src = url;
        $('head',document).append( script );
    }
};

This adds a json() method to the $ function. The first argument is the URL to the JSON resource, with the text {callback} wherever the JSON callback method should be provided. In a JSONP URL, you would use jsonp={callback}; in a Yahoo! JSON URL you would use format=json&callback={callback}.

The second argument is the callback function itself. When the JSON resource finishes loading, this function will be called with a single argument, the JSON object itself. Inside the callback function, this is a reference to the HTML element found by the $ function. (If $ found more than one element, the callback function is called for each of them.)

The callback function is required, so this code won’t work with plain JSON APIs like del.icio.us that don’t let you specify a callback function. This would be easy enough to fix; I didn’t need it for the code I was writing, and didn’t think of it until just now. :-)

The code goes to a bit of extra work to create both an array entry and a unique global name for each callback. The global name is what is substituted into the {callback} part of the URL. It uses this name instead of the array reference to ensure compatibility with any JSON APIs that don’t allow special characters in the callback name. In fact, in the current code the callbacks[] array entries are not really used, but I figured it could be handy to have an array of all outstanding callbacks.

Update: John Resig suggested a couple of improvements to the code, so it’s updated, simpler and better now.

Update 2: Code updated to include Stephen and Brent’s fixes from the comments.

Submitted by Ashutosh (not verified) on Wed, 2006-04-26 02:55.

I’m amazed at how neat jquery makes things simpler. I thought prototype was cool, but I’m beginning to like what you and John are doing with jquery even more…. you have one more convert here!

BTW, got to learn how you do code syntax coloring!!

Submitted by Michael Geary on Sat, 2006-04-29 15:32.

Thanks, Ashutosh. The code syntax coloring uses GeSHi, the Generic Syntax Highlighter. I modified an existing Drupal plugin to make better use of this code formatter, and I’ve been meaning to post it on the Drupal site or here. Sorry about that, I must take care of it.

Submitted by jQuery Convert (not verified) on Mon, 2009-11-09 21:38.

I would also recommend lightweight syntex highlighter called “prettyprint” used on Google Code. It is lightweight and looks very clean.

Regarding this post. I came across it while searching for RSS to JSON converter plugin for jQuery. I thought I would share it here with everybody. The great thing about it is that you don’t need to setup any server-side scripts. All you need is a jQuery.js file. Everything is done on client side leveraging Google Feeds API.

Here is an example from original post:

<script src="jquery.js"></script>
<script src="jquery.jgfeed.js"></script>
<script>
$.jGFeed('http://twitter.com/statuses/user_timeline/26767000.rss',
  function(feeds){
    // Check for errors
    if(!feeds){
      // there was an error
      return false;
    }
    // do whatever you want with feeds here
    for(var i=0; i<feeds.entries.length; i++){
      var entry = feeds.entries[i];
      // Entry title
      entry.title;
    }
  }, 10);
</script>
Submitted by tenuki (not verified) on Wed, 2006-05-31 04:37.

I can’t get this pointing to other thing than window object, inside the callback.

PS: other, less relevant thing, is that it would be nice not to depend on the callback functionality of the server.. (if it’s possible sometime).

Submitted by Michael Geary on Wed, 2006-05-31 11:21.

An easy way to gain access to a this object inside a callback or nested function is through a closure. There is an example in the code above. The outermost $.fn.json function begins with:

    var _$_ = this;

Later, the JSON callback nested two levels deep uses that _$_ variable when it needs a reference to the original this object:

            _$_.each( function() { callback(json); } );

I don’t recommend using the cryptic name _$_ that I used in that code. These days I usually call it self, so the code would be:

    var self = this;

and:

            self.each( function() { callback(json); } );
Submitted by Stephen Clay (not verified) on Mon, 2006-06-19 04:50.

Looks great, just a warning that cb’s assignment is missing a terminating semicolon:

var cb = $.json.callbacks[id] = function( json ) { /* snip */ }

Even uncompressed these sometimes throw a syntax error in Opera.

Submitted by brent (not verified) on Mon, 2006-06-26 18:56.

hi, very cool! just curious about this… if you use

$.json = { callbacks: [] };

and your (new Date()).getTime() returns a 10+ digit number, wont your $.json.callbacks array be huge? why not { callbacks : {} } ?

Submitted by Michael Geary on Tue, 2006-07-04 09:11.

Thanks, Brent and Stephen. I updated the code to include your fixes.

Submitted by Gregory Collins (not verified) on Thu, 2007-01-11 11:40.

Hi there,

We’re in the process of building JSON interfaces to our services. We’ve found a serious problem using this particular technique of dynamically adding script tags to the document head: IE leaks memory like a sieve when you do this. According to some MSDN documentation I read, they refuse to garbage collect script text on a page until the page is destroyed.

Given that we routinely send hundreds of kilobytes of JSON data across the channel, this became a problem for us very quickly. The solution is to wrap the script call inside a dynamically-generated iframe element.

To see a demo of our web-based traffic application, see http://mycommute.maptuit.com/?config=TrafficT.NYC.

Submitted by Visitor (not verified) on Thu, 2007-03-08 05:54.

There is a solution to Ie memory leak, you have to remove the obj(garbage collector) before you can removechild node.

Submitted by Mark (not verified) on Tue, 2007-06-19 03:11.

Hi, I’m new with Json and I’ve tried to get this plugin to work since several days. Is there anybody who could give me an example. Thanks in advance! Mark

Submitted by Johann (not verified) on Thu, 2007-08-02 09:05.

Has this functionality been included in the

$.getJSON( url, params, callback )
method or is the current jQuery implementation different?

Submitted by Michael Geary on Thu, 2007-08-02 11:05.

Both functions let you download JSON data, but they use different techniques and are made for different situations.

$.json() (this plugin) uses a dynamic script tag to download the JSON data. This lets it work across domains, but it requires that the JSON data be wrapped in a callback function (JSONP format).

$.getJSON() is an AJAX call, that is, it uses XMLHttpRequest. So it works only within the same domain—you can’t use it for cross-domain requests. But it doesn’t require JSONP format; it will work with any standard JSON data.

Submitted by Johann (not verified) on Thu, 2007-08-02 13:46.

Thanks Michael.

I looked at the jQuery sources in the mean time. To me, jQuerys $.ajax seems to be lacking a fallback mechanism in case ActiveX controls are disabled. This is where your solution — or Ralf Engelschalls — could help.

Submitted by Eric (not verified) on Sat, 2007-08-11 18:33.

I added your code to my js files and put in the right order. I am new at this so I am trying to learn. When I runt he following code. I get an javascript error on my page and no output. Can you help me with what I am doing wrong. I am really trying to understand this stuff. Thanks.

$(document).ready(function(){
    alert('Document Ready');
    var url='http://api.flickr.com/services/feeds/photos_public.gne?ids=10861890@N04&format=json&jsonp={callback}';
    alert(url);
    $.json(url,function(json) {
        alert('I received the json and put it in the json var : ' + json.toString());
    });
 });
Submitted by Michael Geary on Sat, 2007-08-11 18:53.

Eric, do you have a test page you could post a link to? Or else let me know what the JavaScript error was?

I’ll try a test with your code, but if you have a link or error message that would give me a head start.

Submitted by Michael Geary on Sun, 2007-08-12 01:14.

Eric, you were pretty close there. Here’s an updated version of your code that works:

function log() {
    if( window.console )
        console.debug.apply( console, arguments );
    else
        alert( [].join.apply( arguments, [' '] ) );
}

$(document).ready(function(){
    log( 'Document Ready' );
    var url = 'http://api.flickr.com/services/feeds/photos_public.gne?ids=10861890@N04&format=json';
    log( 'url:', url );
    $().json( url );
});

function jsonFlickrFeed( json ) {
    log( 'json feed received:', json );
}

I added a log() function to replace the alerts. If you load the page in Firefox with Firebug enabled, it will use console.debug() to log the messages, including making objects like the json response clickable and browsable. If Firebug is not available, it falls back to an alert() call. The fancy .apply() stuff is to make multiple arguments work in both these cases.

There were two things wrong in the code. First, you need to call the json function with $(selector).json(), where selector is an optional jQuery selector. You don’t need any selector in this particular case, so I left it empty. (In hindsight, my requirement for $() with or without the selector was a bit goofy—I will probably update the code sometime to make it work the way you tried to use it.)

After I fixed that, though, it still didn’t get to the callback function with the “json received” message. So I tried opening your Flickr feed URL directly in a browser, and found that the JSON response calls a function named jsonFlickrFeed(). I changed the {callback} to MikeTest, so the URL ended with format=json&jsonp=MikeTest. I also tried changing it to format=json&jsoncallback=MikeTest as described on Flickr’s JSON API page, but the response still called jsonFlickrFeed() instead of MikeTest().

It appears that Flickr’s feed API doesn’t support the user-defined callback function name that this plugin normally requres. (Their REST API does support jsoncallback=functionName and works fine with the plugin.)

A quick workaround for that is to simply define the jsonFlickrFeed() function directly instead of specifying the callback function in the $().json() call. At this point the plugin isn’t doing much for you except for creating the dynamic script tag, but it does get things working.

When I get a chance I’ll update the plugin to support this more directly. And hopefully Flickr will update their feed API to support the user-defined callback names as their REST API does.

Submitted by Eric (not verified) on Sun, 2007-08-12 06:27.

Wow, all I have to say is THANK YOU. I really appreciate the time you took to help me understand this. I will be working on this today after I pick through your code you gave me. Thank you for teaching me the log().

Submitted by Michael Geary on Sun, 2007-08-12 10:46.

BTW, because of Flickr’s hardcoded callback function name, the only part of the plugin that you’re really using is the load() function:

function load( url ) {
    var script = document.createElement( 'script' );
    script.type = 'text/javascript';
    script.src = url;
    $('head',document).append( script );
}

So you could omit the plugin entirely and use this code instead:

$(document).ready(function(){
    log( 'Document ready' );
    var url = 'http://api.flickr.com/services/feeds/photos_public.gne?ids=10861890@N04&format=json';
    log( 'url:', url );
    loadJson( url );
});

function loadJson( url ) {
    var script = document.createElement( 'script' );
    script.type = 'text/javascript';
    script.src = url;
    $('head',document).append( script );
}

function jsonFlickrFeed( json ) {
    log( 'json feed received:', json );
}

Be sure to load your page with Firebug and watch the console log - it’s pretty cool to see your name show up in the JSON response.

Also, install the Firefox Web Developer toolbar if you don’t already have it. Load your page and then select Information/View JavaScript from the toolbar. A new window or tab will open showing all of the scripts loaded in the page—inline, .js files, and JSON downloads. Click the Collapse All link at the top of the page to see just the URLs. Very handy to check out your JSON downloads this way.

Submitted by bhagwat sharma (not verified) on Wed, 2010-03-17 21:37.

ABOUT JQUERY WITH JSON
please give me suggation

WITH COMPLETE EXAMPLE

Submitted by Matthew Moore (not verified) on Thu, 2007-08-23 08:43.

Hi,

Is there a way to use the jquery $.ajax along with your plug-in and get a JSONP object?

Below is the code I have, but it’s getting a “XMLHttpRequest” error because I’m trying to call a resource from a sub-domain.

// initial ajax setup
$.ajaxSetup({
  type: "GET",
  dataType: "json",
  global: true
  url: "http://sub.domain.tld/data"
});

// offers ajax call
$.ajax({
  data: {page: "offers", response: "jsonp", limit: "10"},
  success: handleOffersSuccess,
  error: handleOffersError
});

// success funciton
function handleOffersSuccess(obj){
var objCnt = obj.offers.length;
$("#ajax-offers").html("");
    for(var i=0; i<objCnt; i++){
        var advertiser = obj.offers[i].advertiser;
        var subject = obj.offers[i].subject;
        var url = obj.offers[i].url;
        var image_url = obj.offers[i].image_url;
        if( url == "" || url == null ){
            advertiser = EscapeChar(advertiser);
            subject = EscapeChar(subject);
            if(i==0){
              $("#ajax-offers").append("\n    <dt>"+advertiser+"</dt>\n<dd>"+subject+"</dd><dd><img src=\""+image_url+"\"></dd>");
            }else{
              $("#ajax-offers").append("\n    <dt>"+advertiser+"</dt>\n<dd>"+subject+"</dd>");
            }; // end first object test
        }else{
            advertiser = EscapeChar(advertiser);
            subject = EscapeChar(subject);
            url = EscapeChar(url);
            var img = image_url.length;
                if(i==0){
                    if(img!=0){
                    $("#ajax-offers").append("\n    <dt><a href=\""+url+"\" title=\"Offer From "+advertiser+"\">"+advertiser+"</a></dt>\n    <dd><a href=\""+url+"\" title=\"Offer From "+advertiser+": "+subject+"\">"+subject+"</a></dd><dd><a href=\""+url+"\" title=\"Offer From "+advertiser+": "+subject+"\"><img src=\""+image_url+"\"></a></dd>");
                    }else{
                    $("#ajax-offers").append("\n    <dt><a href=\""+url+"\" title=\"Offer From "+advertiser+"\">"+advertiser+"</a></dt>\n    <dd><a href=\""+url+"\" title=\"Offer From "+advertiser+": "+subject+"\">"+subject+"</a></dd>");
                    } // end image test
                }else{
                $("#ajax-offers").append("\n    <dt><a href=\""+url+"\" title=\"Offer From "+advertiser+"\">"+advertiser+"</a></dt>\n    <dd><a href=\""+url+"\" title=\"Offer From "+advertiser+": "+subject+"\">"+subject+"</a></dd>");
                }; // end first object test
        }; // end url test
    }; // end for loop
}; // end offers success function

// offers error funciton
function handleOffersError(request,errtype,e){
    //alert('An error occurred while loading. Error Reported:\n' + e.message + '(' + e.description + ').');
    $("#ajax-offers").html("<p>I'm sorry but there was an error retrieving the data.</p>");
} // end offers error function
 
Submitted by Michael Geary on Thu, 2007-08-23 22:14.

Matthew, it sounds like you are running into cross-domain security. If your page is loaded from a domain other than “sub.domain.tld” then $.ajax will fail.

That is the whole point of the JSONP format with dynamic script tags as used in this plugin—it avoids all of the cross-domain security issues. You can load a script tag from any domain, and JSONP format is simply a way to request that a specific callback function name be executed with the JSON data as an argument.

Does your server actually provide JSONP output, where you can specify the callback function name in the URL? If it does, then you can use the plugin directly and not use $.ajax at all.

Tell me a bit more and we can come up with a solution…

Submitted by Paul McLanahan (not verified) on Mon, 2007-08-27 13:23.

Hi Michael,

Excellent plugin first off. I love your closure solution for queuing the callbacks. I’ve actually used the idea of it for a recent plugin to use the Del.icio.us json feeds. I had to modify it to use an auto-incrementing array to house the callback references instead of the date-string based hash object due to the need to call several scripts in rapid succession. I was seeing callbacks overwritten due to them getting the exact same date string (I was surprised).

In any case… The main reason for my comment is to point out a problem I just fixed in my plugin and which your code appears to share. jQuery 1.1.4 now detects script elements being appended to the DOM using the $().append() method. When it detects one that also has a src attribute, it attempts to use $.ajax to grab the script and execute it. Our code will, for obvious reasons, cause that ajax call to fail. I had to go back to using real DOM methods to get it done. So, your 3rd to last line should be changed…

// From this
$('head',document).append( script );
// To this
document.getElementsByTagName('head')[0].appendChild( script );

Thanks again for the great plugin.

Submitted by Michael Geary on Thu, 2007-09-13 20:07.

Thanks for the kind words and the heads-up, Paul.

As you probably know, the jQuery 1.2 core now supports cross-domain JSONP downloads natively. So there’s probably not much need for the plugin any more. I haven’t tested the new code yet, but it probably does a fine job.

Submitted by Visitor (not verified) on Sun, 2008-09-28 18:52.

Hello

I need your help on this.

Example: I have JSON data

Country Code

Belgium 106 China 103

How do I populate a HTML select dropdown box with the json data

Select Dropdown(Belgium, China)

Now when I select a value like China, it should traverse JSON and display the code.

Can someone show me an example of how, this can be done using JSON and Jquery.

Thanks

Submitted by Julian (not verified) on Sat, 2009-03-28 05:18.

… the core implementation have some serious limitations.

I made jquery-jsonp to deal with them.