json icon

JSON library comparison in Swift

These are not the library to parse and manipulate JSON in Swift that missing. But the arrival of a new library, Freddy, made me want to make a comparison between some of them. So this post will aim to compare the design and the performance between the NSJSONSerialization, SwiftyJSON and Freddy.

The Context

First of all, the JSON (JavaScript Object Notation) is a human-readable data format to transmit and store informations. It consists in attribute-value pairs easily transferable to all languages, swift included. For example this dictionary in Swift:

[
  "type": "Foo",
  "length": 321,
  "fruits": ["apple", "orange"]
]

Is equivalent to the following one in JSON:

{
  "type": "Foo",
  "length": 321,
  "fruits": ["apple", "orange"]
}

Pretty similar, no? So now we are going to try to parse some JSON strings / datas in Swift. Using the standard library NSJSONSerialization it looks like that:

let json = "{\"type\":\"Foo\",\"length\":321,\"fruits\":[\"apple\",\"orange\"]}"
let data = json.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!

do {
  let obj = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)

  // The object is ready to use here
}
catch {
  print("Error when parsing the JSON: \(error)")
}

Here we have a JSON as a string json that we convert to a NSData in order to try to parse it to have a Swift instance of the object. The created object is a Dictionary which contains 3 keys (type, length and fruits) like in the first example.

Now that we have our swift object how to use it?

if let dict = obj as? [String: AnyObject] {
  if let type = dict["type"] as? String {
    print(type) // Foo
  }

  if let length = dict["length"] as? Int {
    print(length) // 321
  }

  if let fruits = dict["fruits"] as? [String] {
    print(fruits) // ["apple", "orange"]
  }
}

As you can see, each time we need access to an element we need to cast it to its corresponding type. Here the example is simple but image if you have to go deeper in the values. That’s not produce easy readable code. Now imagine that you want map the JSON into an object. There is nothing planned in native. This is why many libraries have flourished in open-source on github. But in this post we will only focus on SwiftyJSON and Freddy because the first one is really popular and because the second one is very interesting in the code design point of view.

The Use Case

