My app must fetch JSON via HTTPS request, and then parse that data.
What I'm looking for is to have a "one function rules all" approach to it, like so.
func call(endpoint: String) {
let url = URL(string: "https://example.com/api/"+endpoint)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
// Parse JSON here without using a model.
// Just convert the data into a JSON object and return it
}
}
Every single example code I've come across seems to me that you need to make a model with the expected values, and then do this:
JSONDecoder().decode(ModelHere.self, from: data)
This means that I need to make a new function for each endpoint.
Why not just use a separate function for each endpoint?
I have a lot of endpoints that could be called from within a function.
Creating a new function for each endpoint to pass it a separate model to then parse the data from the response ... that is a lot of junk to sift through in the code.
I'm sure there is a better way to do it that is just obscure, or I'm missing.
Essentially, I'm looking for this:
a function needs remote data to load a view -> fires call('endpoint/example/') -> call() returns the json object -> the function can now take the data it needs
Multiple processor functions, one call() function. I hope I'm making sense.
Thanks
That's what generic
is for, for instance:
You can define T
as a your date model which is a generic type, and you fetch it and return it with completion. Making T
as codable so any model that conforms codable
will make this call.
func call<T: Codable>(endpoint: String, completion: (T) -> ()) {
let url = URL(string: "https://example.com/api/"+endpoint)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
// Parse JSON here without using a model.
// Just convert the data into a JSON object and return it
let model = JSONDecoder().decode(T.self, from: data)
completion(model)
}
}
And when you call the function:
call(endpoint: "something") { model: ModelHere in
// Use your model
}
So you don't have to make separate functions for every model you have.
** Code not been tested, just as an idea
EDIT: Here is an example with feedback from comment (Thank you @Leo)
private var host = "https://jsonplaceholder.typicode.com/"
enum NetworkError: Error {
case invalidUrl
case invalidData
case underlying(_ error: Error)
}
func call<T: Decodable>(endpoint: String,
completion: @escaping (Result<T, NetworkError>) -> ()) -> Bool
{
guard let url = URL(string: host+endpoint) else {
completion(.failure(.invalidUrl))
return false
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
return completion(.failure(.invalidData))
}
do {
let model = try JSONDecoder().decode(T.self, from: data)
completion(.success(model))
} catch let error {
print(error.localizedDescription)
completion(.failure(.underlying(error)))
}
}.resume()
return true
}
And when you call the functions:
struct SampleModel: Decodable {
let id: Int
let content: String
}
func sample() {
let isSuccess = call(endpoint: "fetchSampleModelUrl") { (result: Result<[SampleModel], NetworkError>) in
switch result {
case .success(let model):
print("\(model)")
case .failure(let error):
print("\(error)")
}
}
}