JSONP vs. JSON and Twitter Search API

The main concern of this post is talking about the difference between json and jsonp formats and I will discuss consuming Twitter Search API as an example for clarification.

Lets start by opening this URL in browser or creating a GET request with it using Fiddler:
http://search.twitter.com/search.json?q=egypt
Here we are providing “egypt” as a query to search for. The result is a json format response that looks like this (I only kept the first result here) :

{
	"completed_in": 0.109,
	"max_id": 218732531992895489,
	"max_id_str": "218732531992895489",
	"next_page": "?page=2&max_id=218732531992895489&q=egypt",
	"page": 1,
	"query": "egypt",
	"refresh_url": "?since_id=218732531992895489&q=egypt",
	"results": [{
		"created_at": "Fri, 29 Jun 2012 15:47:54 +0000",
		"from_user": "newgooglenews",
		"from_user_id": 389322092,
		"from_user_id_str": "389322092",
		"from_user_name": "New Google News",
		"geo": null,
		"id": 218732531992895489,
		"id_str": "218732531992895489",
		"iso_language_code": "en",
		"metadata": {
			"result_type": "recent"
		},
		"profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1682800149\/news_normal.png",
		"profile_image_url_https": "https:\/\/si0.twimg.com\/profile_images\/1682800149\/news_normal.png",
		"source": "<a href="http:\/\/ibytes.net" rel="nofollow">NewGoogleNews<\/a>",
		"text": "Egypt President-elect Mohamed Mursi to speak in Cairo - BBC News http:\/\/t.co\/1a6UEqRH #World #news",
		"to_user": null,
		"to_user_id": 0,
		"to_user_id_str": "0",
		"to_user_name": null
	}]
}

Now we request the jsonp response instead. Add a “callback” query string key/value to the URL: callback=OnSearchComplete (“callback” is a standard keyword that is used bu most API providers but you have to check API documentation first and “OnSearchComplete” is just a function name that you will add in the page)
So URL will be:
http://search.twitter.com/search.json?q=egypt&callback=OnSearchComplete

The resulted response will be like:

OnSearchComplete(
{
	"completed_in": 0.109,
	"max_id": 218732531992895489,
	"max_id_str": "218732531992895489",
	"next_page": "?page=2&max_id=218732531992895489&q=egypt",
	"page": 1,
	"query": "egypt",
	"refresh_url": "?since_id=218732531992895489&q=egypt",
	"results": [{
		"created_at": "Fri, 29 Jun 2012 15:47:54 +0000",
		"from_user": "newgooglenews",
		"from_user_id": 389322092,
		"from_user_id_str": "389322092",
		"from_user_name": "New Google News",
		"geo": null,
		"id": 218732531992895489,
		"id_str": "218732531992895489",
		"iso_language_code": "en",
		"metadata": {
			"result_type": "recent"
		},
		"profile_image_url": "http:\/\/a0.twimg.com\/profile_images\/1682800149\/news_normal.png",
		"profile_image_url_https": "https:\/\/si0.twimg.com\/profile_images\/1682800149\/news_normal.png",
		"source": "<a href="http:\/\/ibytes.net" rel="nofollow">NewGoogleNews<\/a>",
		"text": "Egypt President-elect Mohamed Mursi to speak in Cairo - BBC News http:\/\/t.co\/1a6UEqRH #World #news",
		"to_user": null,
		"to_user_id": 0,
		"to_user_id_str": "0",
		"to_user_name": null
	}]
}
);

So its just like the first one but the json object is passed to a function call with OnSearchComplete as the name of the function.

This is the idea behind jsonp; we will have a function in the page with this signature:

function OnSearchComplete(tweets) {
    //body
}

And this function will be called as soon as the page loads this JavaScript file. Yes, the server this way is just exposing a simple JavaScript file that only calls your callback function passing the requested data as json object. And we can add this file -like we add any JavaScript file to the page- by adding this to the page:

<script src="http://search.twitter.com/search.json?q=egypt&callback=OnSearchComplete" type="text/javascript"></script>

