🔨☕

An Introduction to Codable in Swift 4

The Codable protocol is an exciting new feature in Swift 4.

🤓 : Well actually, Codable is a type alias for a protocol composition of the Decodable and Encodable protocols.

Right. Here’s how it’s defined:

/// A type that can convert itself into and out of an external representation.
public typealias Codable = Decodable & Encodable

One of the most obvious use cases for Codable is persisting your native model objects to disk. Codable works hand-in-hand with the new Encoder and Decoder protocols — you can use an Encoder to serialize Codable objects (or complex object graphs) to Data, and you can use a Decoder to reconstruct your objects from Data. Out of the gate, the first classes in Foundation to implement Encoder / Decoder are:

🤓 : Who cares? We already have NSCoding and NSKeyedArchiver / NSKeyedUnarchiver.

Trust me, this is way better!

Goodbye NSCoding

Before we kick NSCoding to the curb, let’s look at one final example, for old times’ sake.

class Trip: NSObject, NSCoding {

    let place: String
    let miles: Int
    let start: Date?

    init(place: String, miles: Int, start: Date?) {
        self.place = place
        self.miles = miles
        self.start = start
    }

    required init?(coder decoder: NSCoder) {
        guard let place = decoder.decodeObject(forKey: "place") as? String else { return nil }
        self.place = place
        self.miles = decoder.decodeInteger(forKey: "miles")
        self.start = decoder.decodeObject(forKey: "start") as? Date
    }

    func encode(with coder: NSCoder) {
        coder.encode(place, forKey: "place")
        coder.encode(miles, forKey: "miles")
        coder.encode(start, forKey: "start")
    }
}

What sucks about this?

To serialize / deserialize to / from Data, you would use NSKeyedArchiver / NSKeyedUnarchiver as follows:

let trip = Trip(place: "San Francisco", miles: 2829, start: Date())

let data = NSKeyedArchiver.archivedData(withRootObject: trip)

let reconstitutedTrip = NSKeyedUnarchiver.unarchiveObject(with: data) as? Trip

Hello Codable

Come on in Codable, can I get you a drink?

struct Trip: Codable {
    let place: String
    let miles: Int
    let start: Date?
}

How about a beer? I meant to ask you about that boilerplate code.

// (tumbleweed)
// (distant screech of a red-tailed hawk)

No boilerplate, no initializers, nothing. Just four lines of code and you’re done. The compiler does all the work for you, as long as all of the properties of your object are also Codable. Check out the swift-evolution proposal for more details: SE–0166: Swift Archival & Serialization.

To serialize / deserialize to / from Data, you would use PropertyListEncoder / PropertyListDecoder as follows:

let trip = Trip(place: "San Francisco", miles: 2829, start: Date())

let encoder = PropertyListEncoder()
let data = try? encoder.encode(trip)

let decoder = PropertyListDecoder()
let reconstitutedTrip = try? decoder.decode(Trip.self, from: data!)

Goodbye (NS)JSONSerialization

Before JSONSerialization rides off into the sunset, let’s look at one last example. Given our Trip class defined in the example above, we can extend the class to initialize itself from a dictionary, and to create a dictionary representation of itself.

extension Trip {

    private static let dateFormatter = ISO8601DateFormatter()

    convenience init?(dictionary: [String: Any]) {
        guard let place = dictionary["place"] as? String else { return nil }
        guard let miles = dictionary["miles"] as? Int else { return nil }
        var start: Date? = nil
        if let dateString = dictionary["start"] as? String {
            start = Trip.dateFormatter.date(from: dateString)
        }
        self.init(place: place, miles: miles, start: start)
    }

    func createDictionaryRepresentation() -> [String: Any] {
        var dictionary = [String: Any]()
        dictionary["place"] = place
        dictionary["miles"] = miles
        if let date = start {
            dictionary["start"] = Trip.dateFormatter.string(from: date)
        }
        return dictionary
    }
}

Again, this is a lot of tedious, error-prone, soul-crushing boilerplate.

Now, assuming we have some JSON data coming from a server, we can instantiate a Trip object as follows:

let string = "{\"place\":\"San Francisco\",\"miles\":2829,\"start\":\"2017-06-23T13:30:21+00:00\"}"

let data = string.data(using: .utf8)!

let json = (try! JSONSerialization.jsonObject(with: data, options: [])) as! [String: Any]

let trip = Trip(dictionary: json)

And if we have a Trip object that we need to serialize to JSON, we can do this:

let trip = Trip(place: "San Francisco", miles: 2829, start: Date())

let data = try! JSONSerialization.data(withJSONObject: trip.createDictionaryRepresentation(), options: [])

Hello JSONDecoder / JSONEncoder

I’ll spare you the jokes and get right to the punch line: with the brand new JSONDecoder / JSONEncoder classes, there is no boilerplate. Given the Trip struct defined above, all we have to do to instantiate a Trip from JSON data is this:

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let trip = try? decoder.decode(Trip.self, from: data)

And all we have to do to serialize a Trip to JSON is this:

let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let data = try? encoder.encode(trip)

🎉

Let’s take a moment to celebrate how incredible this is. Over all these years we’ve collectively wasted millions of hours writing and debugging all of this boilerplate code. Codable marks the dawning of a new age. 🍻 Cheers!

OK party time is over. Let’s pour one out for NSCoding, NSJSONSerialization, all of those model frameworks, and the thousands of Swift JSON parsing libraries.

R.I.P. Born
NSCoding early 90’s?
NSJSONSerialization 2011
RestKit 2009
Mantle 2012
SwiftyJSON 2014
Argo 2014
ObjectMapper 2014
Gloss 2015
Freddy 2015

Further Reading

The examples presented in this post are relatively simple, and there’s a lot more to explore with Codable, JSONEncoder, and JSONDecoder. Here is some recommended reading: