Simon Willison’s Weblog


Pragmatism, purity and JSON content types

6th February 2009

I started a conversation about this on Twitter the other day, but Twitter is a horrible place to have an archived discussion so I’m going to try again here.

If you’re producing a JSON API for other people to use (as opposed to an API that’s only really meant for your own local Ajax responses), you need to decide which Content-Type to use. The best option is not entirely obvious.

RFC 4672 defines JSON and reserves application/json as the preferred media type. The problem is that most browsers will prompt you to download the file rather than displaying it inline (as they would for text/plain or application/javascript). One of my favourite qualities of REST-style APIs is that they enable exploration and debugging using just a browser—using application/json throws a big, frustrating road block in the way. There are ways of telling your browser to treat application/json in the same way as text/plain but that doesn’t really help you if your aim is to create an API that’s easy for other developers to use.

It’s also worth mentioning that if you are returning JSONP (with an extra callback function wrapped around the JSON response to enable the dynamic script tag hack) you HAVE to serve as application/javascript—otherwise the script you are providing won’t be executed by the browser. Don’t forget to include charset=UTF8 as well (for both types of response).

So, it’s pragmatism v.s. purity. The correct thing to do is to return application/json, but doing so makes your API harder for developers to use.

In a brief, non-comprehensive review of some existing JSON APIs (FriendFeed, Flickr, Google Social Graph etc) I couldn’t find any that were using application/json, presumably for this exact reason.

Using the Accept: header

The Accept: header is one of my least favourite parts of HTTP. I like to be confident that if I send a URL to someone, they’ll get back exactly the same bytes as I did when I retrieved it myself (I distrust language negotiation for the same reason). However, a number of people suggested it on Twitter and it looks like it could be a useful solution to this problem.

I’m currently considering the following: ONLY use the application/json Content-Type in reply to requests that include application/json in their Accept header—essentially allowing clients that care about the correct content type to opt-in to receiving it. Everyone else (browsers included) gets application/javascript, which is less correct (though not an all-out lie, since JSON is a subset of JavaScript) but solves the usability problem.

A couple of things worry me about this. Firstly, is this a reasonable thing to use Accept for? Secondly, is there a chance that browsers might add application/json to their Accept header at some point in the future? Safari currently sends text/xml,application/xml,application/xhtml+xml,text/html; q=0.9,text/plain; q=0.8,image/png,*/*; q=0.5 while Firefox sends text/html,application/xhtml+xml,application/xml; q=0.9,*/*; q=0.8. Would it be smarter to look for */* and serve the incorrect Content-Type to those requests and the correct one to everything else?

An alternative is to simply allow people to specify “JSON with a browsable Content-Type” as an alternative format option, or to enable a “pretty=1” query string argument which returns the response as text/plain and potentially pretty prints it as well. I haven’t yet decided if this is better than messing around with the Accept header.