解码的 JSON 字段只给出 nil 值,不能存储到变量中

Decoded JSON fields give only nil values, cannot be stored into variables

这是我需要解码的 JSON,下面的 link 中提供了它的图片以及文本(格式化不起作用,所以它看起来很难看)。我很确定我用我的结构正确地表示了它。

{ "success":{ "total": 1 }, "contents":{ "quotes": [ { "quote": "The last time doesn't exist. It's only this time. And everything is going to be different this time. There's only now.", "author": "Bill Murray", "length": "118", "tags": [ "inspire", "present" ], "category": "inspire", "title": "Inspiring Quote of the day", "date": "2019-01-16", "id": 空 } ], "copyright":“2017-19 theysaidso.com” } }

每当我 运行 我的代码试图从 JSON 中获取字段并将它们存储到变量中以便在 UITableView 中显示它们时,它都会失败。我通过尝试使标签显示为作者姓名作为标题来对其进行测试。 "author" 是 JSON 中的一个字段。 这些是代码的重要部分:

Class ViewController: UITableViewController  {

...
var quoteArray = [Quote]()
//quoteArray Stores the quote objects that contain the fields I need
.....
//STRUCTS TO REPRESENT THE JSON
struct Quote: Decodable {
    let quote: String?
    let author: String?
    let length: String?
    let tags: [String]?
    let category: String?
    let title: String?
    let date: String?
}

struct WebsiteObjectStruct: Decodable {
    let success: SuccessStruct
    let contents: ContentsStruct
}

struct SuccessStruct: Decodable{
    let total: Int?
}

struct ContentsStruct: Decodable{
    let quotes: [Quote]?
    let copyright:  String?
}

.....

//解码发生的函数

fileprivate func fetchJSON(){
        ...
        self.websiteObject = try decoder.decode(WebsiteObjectStruct.self, from: data)
        self.tableView.reloadData()
        ...
}
...

//表格视图函数中行的单元格

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cellId")
    let authorText = quoteArray[0].author
    cell.textLabel?.text = author.text
    //For quoteArray we are looking at zero index because in my JSON there
    // is ONLY EVER ONE element, located at index 0, in quoteArray
    return cell
}

}

应用程序 运行s 和 tableView 是空的,它没有作者的名字(在本例中是 bill murray)。无论如何,这是错误消息:

Failed to decode: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))

它说它希望解码一个数组,但却找到了一个字典。好吧,我有一次改变它来解码一个结构而不是一个数组,并声明了一个变量 在属于结构类型的 class 中(结构的目的反映了数组的目的)。

简而言之,我稍微更改了代码以适应该结构,并且只有当打印语句与解码语句位于相同的编码括号内时,它才能将作者的姓名打印到控制台。尽管如此,它仍无法将其存储到变量中以供使用。

我不认为问题是 Array vs Dictionary 但是控制台谈到 "underlyingError",Array 是 nil。无论变量是什么类型,无论是 Array 还是 Struct,放入 textField 的变量始终为 nil。

我收到这个错误:

Fatal Error: unexpected found nil while unwrapping optional value

可能是线程或异步问题?

更新:此代码有效:

class MainNetworkManager{ //从wep请求JSON格式API

static func fetchJSON(fetchUrl: String, quoteViewController: QuoteViewController) {
    let urlString = fetchUrl
    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { (data, _, err) in
        DispatchQueue.main.async {
            if let err = err {
                print("Failed to get data from url:", err)
                return
            }

            guard let data = data else { return }

            do {
                // link in description for video on JSONDecoder
                let decoder = JSONDecoder()
                // Swift 4.1
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                //self.webStruct = try decoder.decode(WebsiteObjectStruct.self, from: data)
                //                        self?.quoteArray = quotesArray
                //                        self?.reloadInputViews()
                let tempStruct = try decoder.decode(WebsiteObjectStruct.self, from: data)

                //print(tempStruct.contents.quotes[0].length)
                quoteViewController.webStruct = tempStruct

                //quoteViewController.setupLabels(array: (tempStruct.contents.quotes))
                quoteViewController.setupLabels(obj: tempStruct)


            } catch let jsonErr {
                print("Failed to decode:", jsonErr)
            }
        }
        }.resume()
}

要找出您发布的代码中的错误有点困难。我认为问题可能出在变量 websiteObject 的类型定义上。我创建了一个小操场来测试它,你的结构很好。

我使用您的结构创建了一个可以正常工作的小项目。你可以在这里查看:https://github.com/acyrman/Whosebug54211226.

相关更改在 fetchJSON 函数中。我没有使用 self.websiteObject,我不知道你是如何定义它的,而是使用了这样的局部变量:let websiteObject = try decoder.decode(WebsiteObjectStruct.self, from: data),然后继续获取引号并分配给你的 quoteArray变量。

fileprivate func fetchJSON() {
    let urlString = "http://quotes.rest/qod.json?category=inspire"
    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
        if error != nil {
            self?.displayAlert("Error fetching data: \(String(describing: error?.localizedDescription))")
        }

        let decoder = JSONDecoder()
        do {
            guard let data = data else { throw NSError(domain: "this.app", code: -1, userInfo: nil) }

            let websiteObject = try decoder.decode(WebsiteObjectStruct.self, from: data)
            if let quotesArray = websiteObject.contents.quotes {
                DispatchQueue.main.async {
                    self?.quoteArray = quotesArray
                    self?.tableView.reloadData()
                }
            }
        } catch let error {
            self?.displayAlert("Error decoding json data: \(String(describing: error.localizedDescription))")
        }
    }.resume()
}

对于应用程序,我正在从中获取引述:http://quotes.rest/qod.json?category=inspire。同样在 info.plist 中启用 ATS 设置以启用从非 https url.

获取数据也很重要

代码只是为了测试你的结构,不要指望一个干净的代码项目;)

应用程序在 viewDidLoad 中调用 fetchJSON,使用 subtitle 单元格样式 UI 如下所示:

两个问题。

  1. 要使用 quoteArray,您必须复制包含引号的数组

    self.websiteObject = try decoder.decode(WebsiteObjectStruct.self, from: data)
    self.quoteArray = self.websiteObject.contents.quotes ?? []
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
    
  2. cellForRow 中,您必须通过给定的索引路径获取项目。然后dequeue单元格,在Interface Builder中设置样式。

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableview.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
        let quote = quoteArray[indexPath.row]
        cell.textLabel?.text = quote.author
        cell.detailTextLabel?.text = quote.quote
        return cell
    }