在Swift 4中序列化JSON-确定数据类型的问题

时间:2018-10-18 14:38:56

标签: json swift codable

我正在从在线API中获取一些JSON,并将结果放入数组中以备将来使用。到目前为止,所有数据都很好(只是字符串数组),但我不知道如何处理其中一个结果。

This is the JSON(有人建议我使用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:“预期对数组进行解码,但找到了一个字符串/数据。”,底层错误:nil))

我很困惑,因为它是在期待一个数组,但却找到了一个字符串-图像不是数组,就像headlinesabstracts等吗?

3 个答案:

答案 0 :(得分:1)

问题在于multimediaMultimedia对象的数组还是空的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)) ?? []
    }
}

答案 1 :(得分:0)

您已将multimedia定义为数组。 json中有没有任何多媒体的部分,这些部分设置为空字符串:

multimedia: ""

您需要能够处理两种情况。由于Codable旨在处理具体类型,因此最好改用JSONSerialization

如果您强烈希望使用Codable,则可以以字符串形式处理JSON响应,以将multimedia: ""转换为所需的格式,然后将其传递给解码器。例如,您可以将多媒体设置为可选,并只需使用multimedia: ""删除所有行。

答案 2 :(得分:0)

问题在于来自服务器的JSON响应,Multimedia数组在其中一个JSON响应中为空"" String。要处理这种情况,您需要手动实现init(from decoder:)方法并处理空的String情况。另外,您不需要创建单独的数组来存储值,可以直接在完成处理程序闭包中传递TopStoriesResponse结构,并在需要时在ViewController中获取值。

假设您创建了一个具有成功(T)和失败(Error)的Result枚举,并将其传递给您的completedHandler以供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)
            }
        }
    }
}

要处理空的String情况,您需要如上所述在Multimedia结构中实现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]
}