在 Swift 中序列化 JSON 4 - 确定数据类型时出现问题
Serializing JSON in Swift 4 - problem figuring out data type
我正在从网上 API 抓取一些 JSON 并将结果放入数组以备将来使用。到目前为止所有数据都很好(只是字符串数组),但我不知道如何处理其中一个结果。
This is the JSON (someone advised that I use https://jsonlint.com 使其可读且非常有用)
这是获取 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)
// Pass results into arrays (title, abstract, url, image)
for result in response.results {
let headlines = result.title
let abstracts = result.abstract
let url = result.url
self.headlines.append(headlines)
self.abstracts.append(abstracts)
self.urls.append(url)
}
let imageResponse = try
JSONDecoder().decode(Story.self, from: data)
for imageResults in imageResponse.multimedia {
let images = imageResults.url
self.images.append(images)
}
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
这些是用于序列化 JSON:
的结构
struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]
}
struct Multimedia: Codable {
let url: String
let type: String
}
我将结果组织到这些数组中:
var headlines = [String]()
var abstracts = [String]()
var urls = [String]()
var images = [String]()
我在viewDidLoad
中调用函数
getJSON { (true) in
print("Success")
print("\n\nHeadlines: \(self.headlines)\n\nAbstracts: \(self.abstracts)\n\nURLS: \(self.urls)\n\nImages: \(self.images)")
}
正如您在 getJSON
函数中看到的那样,我尝试使用
获取图像
let imageResponse = try JSONDecoder().decode(Story.self, from: data)
for imageResults in imageResponse.multimedia {
let images = imageResults.url
self.images.append(images)
}
但是我收到错误
CodingKeys(stringValue: "multimedia", intValue: nil)], debugDescription: "Expected to decode Array but found a string/data instead.", underlyingError: nil))
我很困惑,因为它说它期待一个数组,但却找到了一个字符串 - 图像不是数组吗,就像 headlines
、abstracts
等?
您已将 multimedia
定义为数组。 json 中有些部分没有任何多媒体,设置为空字符串:
multimedia: ""
您需要能够处理任何一种情况。由于 Codable
旨在处理具体类型,因此您最好改用 JSONSerialization
。
如果你非常喜欢使用Codable
,你可以操作字符串形式的JSON响应,将multimedia: ""
转换成你期望的格式,然后传递给解码器。例如,您可以将多媒体设为可选,只需删除带有 multimedia: ""
.
的任何行
问题是 multimedia
是 Multimedia
个对象的数组或空的 String
。您需要为 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)) ?? []
}
}
问题在于来自服务器的 JSON 响应,Multimedia
数组在 JSON 响应之一中显示为空 "" String
。要处理这种情况,您需要手动实现 init(from decoder:)
方法并处理空字符串的情况。此外,您不需要创建单独的数组来存储值,您可以直接在完成处理程序闭包中传递 TopStoriesResponse
结构,并在需要时在 ViewController 中获取值。
假设您创建了一个包含成功 (T) 和失败 (错误) 的结果枚举,并将其传递到您的 completionHandler 中供 ViewController 处理
enum Result<T> {
case success(T)
case failure(Error)
}
struct Networking {
static func getJson(completionHandler: @escaping (Result<TopStoriesResponse>) -> ()) {
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, error) in
guard let data = data, error == nil else {
completionHandler(Result.failure(error!))
return
}
do {
let topStoriesResponse: TopStoriesResponse = try JSONDecoder().decode(TopStoriesResponse.self, from: data)
print(topStoriesResponse.results.count)
completionHandler(Result.success(topStoriesResponse))
} catch {
completionHandler(Result.failure(error))
}
}.resume()
}
}
现在在您的 ViewController 中您可以调用 getJson 方法并切换 completionHandler 中的结果枚举以获取值
class ViewController: UIViewController {
var topStories: TopStoriesResponse?
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
Networking.getJson { (result: Result<TopStoriesResponse>) in
switch result {
case let .success(topStories):
self.topStories = topStories
topStories.results.forEach({ (story: Story) in
print("Title: \(story.title) \n Abstracts = \(story.abstract) URL = \(story.url)")
})
//reload tableView
case let .failure(error):
print(error.localizedDescription)
}
}
}
}
要处理空字符串的情况,您需要在多媒体结构中实现 init(decoder:) 方法,如上所述
struct Multimedia: Decodable {
let url: String
let image: String
let height: Float
let width: Float
}
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)) ?? []
}
}
struct TopStoriesResponse: Decodable {
let status: String
let copyright: String
let num_results: Int
let results: [Story]
}
我正在从网上 API 抓取一些 JSON 并将结果放入数组以备将来使用。到目前为止所有数据都很好(只是字符串数组),但我不知道如何处理其中一个结果。
This is the JSON (someone advised that I use https://jsonlint.com 使其可读且非常有用)
这是获取 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)
// Pass results into arrays (title, abstract, url, image)
for result in response.results {
let headlines = result.title
let abstracts = result.abstract
let url = result.url
self.headlines.append(headlines)
self.abstracts.append(abstracts)
self.urls.append(url)
}
let imageResponse = try
JSONDecoder().decode(Story.self, from: data)
for imageResults in imageResponse.multimedia {
let images = imageResults.url
self.images.append(images)
}
completionHandler(true)
} catch let jsonErr {
print("Error serializing JSON", jsonErr)
}
}.resume()
}
这些是用于序列化 JSON:
的结构struct TopStoriesResponse: Decodable {
let status: String
let results: [Story]
}
struct Story: Decodable {
let title: String
let abstract: String
let url: String
let multimedia: [Multimedia]
}
struct Multimedia: Codable {
let url: String
let type: String
}
我将结果组织到这些数组中:
var headlines = [String]()
var abstracts = [String]()
var urls = [String]()
var images = [String]()
我在viewDidLoad
getJSON { (true) in
print("Success")
print("\n\nHeadlines: \(self.headlines)\n\nAbstracts: \(self.abstracts)\n\nURLS: \(self.urls)\n\nImages: \(self.images)")
}
正如您在 getJSON
函数中看到的那样,我尝试使用
let imageResponse = try JSONDecoder().decode(Story.self, from: data)
for imageResults in imageResponse.multimedia {
let images = imageResults.url
self.images.append(images)
}
但是我收到错误
CodingKeys(stringValue: "multimedia", intValue: nil)], debugDescription: "Expected to decode Array but found a string/data instead.", underlyingError: nil))
我很困惑,因为它说它期待一个数组,但却找到了一个字符串 - 图像不是数组吗,就像 headlines
、abstracts
等?
您已将 multimedia
定义为数组。 json 中有些部分没有任何多媒体,设置为空字符串:
multimedia: ""
您需要能够处理任何一种情况。由于 Codable
旨在处理具体类型,因此您最好改用 JSONSerialization
。
如果你非常喜欢使用Codable
,你可以操作字符串形式的JSON响应,将multimedia: ""
转换成你期望的格式,然后传递给解码器。例如,您可以将多媒体设为可选,只需删除带有 multimedia: ""
.
问题是 multimedia
是 Multimedia
个对象的数组或空的 String
。您需要为 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)) ?? []
}
}
问题在于来自服务器的 JSON 响应,Multimedia
数组在 JSON 响应之一中显示为空 "" String
。要处理这种情况,您需要手动实现 init(from decoder:)
方法并处理空字符串的情况。此外,您不需要创建单独的数组来存储值,您可以直接在完成处理程序闭包中传递 TopStoriesResponse
结构,并在需要时在 ViewController 中获取值。
假设您创建了一个包含成功 (T) 和失败 (错误) 的结果枚举,并将其传递到您的 completionHandler 中供 ViewController 处理
enum Result<T> {
case success(T)
case failure(Error)
}
struct Networking {
static func getJson(completionHandler: @escaping (Result<TopStoriesResponse>) -> ()) {
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, error) in
guard let data = data, error == nil else {
completionHandler(Result.failure(error!))
return
}
do {
let topStoriesResponse: TopStoriesResponse = try JSONDecoder().decode(TopStoriesResponse.self, from: data)
print(topStoriesResponse.results.count)
completionHandler(Result.success(topStoriesResponse))
} catch {
completionHandler(Result.failure(error))
}
}.resume()
}
}
现在在您的 ViewController 中您可以调用 getJson 方法并切换 completionHandler 中的结果枚举以获取值
class ViewController: UIViewController {
var topStories: TopStoriesResponse?
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
func loadData() {
Networking.getJson { (result: Result<TopStoriesResponse>) in
switch result {
case let .success(topStories):
self.topStories = topStories
topStories.results.forEach({ (story: Story) in
print("Title: \(story.title) \n Abstracts = \(story.abstract) URL = \(story.url)")
})
//reload tableView
case let .failure(error):
print(error.localizedDescription)
}
}
}
}
要处理空字符串的情况,您需要在多媒体结构中实现 init(decoder:) 方法,如上所述
struct Multimedia: Decodable {
let url: String
let image: String
let height: Float
let width: Float
}
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)) ?? []
}
}
struct TopStoriesResponse: Decodable {
let status: String
let copyright: String
let num_results: Int
let results: [Story]
}