Blog

Swift Tip: Custom Types for Codable Conformance

Sometimes we want to add Codable conformance to a struct when one of its member types isn’t codable. Revisiting Swift Talk episodes #72 and #73, where we built an app for comparing running routes, we encountered just such a case.

Here’s the struct we want to make codable:

struct Graph {
    var edges: [CLLocationCoordinate2D: Destination]

    struct Destination: Codable, Equatable {
        var location: CLLocationCoordinate2D
        var distance: CLLocationDistance
        var trackNames: [Track.Name]
    }
}

In this example, we’re building a graph of running routes on which we can use Dijkstra’s algorithm to find the shortest route between two points.

However, CLLocationCoordinate2D is neither Codable nor Hashable. This means we can’t use a Core Location coordinate as key in a dictionary, and we don’t get auto-synthesized support for coding and decoding on this struct.

The easiest approach would be to conform CLLocationCoordinate2D to Codable ourselves:

extension CLLocationCoordinate2D: Codable {
    public func encode(to encoder: Encoder) throws {
        // ...
    }

    public init(from decoder: Decoder) throws {
        // ...
    }
}

We might regret this decision in the future: what if Apple makes CLLocationCoordinate2D codable in a future update? There is a decent chance that they might use slightly different coding keys, and we would no longer be able to read the data persisted with our own Codable conformance (for more detail, see this post on the Swift Evolution mailing list).

So we decided to use a different approach, creating our own Coordinate struct:

struct Coordinate: Codable, Hashable {
    let latitude, longitude: Double
}

Since this struct only contains two Double properties, the compiler synthesizes the Codable and Hashable implementations for us. Using this custom type, we can now conform our Graph struct to Codable and Hashable without any further work on our part:

struct Graph: Codable, Hashable {
    var edges: [Coordinate: Destination]

    struct Destination: Codable, Equatable, Hashable {
        var location: Coordinate
        var distance: CLLocationDistance
        var trackNames: [Track.Name]
    }
}

Once we’re independent of the CLLocationCoordinate2D type for encoding and decoding, a potential Codable implementation by Apple won’t break our code anymore. There’s a downside to this approach though: we have to convert between the Coordinate and CLLocationCoordinate2D types at a few points in our code.

To do this, we create two initializers:

extension CLLocationCoordinate2D {
    init(_ coordinate: Coordinate) {
        self = .init(latitude: coordinate.latitude, longitude: coordinate.longitude)
    }
}

extension Coordinate {
    init(_ coordinate: CLLocationCoordinate2D) {
        self = .init(latitude: coordinate.latitude, longitude: coordinate.longitude)    
    }
}

If you’d like to learn more about Codable, our book Advanced Swift has a full chapter on encoding and decoding, and you can find useful examples in Codable Enums and Networking with Codable, both published earlier this year. 🙂


Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts