编写JSON序列化函数

时间:2018-10-23 04:59:46

标签: json swift

我想创建一个函数,该函数需要几个参数,然后从Web 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的url链接,为查找所需数据而设置的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()
}

我不确定structToDecodearrayThatHoldsResponse的数据类型(在上面的示例函数中,我只是使用String作为占位符),假设它们看起来像

结构

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 个答案:

答案 0 :(得分:2)

泛型的力量。您可以创建一个泛型函数,其中参数为urlStringT继承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()
    }
}

答案 1 :(得分:2)

根据Jacob的回答,我建议也返回一个可能的错误。

要保持通用布局,请将一个(也是通用的)枚举声明为返回类型

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