As a mobile app developer, I work with APIs a lot. The vast majority of them use JSON. Due to a refactor of the MBTA’s API for the Boston T (and on past projects) I had the extreme misfortune to work under the JSON API format.
The reason it’s so awful for mobile devs is primarily that JSON API was designed to work with dynamic Javascript web frontends and not native languages.
OO vs. JS
Native mobile apps are written in Swift or Objective-C for iOS, and Kotlin or Java for Android. These are robust, fully featured languages which descend from the likes of C and incorporate features that encourage object oriented design. As a result, we native devs more often than not turn the JSON we receive into objects with (strongly or dynamically) typed properties, methods, etc.
Or we try to, anyway, since JSON API 1.0 makes this exceedingly difficult.
You see, JSON API 1.0 separates the data from the identifier and type. The reason for this is several fold, but it comes down to how you expect to be using the JSON in Javascript (with no thought put into how native devs will use it).
Let’s illustrate this by comparing Swift and Javascript uses, starting with Javascript.
Suppose I’m creating something like the Facebook home screen: a list of Post
s created by User
s that can be Like
d by other User
s. There’s an API somewhere that will give you everything you need to build the frontend. How do you want that API to look? Well, there are some things you’re worried about, and some things you’re not.
You’re definitely going to want a list of posts. That’s obvious. Each post, though, will have nested information associated with it: specifically, likes of that post. Those likes will have a user associated with them, as will the post itself. One user can like multiple posts, of course, plus a poster can also be a liker, so you might have to include one user multiple times… and if the user model has a lot of attributes, that might greatly increase the size of the JSON the API has to send. So, to cut down on this, you might decide to include only the id of the user in the nested information, and send a separate list of users that contains each relevant user only once. To allow for polymorphic situations, you might include the type of the model along with the id. So you’d expect the API to send something like this:
{ "posts": [{ "id": 1, "type": "Post", "data": { "text": "I am a post", "likes": [{"id": 1, "type": "Like"}, ...] "author": {"id": 1, "type": "User"} }, }, ...], "likes": [{ "id": 1, "type": "Like", "data": { ..., "author": {"id": 2, "type": "User"} }, ...], "users": [{ "id": 1, "type": "User" "data": { "name": "Alice", ... }, { "id": 2, "type": "User" "data": { "name": "Bob", ... }, ...] }
As you can see, the post’s data has a list of like ids with a type and an author with an id and type, and the like’s data has an author with an id and type. This seems great! You’ve minimized what’s returned by the server. In your front end you’ll use this more or less as is: cycling through the posts, and filtering likes and users by id and type as necessary.
Now let’s see how this works natively in Swift. In idiomatic Swift, models are just data holders. So you’d want to implement the Post class like this:
class Post { var objectId: Int? var text: String? var author: User? var likes: [Like]? }
To enable instantiating this class from JSON, we’d use something like the Codable protocol, which we can enable like this:
class Post { var objectId: Int? var text: String? var author: User? var likes: [Like]? enum PersonCodingKey: String, CodingKey { case objectId = "id" case text case author case likes } }
So far so good. Let’s try decoding the post array we get from the server in the standard Swift way:
JSONDecoder().decode([Post].self, from: jsonData)
What do we get? Well, we get an array of Post
objects with every property nil
except for objectId
. Whaaaaaat? That’s unusable; we certainly won’t be creating any UIs with just that information.
You see, JSONDecoder
goes through the array of JSON objects trying to match JSON keys to property names. So it looks for “text” (which is hidden inside the JSON’s “data” property), can’t find it of course, and moves on to look for “likes” (also hidden in “data”), which it can’t find either.
Ok, fine, you think, maybe we can just add a data
property to Post
. That only gets you one step further, though, since then your list of likes will a) be nested within a superfluous property “data,” and b) will have every property equal to nil
as well. In order to successfully decode the JSON, you’ll need to write a lot of custom code, one way or another, making the whole Codable
protocol close to useless. And this doesn’t even address situations where you don’t have control over the model (since you can’t add properties through extension
). All of a sudden, all the tools you’re used to having at your disposal to quickly create objects from JSON are inapplicable.
That’s why I hate JSON API 1.0. It was optimized for problems I don’t have, and creates a ton more work (and cost!) by invalidating swathes of solutions for well known problems that I, as a mobile dev, do have.
Please, if you’re building an API and you ever plan to have an app, in the name of all that’s holy, DON’T USE JSON API 1.0!