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)
        }
    }
}

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

4 Comments

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
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
@PramodShukla Then use an enum with associated values as root object as described here
@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
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
    } 

   }
]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.