3

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)
        }
    }
}
Zonily Jame
  • 5,053
  • 3
  • 30
  • 56
Dan
  • 151
  • 3
  • 12

2 Answers2

6

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

Zonily Jame
  • 5,053
  • 3
  • 30
  • 56
vadian
  • 274,689
  • 30
  • 353
  • 361
  • worked like a charm, thanks a lot! Spent 1 full day trying to understand if the issue comes from how the data structure is defined or if it's something with the decoding class, the the initial generic is loading data from an url, so really could not tell if it;s the generic or the datatype, but now it makes sense. Also, for decoding the User it worked by using User.self and not [User].self, still didn;t figured it out why – Dan Feb 03 '20 at 21:15
  • what should id do if in case of suucess getting array and in failure dictionary from same api, how can i handle with this genric T – Pramod Shukla Jan 12 '22 at 17:13
  • @PramodShukla Then use an enum with associated values as root object as described [here](https://stackoverflow.com/questions/66158295/trouble-implementing-swift-resultsuccess-error-in-api-request/66158661#66158661) – vadian Jan 12 '22 at 17:39
  • @vadian how i can use enum can you give me any ex- in case of root object array instead of dictionary code goes to failure block – Pramod Shukla Jan 13 '22 at 05:55
2

First case works as it's an array like

[
{
"name": "Taylor Swift"
}
]

second case doesn't as it's {} , so you can easily fix it by making a single T instead of [T] or wrap the json like

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

   }
]

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87