Typesafe API calls in Swift: Generating Alamofire Handlers with quicktype

how-to

Alamofire is an elegant HTTP networking library that makes working with JSON APIs in Swift a breeze. quicktype makes Alamofire even more awesome by generating extensions and Codables for typesafe API calls.

Here's Alamofire's canonical example of handling a JSON API response:

Alamofire.request("https://httpbin.org/get").responseJSON { response in
    if let json = response.result.value {
        // json is of type Any, so we must cast and use dictionary indexers
        let data = json as! [String: Any]
        let headers = data["headers"] as! [String: String]
        print(headers["Accept-Language"])
    }
}

Unfortunately, as you can see, you're still left with json: Any, a dynamic value that you must cast and awkwardly introspect for the data you care about.

quicktype can generate statically-typed Alamofire response handlers along with Codable structs from JSON, allowing us to rewrite the above sample like this:

Alamofire.request("https://httpbin.org/get").responseHTTPBinGet { response in
    if let json = response.result.value {
        print(json.headers.acceptLanguage)
    }
}

How it's done

You can use the quicktype CLI or app.quicktype.io to generate Alamofire extensions and Codable structs directly from the API endpoint:

$ quicktype https://httpbin.org/get --alamofire -o HTTPBinGet.swift

quicktype generates the following Codable type for this API:

struct HTTPBinGet: Codable {
    let args: Args
    let headers: Headers
    let origin, url: String
}

struct Args: Codable {
}

struct Headers: Codable {
    let accept, acceptEncoding, acceptLanguage, connection: String
    let host, upgradeInsecureRequests, userAgent: String

    enum CodingKeys: String, CodingKey {
        case accept = "Accept"
        case acceptEncoding = "Accept-Encoding"
        case acceptLanguage = "Accept-Language"
        case connection = "Connection"
        case host = "Host"
        case upgradeInsecureRequests = "Upgrade-Insecure-Requests"
        case userAgent = "User-Agent"
    }
}

And Alamofire extensions to handle the API response:

extension DataRequest {
    // ...

    @discardableResult
    func responseHTTPBinGet(
        queue: DispatchQueue? = nil,
        completionHandler: @escaping (DataResponse<HTTPBinGet>) -> Void)
        -> Self {
            return responseDecodable(queue: queue, completionHandler: completionHandler)
    }
}

The code generated by quicktype allows you to do a statically-typed Alamofire request, getting the benefit of type-safety and code completion on your response data!

Alamofire.request("https://httpbin.org/get").responseHTTPBinGet { response in
    if let json = response.result.value {
*         // json is an instance of HTTPBinGet
        print(json.headers.acceptLanguage)
    }
}

Try this feature in your browser.

David

David