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

March 31st, 2011 by Alex Leave a reply »

Lately, i was writing 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.

UPDATE:
As noted by Jonathan in comments, we can override the native jQuery xhr for browsers that have XDomainRequest (that is, for IE8 and others):

if ('XDomainRequest' in window && window.XDomainRequest !== null) {
 
  // override default jQuery transport
  jQuery.ajaxSettings.xhr = function() {
      try { return new XDomainRequest(); }
      catch(e) { }
  };
 
  // also, override the support check
  jQuery.support.cors = true;
 
}

So, after we include this code, we can run our xhr as usually:

$.ajax({
	url:'http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%20%3D%20%22'+$.URLEncode(oururl)+'%22&format=json&diagnostics=true?callback=?',
	type: 'GET',
	success: function(json) {
		// we have our json now
	}
});

8 comments

  1. Alex says:

    Jonathan, i like your solution much more, can i add it to a post?

  2. Elijah says:

    I recommend you steer users towards using JSON.parse() rather than eval(), especially for JSONp grabbed from a different domain. That’s a *huge* security hole.

  3. Alex says:

    @Elijah, I totally agree – adding that to post now

  4. Josh Sonnier says:

    @Jonathan, in IE8 I get “access is denied” during ajax get. Any idea why? Is that something you came across in testing?

  5. Alex says:

    @Josh, make sure you copied the code from Jonathan’s example and make sure that “detect IE CORS” code runs before any of your ajax

  6. vishak nag says:

    You just saved my Day. I was looking around to fix this problem for hours. Thanks a million for a clear explanation and quality code.