Friday, August 5, 2011

Getting a cross-domain JSON with jQuery in Internet Explorer 8 and newer versions

A simple widget using jQuery and utilizing the YQL (Yahoo Query Language). This allowed me to load an xml page and have the YQL parse it for me into JSON.. But, i forgot that since IE8, for cross-domain requests i should use special XDR (X-Domain Request) – this is a special API developed by Microsoft for Internet Explorer 8 and newers version. So, since we already had a jQuery configured for this:




function parse(json) {
  // parse JSON object here
}
url = "http://news.google.com/?output=rss"; // just an example here
 
$.ajax({
 url:'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(url)+'%22&format=json&diagnostics=true?callback=?',
 type: 'GET',
 success:parse // parse is a simply declared function for parsing the returned JSON into a custom block on the page 
})


This worked just fine in any browser, even IE7 (didn’t check IE6, but i’m sure even there it works), but not in IE8. So for IE8 we need to check the browser user agent and if the version is >= 8, then use another object:


if ($.browser.msie && parseInt($.browser.version, 10) >= 8 && window.XDomainRequest) {
 // Use Microsoft XDR
 var xdr = new XDomainRequest();
 xdr.open("get", 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(url)+'%22&format=json&diagnostics=true?callback=?');
 xdr.onload = parse;
 xdr.send();
} else {
 $.ajax({
  url:'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(url)+'%22&format=json&diagnostics=true?callback=?',
  type: 'GET',
  success:parse
 });
}


And that’s it, now it works in all browsers + IE8. Oops, but XDR doesn’t parse the returned text. In other words, when we use jQuery, we have a ready JSON object returned, but when we use XDR, we have a plain text received. So what we can do is just evaluate the returned text (since it’s a perfectly formatted JSON object) like this:


// we skip all other conditions and all that stuff
var xdr = new XDomainRequest();
xdr.open("get", 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(url)+'%22&format=json&diagnostics=true?callback=?');
xdr.onload = function() {
  json = 'json = '+xdr.responseText; // the string now looks like..  json = { ... };
  eval(json); // json is now a regular JSON object
  parse(json); // parse using same function as for jQuery's success event
}
xdr.send();


Now we can use same parse function for both cases:


if ($.browser.msie && parseInt($.browser.version, 10) >= 8 && window.XDomainRequest) {
 // Use Microsoft XDR
 var xdr = new XDomainRequest();
 xdr.open("get", 'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(url)+'%22&format=json&diagnostics=true?callback=?');
 xdr.onload = function() {
   json = 'json = '+xdr.responseText; // the string now looks like..  json = { ... };
   eval(json); // json is now a regular JSON object
   parse(json); // parse using same function as for jQuery's success event
 }
 xdr.send();
} else {
 $.ajax({
  url:'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(url)+'%22&format=json&diagnostics=true?callback=?',
  type: 'GET',
  success:parse
 });
}


Security issues and parsing JSON
As noted by Elijah in the comments of this page, there are security holes in using eval(json) as a method of parsing (evaluating) a JSON string. Considering that, i recommend this function natively used in jQuery (I’ve left the comments of jQuery developer here, because he describes everything pretty well, and i also included the String trim function for IE):


// JSON RegExp
var rvalidchars = /^[\],:{}\s]*$/;
var rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
// Used for trimming whitespace
var trimLeft = /^\s+/;
var trimRight = /\s+$/;
 
function trim( text ) {
 return text == null ?
  "" :
  text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
};
 
function parseJSON( data ) {
 if ( typeof data !== "string" || !data ) {
  return null;
 }
 
 // Make sure leading/trailing whitespace is removed (IE can't handle it)
 data = trim( data );
 
 // Attempt to parse using the native JSON parser first
 if ( window.JSON && window.JSON.parse ) {
  return window.JSON.parse( data );
 }
 
 // Make sure the incoming data is actual JSON
 // Logic borrowed from http://json.org/json2.js
 if ( rvalidchars.test( data.replace( rvalidescape, "@" )
  .replace( rvalidtokens, "]" )
  .replace( rvalidbraces, "")) ) {
 
  return (new Function( "return " + data ))();
 
 }
 
 //jQuery.error( "Invalid JSON: " + data )    // i substituted this since we don't have jQuery in this example
 return false;
}


As you may see, it first does some simple checks, looks for valid characters, tries native JSON library (which is already present in most modern browsers) and just does the thing. Sorry i didn’t have time to test this parseJSON function, but the logic is seen here and you can easily trace errors in any normal javascript debugger (like Chrome javascript console).


Note, if you ARE using jQuery for XHR, you can just use $.parseJSON(data). The code above is its copy and published for those that don’t use jQuery or use other libraries.


This page can be found by searching for:
jquery xdomainrequestXDomainRequest jqueryjquery xdrie8 jquery jsonjquery cross domain jsoncross domain jsonie8 cross domain ajaxjquery ie8 ajax crossdomainie8 cross domain jsonIE cross domain json

No comments: