GitHubのEvents APIでは、イベントの種類に応じて一部のJSONの構造が変わります。構造が可変となっているのはpayloadと呼ばれる箇所で、種類に固有の情報はpayload以下に格納されます。例えば、CommitCommentEventpayloadcomment, senderといった情報を持ち、CreateEventDeleteEventref, ref_typeといった情報を持ちます。

https://developer.github.com/v3/activity/events/types/

SwiftでEvents APIのレスポンスを表そうと考えた場合、以下の2つが思い浮びました。

  • Eventという1つの型を定義してpayload[String: AnyObject]にする。
  • EventTypeというプロトコルを定義して種類ごとに型を定義する。

どちらも微妙な気がしたので、もう少し考えて以下のような方法を試してみました。

struct Event {
    let id: Int64
    let type: Type

    init(dictionary: [String: AnyObject]) throws {
        ...
    }

    enum Type {
        case CommitCommentEvent(CommitCommentPayload)
        case CreateEvent(ReferencePayload)
        case DeleteEvent(ReferencePayload)
        ...

        init(type: String, payload: [String: AnyObject]) throws {
            switch type {
            case "CommitCommentEvent":
                self = .CommitCommentEvent(try CommitCommentPayload(dictionary: payload))

            case "CreateEvent":
                self = .CreateEvent(try ReferencePayload(dictionary: payload))

            case "DeleteEvent":
                self = .DeleteEvent(try ReferencePayload(dictionary: payload))

            ...
            }
        }

        struct CommitCommentPayload {
            let comment: Comment
            let sender: User

            init(dictionary: [String: AnyObject]) throws {
                ...
            }
        }

        struct ReferencePayload {
            let referenceName: String
            let referenceType: ReferenceType

            init(dictionary: [String: AnyObject]) throws {
                ...
            }
        }
    }
}

このように定義すると、Eventtypeに応じて可変なpayloadがassociated valueとして取得できるようになり、Eventの利用側では危険な操作を伴わずに種類に応じたデータを取得できるようになります。

let event: Event = ...

switch event.type {
case .CommitCommentEvent(let payload):
    print(payload.comment)

case .ReferencePayload(let payload):
    print(payload.referenceName)
}