Search code examples
jsonswiftgenericsjson-deserializationjsondecoder

Swift Generic Json Parser


I have this generic JSON Parser that works decoding Arrays

class JSONParserFromStruct {
    typealias result<T> = (Result<[T], Error>) -> Void

    func downloadList<T: Decodable>(of _: T.Type,
                                    from data: Data,
                                    completion: @escaping result<T>) {
        do {
            let decodedData: [T] = try! JSONDecoder().decode([T].self, from: data)
            completion(.success(decodedData))
        } catch {
            completion(.failure(DataError.decodingError))
        }
    }
}

This Users.json File in Bundle

{
    "name": "Taylor Swift"
}

Calling it like this:

func performRequest() {
    let url = Bundle.main.url(forResource: "Users", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    genericParser.downloadList(of: User.self, from: data) { result in
        switch result {
        case let .failure(error):
            if error is DataError {
                print("eroarea este: \(error.localizedDescription)")

                print(url)

            } else {
                print("err is \(error.localizedDescription)")
            }
            print(error.localizedDescription)

        case let .success(weather):
            print(weather)
        }
    }
}

Works like a charm

However, when trying to use a different json file called Weather.json, it fails with error

"debugDescription: "Expected to decode Array but found a dictionary instead"

This is the json i get the error for

{
    "main": {
        "temp": 281.52,
        "feels_like": 278.99,
        "temp_min": 280.15,
        "temp_max": 283.71,
        "pressure": 1016,
        "humidity": 93
    }
}

Data Model

struct Weather: Codable {
    let main: Main
}

struct Main: Codable {
    let temp: Double
}

...using the same JSONParserFromStruct class, however it fails.

This is how it's called

 func performRequest() {
    let url = Bundle.main.url(forResource: "Weather", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    genericParser.downloadList(of: Weather.self, from: data) { result in
        switch result {
        case let .failure(error):
            if error is DataError {
                print("eroarea este: \(error.localizedDescription)")

                print(url)

            } else {
                print("err is \(error.localizedDescription)")
            }
            print(error.localizedDescription)

        case let .success(weather):
            print(weather)
        }
    }
}

Solution

  • Your parser is not generic enough because it can only decode arrays.

    The generic type T can be anything, a single object as well as an array, so just use T

    class JSONParserFromStruct {
        typealias ResultBlock<T> = (Result <T, Error>) -> Void
    
        func downloadList<T: Decodable>(of type: T.Type,
                                          from data: Data,
                                          completion: @escaping ResultBlock<T>) {
    
            do {
                let decodedData: T = try JSONDecoder().decode(T.self, from: data)
                completion(.success(decodedData))
            }
            catch {
                completion(.failure(DataError.decodingError))
            }
        }
    }
    

    To decode the User array specify the array as parameter type

    genericParser.downloadList(of: [User].self, from: data)
    

    Now

    genericParser.downloadList(of: Weather.self, from: data)
    

    is supposed to work