Запись функции сериализации JSON

Я хотел бы сделать функцию, которая принимает несколько параметров, а затем выводит нужные мне данные из веб-API. Очевидно, что в течение долгого времени мне нужно настроить его в соответствии с прецедентом, но просто для удовольствия. Я пытаюсь найти супер базовую функцию, которая успешно анализирует JSON, так как около половины строк кода в функции Ниже приведена общая обработка ошибок.

Например, если я вообще использую что-то вроде

func getJSON(completionHandler: @escaping (Bool) -> ()) {
    let jsonUrlString = "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808"
    guard let url = URL(string: jsonUrlString) else {return}

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        guard let data = data, err == nil else {
            print(err!)
            return
        }

        do {
            let response = try
                JSONDecoder().decode(TopStoriesResponse.self, from: data)

            self.storyData = response.results

            completionHandler(true)

        } catch let jsonErr {
            print("Error serializing JSON", jsonErr)
        }
    }.resume()
}

Единственные три вещи, которые будут меняться от случая к случаю (опять же, в самом абсолютно базовом сценарии), - это ссылка на API-интерфейс, Struct, которую я создал, чтобы искать нужные мне части данных, и массив, который Я выводю результаты, как только запрос данных будет завершен.

Могу ли я обрезать жир на этом и сделать что-то вроде

func jsonFetcher(apiLink: String, structToDecode: String, arrayThatHoldsResponse: [String], completionHandler: @escaping (Bool) -> ()) {
    let jsonUrlString = apiLink
    guard let url = URL(string: jsonUrlString) else {return}

    URLSession.shared.dataTask(with: url) { (data, response, err) in
        guard let data = data, err == nil else {
            print(err!)
            return
        }

        do {
            let response = try
                JSONDecoder().decode(structToDecode, from: data)

            arrayThatHoldsResponse = response.results

            completionHandler(true)

        } catch let jsonErr {
            print("Error serializing JSON", jsonErr)
        }
    }.resume()
}

Я просто не уверен в типах данных structToDecode и arrayThatHoldsResponse (в приведенной выше функции примера я просто использую String в качестве заполнителя), предполагая, что они выглядят как

Struct (ы)

struct TopStoriesResponse: Decodable {
    let status: String
    let results: [Story]
}

struct Story: Decodable {
    let title: String
    let abstract: String
    let url: String
    let multimedia: [Multimedia]

    private enum CodingKeys: String, CodingKey {
        case title
        case abstract
        case url
        case multimedia
    }

    init(from decoder:Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try container.decode(String.self, forKey: .title)
        abstract = try container.decode(String.self, forKey: .abstract)
        url = try container.decode(String.self, forKey: .url)
        multimedia = (try? container.decode([Multimedia].self, forKey: .multimedia)) ?? []
    }

}

массив

var storyData = [Story]()

Таким образом, я могу просто позвонить

jsonFetcher(apiLink: link, structToDecode: myStruct, arrayThatHoldsResponse: myArray, completionHandler: <#T##(Bool) -> ()#>)

Спасибо за любую помощь!

Всего 2 ответа


Сила дженериков. Вы можете создать общую функцию, где параметр - urlString . Наследование T - протокол Decodable .

Таким образом, вы можете вызывать эту функцию каждый раз, пока ваше наследование модели Decodable протоколом Decodable .

func fetchData<T: Decodable>(urlString: String, completion: @escaping (T) -> ()) {
    let url = URL(string: urlString)!

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
            print(error.localizedDescription)
        }

        guard let data = data else { return }

        do {
            let object = try JSONDecoder().decode(T.self, from: data)
            completion(object)
        } catch let jsonErr {
            print("Failed to decode json:", jsonErr)
        }
    }.resume()
}

Как вызвать функцию:

struct User: Decodable { }

fetchData(urlString: "yourUrl") { (User: User) in
    // Handle result
}


struct Animal: Decodable { }

fetchData(urlString: "yourUrl") { (animal: Animal) in
    // Handle result
}


// Or if you want to fetch an array of users instead
fetchData(urlString: "yourUrl") { (users: [User]) in
    // Handle result
}

В твоем случае

var storiesData: [Story] = []

fetchData(urlString: "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=f4bf2ee721031a344b84b0449cfdb589:1:73741808") { (stories: [Story] in
    storiesData = stories
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

Основываясь на ответе Джейкоба , я рекомендую также вернуть возможную ошибку.

Чтобы универсальный макет объявлял a - также generic - enum как возвращаемый тип

enum FetchResult<T> {
    case success(T), failure(Error)
}

и возвращает FetchResult с переданным статическим типом

func fetchData<T: Decodable>(url: URL, completion: @escaping (FetchResult<T>) -> Void) {

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        guard let data = data else {completion(.failure(error!)); return } 
        do {
            let object = try JSONDecoder().decode(T.self, from: data)
            completion(.success(object))
        } catch {
            completion(.failure(error))
        }
    }.resume()
}

и использовать его

let jsonUrl = URL(string: "https://api.nytimes.com/svc/topstories/v1/business.json?api-key=••••••••••••••••••:1:73741808")!

fetchData(url: jsonUrl) { (result : FetchResult<TopStoriesResponse>) in
    switch result {
    case .success(let object): print(object) // do something with object
    case .failure(let error): print(error) // handle the error
    }
}

Есть идеи?

10000