or of course something like this:

$("<script>")
                 .attr("language", "javascript")
                 .attr("type", "text/javascript")
                 .attr("src", "http://search.twitter.com/search.json?q=egypt&callback=OnSearchComplete")
                 .appendTo($("head"));

So what are the benefits of using jsonp?

  • No XMLHTTPRequest object is used (no ajax)
  • No Same-origin policies limitations
  • No need for cross-domain policy files on servers

This is a simple complete solution for Twitter searching:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="http://code.jquery.com/jquery-1.7.2.min.js" type="text/javascript"></script>
    <script language="javascript" type="text/javascript">

        $.twitter = function (query, callback, options) {
            
            if (query == undefined || query == null || query == "") {
                throw new Error("search query is not defined!");
            }

            if (callback == undefined || callback == null || callback == "") {
                throw new Error("callback function is not defined!");
            }

            var twitterSearchUrl = "http://search.twitter.com/search.json?q=" + query + "&callback=" + callback;
            if (options != undefined && options != null) {
                twitterSearchUrl += "&" + $.param(options);
            }

            $("<script>")
                    .attr("language", "javascript")
                    .attr("type", "text/javascript")
                    .attr("src", twitterSearchUrl)
                    .appendTo($("head"));

            var returnObject = {};
            returnObject.callback = callback;
            returnObject.LoadPage = function (pageString) {
                var twitterSearchPageUrl = "http://search.twitter.com/search.json" + pageString + "&callback=" + this.callback;
                $("<script>")
                    .attr("language", "javascript")
                    .attr("type", "text/javascript")
                    .attr("src", twitterSearchPageUrl)
                    .appendTo($("head"));
            };

            return returnObject;
        };

        var twitterSearcher;
        
        $(function () {
            $("#ButtonSubmit").click(function (event) {
                twitterSearcher = $.twitter($("#TextSearch").val(), "OnSearchComplete", { rpp: $("#SelectResultsPerPage").val(), result_type: "recent" });
            });
        });


        function OnSearchComplete(tweets) {
            if (tweets != undefined && tweets != null) {
                $("#TweetsList").html("");
                $("#PageCountContainer").show();
                $("#PageCount").text(tweets.page);
                $("#PrevPageLink").unbind("click");
                $("#NextPageLink").unbind("click");
                if (tweets.next_page != undefined && tweets.next_page != null) {
                    //twitterSearcher.LoadPage(tweets.next_page);
                    $("#NextPageLink").show();
                    $("#NextPageLink").click(function () {
                        
                        twitterSearcher.LoadPage(tweets.next_page);
                        return true;
                    });
                }
                else {
                    $("#NextPageLink").hide();
                }
                
                if (tweets.previous_page != undefined && tweets.previous_page != null) {
                    //twitterSearcher.LoadPage(tweets.next_page);
                    $("#PrevPageLink").show();
                    $("#PrevPageLink").click(function () {
                        
                        twitterSearcher.LoadPage(tweets.previous_page);
                        return true;
                    });
                }
                else {
                    $("#PrevPageLink").hide();
                }
                
                for (var i = 0; i < tweets.results.length; i++) {
                    $("#TweetsList").append($("<li>").html("<img src='" + tweets.results[i].profile_image_url + "' /> " + tweets.results[i].text));
                }
            }
        }

    </script>
</head>
<body>
    <input id="TextSearch" type="text" />
    <input id="ButtonSubmit" type="button" value="Search" />
    <select id="SelectResultsPerPage">
        <option value="10">10 Results</option>
        <option value="50">50 Results</option>
        <option value="70">70 Results</option>
        <option value="100">100 Results</option>
    </select>
    <ol id="TweetsList"></ol>
    <div>
        <a style="display:none" id="PrevPageLink" href="javascript:;">Prev. Page</a>
        <span style="display:none" id="PageCountContainer"> <span id="PageCount"></span> </span>
        <a style="display:none" id="NextPageLink" href="javascript:;">Next Page</a>
    </div>
</body>
</html>
Advertisements
This entry was posted in Software and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s