Episode #388

Parsing JSON into Models

Series: Making a Podcast App From Scratch

15 minutes
Published on April 25, 2019

This video is only available to subscribers. Get access to this video and 585 others.

In this episode we take the response from the top podcasts feed and decode the JSON into models using Codable.

Episode Links

Modeling the API Response

We’ll start by defining the response JSON structure as Decodable structs.

extension TopPodcastsAPI {
    struct Response: Decodable {
        let feed: Feed
    }

    struct Feed: Decodable {
        let results: [PodcastResult]
    }
}

Note that Response is Decodable. This will allow us to decode the JSON that comes back from the server. Every type used in a Decodable must implement Decodable.

Create struct PodcastResult with properties matching to JSON names obtained from the feed.

struct PodcastResult : Decodable {
    let id: String
    let artistName: String
    let name: String
    let artworkUrl100: String
    let genres: [Genre]
} 
struct Genre: Decodable {
    let name: String
    let genreId: String
}

Extracting a function to parse the response

Here we create a generic function that takes any Decodable type T and handles the parsing logic for us. Note that we are returning a closure this method (a function returning a function). Doing this allows us to compose the result of this function with the perform() function.

private func parseDecodable<T : Decodable>(
        completion: @escaping (Result<T, APIError>) -> Void
    ) -> (Result<Data, APIError>)

Change the completionHandler to handle success and failure. Note the changes in completionHandler, it takes Result<Data, APIError>and processes callback on the main queue.

switch result {
    case .success(let data):
        do {
        let jsonDecoder = JSONDecoder()
        let object = try jsonDecoder.decode(T.self, from: data)
        DispatchQueue.main.async {
            completion(.success(object))
        }
    }   
        catch let decodingError as DecodingError {
         DispatchQueue.main.async {
        completion(.failure(.decodingError(decodingError)))
         }
    }
        catch {
            fatalError("Unhandled error raised.")
    }
    case .failure(let error):
        DispatchQueue.main.async {
            completion(.failure(error))
        }   
}

In case of success, use JSONDecoder to decode the data. In case of failure, catch DecodingError and any unhandled error.

Fetching Parsed JSON Response

To fetch the response, switch Result<Data, APIError> to Result<Response, APIError>. We call parseDecodable through the completionBlock of func perform, this reduces nesting of closures.

func fetchTopPodcasts(limit: Int = 50, allowExplicit: Bool = false, completion: @escaping (Result<Response, APIError>) -> Void ) {
    //... fetching parsed response
    perform(request: request, completion: parseDecodable(completion: completion)) {
        //...
    }
}

This episode uses Xcode 10.2, Swift 5.0.