解码的JSON字段仅提供nil值,无法存储到变量中

时间:2019-01-16 06:02:09

标签: json swift multithreading fatal-error reloaddata

这是我需要解码的JSON,下面的链接中提供了它的图片以及文本(格式无法正常工作,因此看起来很难看)。我很确定我用自己的结构正确表示了它。

enter image description here

{     “成功”:{         “总计”:1     },     “内容”:{         “引号”:[         {         “ quote”:“上一次不存在。只有这一次。这次一切都将有所不同。只有现在。”,         “ author”:“ Bill Murray”,         “ length”:“ 118”,         “标签”:[         “启发”,         “当下”         ],         “类别”:“启发”,         “ title”:“每日励志名言”,         “ date”:“ 2019-01-16”,         “ id”:null         }         ],         “版权”:“ 2017-19 themsaidso.com”     } }

每当我运行我的代码(尝试从JSON中获取字段并将它们存储到变量中以便在UITableView中显示它们)时,它都会失败。我尝试通过使标签显示为带有作者姓名作为标题来对其进行测试。 “作者”是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()
        ...
}
...

//用于TABLEVIEW的行的行

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
}

}

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

  

解码失败:typeMismatch(Swift.Array,   Swift.DecodingError.Context(codingPath:[],debugDescription:   “打算对数组进行解码,但是找到了一个字典。”,   底层错误:nil))

它表示希望对数组进行解码,但是找到了字典。好吧,我将其更改为一次解码,而不是解码一个数组,而是解码一个结构,并声明了一个变量 在属于struct类型的类中(struct的目的反映了数组的目的)。

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

我不认为问题出在数组与字典上,但是控制台谈到了“ underlyingError”,即数组为零。无论变量是什么类型,无论是数组还是Struct,放置在textField中的变量始终为nil。

我收到此错误:

  

致命错误:展开可选值时意外发现nil

也许是穿线或松动的问题?

更新:此代码有效:

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

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

2 个答案:

答案 0 :(得分:0)

弄清楚您所发布代码的错误有些困难。我认为问题可能出在变量websiteObject的类型定义上。我创建了一个小操场进行测试,您的结构很好。

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

相关的更改在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提取数据也很重要。

enter image description here

该代码仅用于测试您的结构,不要指望一个干净的代码项目;)

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

enter image description here

答案 1 :(得分:0)

两个问题。

  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中,您必须通过给定的索引路径来获取项目。然后 出队 该单元格,在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
    }