As part of this post we will use a simplified version of the results returned by the RandomUser API (http://api.randomuser.me):

{
  "results": [
    {
      "user": {
        "name": {
          "first": "sergio",
          "last": "richards"
        },
        "registered": 1267899572
      }
    },
    {
      "user": {
        "name": {
          "first": "don",
          "last": "fox"
        },
        "registered": 1200124706
      }
    }
  ],
  "version": "0.7"
}

Here we have a JSON object with 2 keys:

  • results: the results as a list of object which represent the users:
    • name: with the first and last name values.
    • registered: which is an unique identifier.
  • version: the API version number.

To manipulate the JSON we are going to transform it to an NSData ready to use by the parsers:

let data = "{ ... }".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!

Then let’s create the User model:

struct User {
  let firstname: String
  let lastname: String
  let id: Int
}

To finish we will parse the JSON data and map the values to the User instances for each of the libraries.

SwiftyJSON Version

Let’s begin with SwiftyJSON. For example to parse the JSON and access to the firstname value of the first user we need to write something like that :

let json = JSON(data: data)

let firstname = json["results"][0]["user"]["name"]["first"].stringValue
print(firstname) // sergio

We first parse the JSON. There is no error to check, because if anything goes wrong, the json will be empty and we can check that by getting the json.error. Than we can retrieve the firstname in one line by chaining the path and cast the result to a String only in the end. Everything is hidden by the library.

To map the users in the User class we will need a bit more of work. To have something generic, we are going to declare a new protocol :

protocol JSONDecodable {
  init(json: JSON)
}

JSONDecodable requires conforming types to implement an initializer that takes an instance of JSON. User needs to conform to this protocol.

extension User: JSONDecodable {
  init(json: JSON) {
    firstname = json["name"]["first"].stringValue
    lastname  = json["name"]["last"].stringValue
    id        = json["registered"].intValue
  }
}

If we pass an instance of JSON when we initialize a new User, the properties will be populated using the JSON stored values.

To have our list of users we simply have to do this:

let users = json["results"].arrayValue.map {
  User(json: $0["user"])
}

print(users)
// [
// User(firstname: "sergio", lastname: "richards", id: 1267899572),
// User(firstname: "don", lastname: "fox", id: 1200124706)
// ]

For each user in the results array we map the json data to the User initializer. And we have our list of users.

Now take a look at the Freddy version.

Freddy Version

Let’s start in the same way with Freddy than SwiftyJSON. We are firstly going to parse the JSON and access to the firstname value of the first user we need to write something like that :

do {
  let json = try JSON(data: data)

  let firstname = try json.string("results", 0, "user", "name", "first")
  print(firstname) // sergio
}
catch {
  // Handle errors
}

This is substantially the same thing than SwiftyJSON except that we must address the errors.

To map users, Freddy already provides a JSONDecodable protocol but the init can throw errors:

protocol JSONDecodable {
  init(json: JSON) throws
}

We take care of User conforms to the protocol:

extension User: JSONDecodable {
  init(json: JSON) throws {
    firstname = try json.string("name", "first")
    lastname  = try json.string("name", "last")
    id        = try json.int("registered")
  }
}

And here how we can map the Users:

let results = try json.array("results").flatMap { $0["user"] }
let users   = try results.map(User.init)

print(users)
// [
// User(firstname: "sergio", lastname: "richards", id: 1267899572),
// User(firstname: "don", lastname: "fox", id: 1200124706)
// ]

Again, this is very similar to SwiftyJSON.

Now we are going to compare the performance between both because Freddy uses its own JSON parser.

Performance Comparison

Referring to this page and this post (in french sorry) it seems than if you just want to parse JSON and nothing else Freddy will be up to 2x more slower than NSJSONSerialization and SwiftyJSON. But it is a real case? Why parse the JSON if it is not for use behind?

However if you want check the whole JSON object by checking each type Freddy seems 8.4x more performant than NSJSONSerialization. I didn’t make performance comparaison with SwiftyJSON but it should be worst because it makes much more control.

To Go Further

SwiftyJSON and Freddy adopt 2 different approaches. Freddy is focused on an approach to parsing JSON that feels right in Swift while SwiftyJSON aims to make the JSON manipulation easier.

With small data sets you can use both according to your feelings. However if you work with large and complex data you should take performance into consideration. If you just need to parse your JSON to take small chunk, SwiftyJSON and Freddy are equivalent, but in the contrary, Freddy seems better to map JSON to Swift objects.

Any questions? Just leave a comment.

Edit:

Thank you to John Gallagher for its precisions. I’ve updated the post with its comments.

1 Star2 Stars3 Stars4 Stars5 Stars (4 votes, average: 4.75 out of 5)
Loading...

2 comments

Cancel

Time limit is exhausted. Please reload CAPTCHA.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  1. John Gallagher · February 4, 2016

    Disclaimer: I’m one of the authors of Freddy.

    I left a longer response on the blog post with performance comparisons (http://swift-tuto.fr/177/json-les-performances-entre-swiftyjson-et-freddy/#comment-21), but I’ll comment here too. Please don’t do performance comparisons with Swift projects built in Debug mode. If you run the performance test in Release mode, worst case appears to be that Freddy’s parser is a little worse than 2x slower than NSJSONSerialization (nowhere near 10x!). This also doesn’t account for the time it takes to extract data out after parsing, which should be much faster in Freddy than SwiftyJSON, because SwiftyJSON has to take the performance hit of doing a runtime downcast check when you access objects (which is very slow if you have to do it many times).

    Your conclusion that SwiftyJSON will be faster if you only need a relatively small portion of the JSON is probably true, but the gap (in Release mode) is not nearly as large as has been reported here, and that gap will shrink – and probably become negative, eventually – as you need to get proportionally more data out.

    • Yannick Loriot · February 4, 2016

      I updated the post, thank you for your precisions.

      You are right, I missed the Debug vs Release mode. Freddy is a great lib and I think people should take a look and adopt.