-
Notifications
You must be signed in to change notification settings - Fork 119
JSONParser
Originally, Freddy did not parse JSON - it converted Data
to the JSON
enum by running the data through JSONSerialization
and then recursively walking through the returned Any
, converting each object to the corresponding JSON
case. However, in real world use, we noticed a severe performance issue with large JSON documents - multiple seconds (!) to parse a JSON document of about 1.5 MiB on an iPhone 5.
Profiling led to the conclusion that the problem was not JSONSerialization
itself (which is quite fast), but with the recursive walk over the returned Any
, switching over the types to convert to JSON
. To remedy this, Freddy now includes a pure Swift JSON parser which emits the JSON
enum directly. This JSON parser is not as fast as JSONSerialization
(see below), but it is close enough for our work.
All of the following tests can be run on the benchmarks
branch of this repository. Tests were run on a 2013 MacBook Pro with a 2.7 GHz i7 using Xcode 7.2 (Swift 2.1). The test document is downloaded by a build hook; at the time of this writing, it was about 16 MiB.
Parser | Time (lower is better) |
---|---|
JSONSerialization |
0.25s |
Freddy.JSONParser |
0.75s |
Notes: JSONSerialization
emits an Any
. Freddy.JSONParser
emits a Freddy.JSON
. Our parser is approximately 3x slower than NSJSONSerialization
.
Technique | Time (lower is better) |
---|---|
JSONSerialization then recursive type-switching |
6.29s |
Freddy.JSONParser |
0.75s |
Notes: These tests convert Data
to Freddy.JSON
. The conversion process from JSONSerialization
's Any
to Freddy.JSON
is extremely expensive - about 6 seconds for this data. This is approximately 8.4x slower than Freddy.JSONParser
.
Technique | Time (lower is better) |
---|---|
JSONSerialization to Objective-C model classes |
0.58s |
Freddy.JSONParser to Swift structs |
2.08s |
Notes: This comparison is not entirely fair, as the Objective-C model class deserialization is deficient (in comparison to the Freddy style): it does not return any error information if the JSON data doesn't match what the code expects, which can lead to incorrect types assigned to properties or application crashes (potentially both). We left this benchmark in because this is how much Objective-C JSON parsing code has been written, even though it's technically incorrect. We are within a factor of 4 with full Swift type-checking and error handling.
If maximum JSON performance (with Swift type safety) is your goal, the best advice is to follow standard Swift optimization tips. An optimization tip of particular note is that generic functions are not specialized across module boundaries, so you can achieve faster code by avoiding some of Freddy's generic functions. For example, this:
struct Foo {
var bar: Int
var baz: String
}
extension Foo: JSONDecodable {
init(json: Freddy.JSON) throws {
bar = try json.decode(at: "bar")
baz = try json.decode(at: "baz")
}
}
makes use of Freddy's generic decode
method. You can get a slight performance increase by using the non-generic alternatives to decode
:
struct Foo {
var bar: Int
var baz: String
}
extension Foo: JSONDecodable {
init(json: Freddy.JSON) throws {
bar = try json.getInt(at: "bar")
baz = try json.getString(at: "baz")
}
}
This should not be necessary in most cases, as the performance difference is usually small.
Created by Big Nerd Ranch 2015