JSON-P output with Rails

Tweet

At the recent Web Directions 06 conference Cameron Adams and Sitepoint’s own Kevin Yank gave a talk on Mashups and APIs, with a mention of JSON-P.

JSON-P is a method of wrapping the JSON output of your API calls to allow other developers to call your API from within their page, bypassing the browser security mechanism.

If you peek at the HTML source of Web Connections you’ll notice we’re using JSON-P internally to provide the JS data to do the mapping. In the URL of the JSON-P call we can specify the parameter variable, callback or both, and this will allow others to use our JSON in their own mashups.

It also integrates nicely with the new Rails REST functionality. For example, Jeremy Keith’s profile page can also be output in JSON format by plugging ‘.js’ on the end. This is nice, but not very useful if you wanted to call it from another page.

Say Jeremy wanted to mashup this data on his own website to show who he’s friends with. He’d need some kind of JSON-P output so he can access the data from a script tag.

To add the JSON-P “padding” it’s just a matter of specifying a the “variable” parameter declaring the name of the Javascript variable you want the output assigned to. For this example we’ll specify ‘variable=personJSON’. Notice the difference in output? Now try specifying callback, or both callback and variable.

So how is this implemented behind the scenes? I have a helper function which I use throughout my application to output JSON:


class ApplicationController < ActionController::Base
  protected
    def render_json(json, options={})
      callback, variable = params[:callback], params[:variable]
      response = begin
        if callback && variable
          "var #{variable} = #{json};n#{callback}(#{variable});"
        elsif variable
          "var #{variable} = #{json};"
        elsif callback
          "#{callback}(#{json});"
        else
          json
        end
      end
      render({:content_type => :js, :text => response}.merge(options))
    end
end

I can then use this in my PeopleController#show action quite simply:


class PeopleController < ApplicationController
  def show
    @person = Person.find(params[:id])
    respond_to do |format|
      format.js { render_json @person.to_json }
    end
  end
end

and that’s all there is to it. JSON-P output with Rails.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.sitepoint.com/ Kevin Yank

    As we noted in our presentation, be aware of the security implications of JSON-P before you use an API of this type. If you don’t trust the provider of the API, you shouldn’t use JSON-P.

    Specifically, the site providing the API is able to send any JavaScript code it likes, including code that can compromise your site’s security.

  • jp

    so, with the above additions you get the dual functionality of returned json-p when accessing /show/1.js? versus html with /show/1 ?

    I am only ever able to get a json reponse with /show/1

    Am I missing something that allows for the retrieval of html vs json by using a file extension?

      def show
        @grocery = Grocery.find(params[:id])
       
        respond_to do |format|
            format.js { render_json @grocery.to_json }
           #format.js { render({:content_type => :js, :text =>  @grocery.to_json})}  
        end
      end
  • http://www.toolmantim.com timlucas

    Sorry jp, I was a little over-concise. You’ll also want to respond to html:

    
     def show
        @grocery = Grocery.find(params[:id])
    	
        respond_to do |format|
            format.html # falls back on the rhtml file
            format.js { render_json @grocery.to_json }
        end
      end
    
  • Pingback: Dr Nic » Supporting JSON callbacks in Rails

  • Pingback: Oddly Zen » Blog Archive » links for 2006-12-01

  • yeah

    so what about PUT and POST?

  • http://www.toolmantim.com timlucas

    yeah: what do you mean? There’s nothing GET specific about this. It’ll work just fine for PUT and POST too.

  • Michael Mahemoff

    Strictly speaking, this isn’t really the “JSONP” protocol AFAICT, but an application of the principle behind JSONP. That is, don’t just return JSON, but instead let the caller tell the server how to augment the JSON.

    JSONP is a simple protocol where there’s a single relevant parameter in the URL, called precisely “jsonp”. The idea is to be as general as possible. Using JSONP, it is possible to specify a callback or a variable or anything else you could imagine (e.g. you could pass in “jsonp” param as “el.style.backgroundColor=” to affect an element’s appearance).

    http://ajaxian.com/archives/jsonp-json-with-padding

  • http://www.toolmantim.com timlucas

    Michael: thanks for clearing that up. What’s being used as the definitive document describing JSONP “protocol”? Bob Ippolito’s blog post it? It’s definitely sensible to reduce it all down to one parameter and keep it consistent between services. I like the idea of keeping it generic, and calling it “jsonp”, though I think the concept of a “callback” parameter (as is used by a fair few APIs now) is somewhat more intuitive than a generic “jsonp” parameter.

  • Michael Mahemoff

    Tim, yes Bob’s original post is the closest thing to a definitive reference I know of. Maybe it could do with an RFC just to give it some more momentum and cred with the enterprise.

    Agree, “callback” works too and is arguably better as it is just as general and intuitive. Downside is it can’t be inlined as an anonymous function, so it may entail a bit more JS code than some JSONP usages, which may make people want “variable” too, which starts to get complex and error-prone for API providers, which is the argument for JSONP :). If I were implementing an API, I would probably provide all three, as they can happily co-exist.

  • Brian

    Can you show a cross browser JSON post example?

  • Pingback: Ragnarok» Blog Archive » [Ruby文档中文化团队]用NetBeans Ruby 实践JMaki1.0