Swift + JSON

·

Abstract

I deal with JSON deserialization in Swift in a moderately elegant way, without polluting codebase with introducing new operators and initializers.

Context

This is a short, written hastily on a train, post on a topic that many have written about. I wrote it in response to Radek Pietruszewski’s article “Rethinking JSON in Swift”. I encourage you to read Radek’s post for more introduction.

Protocol

In my checkers game I use JSON to save and load game state. I use SwiftyJSON to deal with JSON parsing. On “my side” I implement JSONConvertible protocol:

protocol JSONConvertible {
    func toJSON() -> JSON
    static func fromJSON(json: JSON) -> Self?
}

Swift 1.1

In the old days of Swift 1.1, to implement the protocol, I was using unwrapped — an equivalent of Radek’s ensure:

struct Piece {
    let id: Int
    let player: Player
    let isCaptured: Bool
    let isKing: Bool
}


extension Piece: JSONConvertible {
    func toJSON() -> JSON {
        return JSON([
            "id": id,
            "player": player.toJSON().stringValue,
            "isCaptured": isCaptured,
            "isKing": isKing,
        ])
    }

    static func fromJSON(json: JSON) -> Piece? {
        return unwrapped(
            json["id"].int,
            Player.fromJSON(json["player"]),
            json["isCaptured"].bool,
            json["isKing"].bool
        ).map { (id, player, isCaptured, isKing) in
            // newlines added to fit in
            // my narrow blog layout
            Piece(id: id,
                  player: player,
                  isCaptured: captured,
                  isKing: king)
        }
    }
}

Swift 1.2

In Swift 1.2 I just use if let with multiple optionals:

static func fromJSON(json: JSON) -> Piece? {
    if let id = json["id"].int,
           player = Player.fromJSON(json["player"]),
           isCaptured = json["isCaptured"].bool,
           isKing = json["isKing"].bool {
        return Piece(id: id,
                     player: player,
                     isCaptured: isCaptured,
                     isKing: isKing)
    } else {
        return nil
    }
}

Advantages

I like this approach because it doesn’t involve any new operators while still being readable — there’s not enough noise to justify removing it at the cost of introducing new operators.

What I don’t like is the fact that I had to type id, player, isCaptured, and isKing three times there, but if I were to implement a new initializer, I would have to type them this many times anyway.

Limitations

Because fromJSON returns Self?, it can be implemented “only” by structs and final classes. Using JSONConvertible with regular classes results in 'MyClass' is not convertible to 'Self?' error as the compiler can’t be sure that subclasses override fromJSON (your class can be in a framework and then its subclasses are not known at compile time).

For more discussion of this limitation, see Rob Napier’s answer to a question about returning Self.

Update · 2015-03-02

ElvishJerricco pointed out on Reddit that the final class limitation mentioned above can be worked around by using a required initializer that accepts JSON and returning it from fromJSON. Unfortunately, such initializer can’t be defined in an extension, which destroys the elegance of the solution for me:

class MyClass {
    /* ... */
    required init?(json: JSON) { /* ... */ }
}


extension MyClass: JSONConvertible {
    func toJSON() -> JSON { /* ... */ }

    static func fromJSON(json: JSON) -> Self? {
        return self(json: json)
    }
}

I don’t have a good answer here, but if one has to use non-final classes and add code to main class definitions, maybe a better solution is to get rid of fromJSON altogether?

protocol JSONConvertible {
    init?(json: JSON)
    func toJSON() -> JSON
}

You can discuss this post on Reddit or message me on Twitter.