I Did Not Shoot the Marshal

JSON in Swift 4 and Open Source Thoughts

Swift 4 brings a cornucopia of welcome (and a few unwelcome) updates. One of the ones I am excited about is the introduction of Codable. As the owner of a popular open source JSON mapping framework, I really look forward to Codable making my work obsolete. It’s the death of an era, but it is a necessary death.

Here is what mapping JSON to an object looks like using Marshal.

struct User: Unmarshaling {
var id: Int
var name: String
var email: String
init(object: MarshaledObject) throws {
id = try object.value(for: "id")
name = try object.value(for: "name")
email = try object.value(for: "email")
}
}
let users: [User] = try json.value(for: "users")

view rawunmarshaling.swift hosted with ❤ by GitHub

And here is the simplest possible implementation of a Codable student.

struct Student: Codable {
var name: String
var age: Int
var email: String
}
let student = Student(name: "Jason", age: 99, email: "jason@example.com")
let encoder = JSONEncoder()
let data = try encoder.encode(student)
let decoder = JSONDecoder()
let newStudent = try decoder.decode(Student.self, from: data)

view rawimplicit_codable.swift hosted with ❤ by GitHub

The JSON keys we’re looking for here are implicitly defined to be the same as our variable names. If we have keys that don’t match the names of our variables, say for example, the key for age was “student_age” instead of just “age”, we can explicitly provide our keys like this:

struct Student: Codable {
var name: String
var age: Int
var email: String
enum CodingKeys: String, CodingKey {
case name
case age = "student_age"
case email
}
}

view rawexplicit_codable.swift hosted with ❤ by GitHub

The nice thing about Codableis that we get implementations of both Encodableand Decodable for free. However, the keys can still be implicit, and I find this to be a lurking danger. Having had too many gotcha moments using Mantle long ago, I felt that it was important to have the mapping always be explicit. This is why Marshal never embraced reflection.

Explicit is better than implicit.
- The Zen of Python

But, you don’t have to take my word for it. The Swift blog, in it’s final section in the Working with JSON in Swift post had this to say:

“Converting between representations of the same data in order to communicate between different systems is a tedious, albeit necessary, task for writing software. Because the structure of these representations can be quite similar, it may be tempting to create a higher-level abstraction to automatically map between these different representations. For instance, a type might define a mapping between snake_case JSON keys and camelCase property names in order to automatically initialize a model from JSON using the Swift reflection APIs, such as Mirror. However, we’ve found that these kinds of abstractions tend not to offer significant benefits over conventional usage of Swift language features, and instead make it more difficult to debug problems or handle edge cases. In the example above, the initializer not only extracts and maps values from JSON, but also initializes complex data types and performs domain-specific input validation. A reflection-based approach would have to go to great lengths in order to accomplish all of these tasks. Keep this in mind when evaluating the available strategies for your own app. The cost of small amounts of duplication may be significantly less than picking the incorrect abstraction.”

Now, as far as I understand, Codable is not actually using any reflection—the compiler is actually generating those functions for you—but I think that the sentiment remains for me. I personally am going to always explicitly define my CodingKeys, even if they all match my variable names.

Core Data?

One thing I have yet to completely understand is how Codeable works with CoreData. Marshal has a protocol that allows the user to update an existing model with a dictionary and some context. It appears, according to my cursory looks that the userInfo dictionary on the decoder is where you want to pass along any context. But, I haven’t looked very deep because I don’t personally use CoreData in any of my projects.

Beyond JSON

One of the “cool” things about Marshal is that it wasn’t just about JSON. It could handle anything that conformed to the simple MarshaledObject protocol. This means you could use the same initialization methods to create an object from JSON as you could from a plist, CloudKit, or some other data source. Codable also supports this sort of behavior through use of different decoders. Personally, I think the Codable approach is more elegant in this regard.

Open Source Thoughts

I really enjoyed using and working on Marshal. But wow was it a lot of work. At the time of writing this article Marshal has 477 stars. Which, is a good amount, but trivial compared to many other projects. Even with this relatively low usage, it was very burdensome to maintain between having a job, family, and hobbies/other projects. I definitely have a completely new respect for big people in the open source scene, and a strong opinion that companies that use open source should contribute to open source, whether that contribution be opening up their own code, encouraging employees to do open source work on the clock (not on their own time), or financial donations.

I’m happy that this Codable work was part of Swift’s development as an open source effort. Even though sometimes (like anything you love or work with all day) Swift drives me nuts with some of its quirks/decisions, I appreciate all the efforts of the Swift team and the community as a whole. Open source is hard. Communication is hard. Making everyone happy is hard. I’d say this is one of the hardest problems in Software Engineering. But I’m glad so many people are working together so that we can all enjoy making apps more.

Read more