【问题标题】:Index out of range error when trying to access JSON data from structs尝试从结构访问 JSON 数据时出现索引超出范围错误
【发布时间】:2018-11-13 18:40:43
【问题描述】:

我正在尝试从 IEX 金融 API 获取股票数据,我需要的所有内容都可以很好地输出到控制台,但我在尝试使用该数据插入我的表格视图时遇到了问题。

这是API url,这是JSON in a readable format

这些是我设置的结构

struct Welcome: Decodable {
    let aapl, fb, msft, tsla, goog: Aapl

    enum CodingKeys: String, CodingKey {
        case aapl = "AAPL"
        case fb = "FB"
        case msft = "MSFT"
        case tsla = "TSLA"
        case goog = "GOOG"
    }
}

struct Aapl: Decodable {
    let quote: Quote
    let news: [News]
}

struct Quote: Decodable {
    let symbol: String
    let companyName: String
    let latestPrice: Double
}

struct News: Decodable {
    let url: String
    let image: String
}

这是获取数据的方式:

fetchData(url: stockApiUrl) { (result: FetchResult<Welcome>) -> (Void) in
    switch result {
    case .success(let object): self.stockData = [object]
        print("stockData: \n\n\(self.stockData)")
    case .failure(let error):
        print("Error decoding JSON: \n\n\(error)")
    }
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

并将数据传入

var stockData = [Welcome]()

使用现在的代码,它会输出公司所需的所有数据,但我对如何访问所有公司数据感到困惑,以便我可以抓取每一部分(companyNamelatestPrice等)单独显示,以便我可以在表格视图中显示它们。

例如,在我的cellForRowAt 表视图方法中,我试图将companyName 设置为标签,但使用let quotes = stockData[indexPath.row] 会给我一个索引超出范围错误,所以我不能再使用类似@ 987654331@.

我想知道是否需要重新构建我的结构(尽管我将我的代码与 quicktype.io 中的建议代码进行了比较,并且它是相同的),或者重新设计其他东西。

感谢您的指导!

编辑:

numberOfRows方法:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0 {
        return stockData.count
    } else if section == 1 && fetchingMore {
        return 1
    }
    return 0
}

fetchData函数:

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)
            let object = try? JSONDecoder().decode([String:Aapl].self,from: data)
            completion(.success(object))
        } catch {
            completion(.failure(error))
        }
    }.resume()
}

【问题讨论】:

  • 你需要展示你的numberOfRowsInSection方法。
  • 我刚刚编辑了我的问题以包含该方法 - 我实际上在那里有一个设定的数字,但您提醒将其更改为 return stockData.count。现在我没有得到索引超出范围错误,但它只显示一行,当数据调用返回 5 家公司的数据时,我不确定那里发生了什么。我仍然无法访问 companyNames 以放入标签。
  • 与超出范围的问题无关,但考虑到您将结果(即 字典 的表示)包装在一个数组中(→ [object]) .该数组必须只包含一项。我会编写一个自定义初始化程序并将 5 个股票转换为一个数组。

标签: ios json swift struct


【解决方案1】:

要使用更通用和更具描述性的名称,请将 Aapl 重命名为 Stock

struct Stock: Decodable {
    let quote: Quote
    let news: [News]
}

stockData 声明为字典并为键创建一个数组

var stockData = [String:Stock]()
var keys = [String]()

fetchData 替换为

fetchData(url: stockApiUrl) { (result: FetchResult<[String:Stock]>) -> (Void) in
    switch result {
    case .success(let object): 
        self.stockData = object
        self.keys = Array(object.keys)
        print("stockData: \n\n\(self.stockData)")
    case .failure(let error):
        print("Error decoding JSON: \n\n\(error)")
    }
    DispatchQueue.main.async {
        self.tableView.reloadData()
    }
}

cellForRow

let stock = stocks[keys[indexPath.row]]!
cell.companyNameLabel = stock.quote.companyName

【讨论】:

  • 不是 keys[indexPath.row] = stock.keys[indexPath.row] 吗?如果是,为什么新分配
  • 是的。但是一遍又一遍地获取密钥可能比使用静态数组更昂贵。
  • 这是一个很好的解决方案,感谢 vadian - 我希望找到一个保留通用 fetchData 功能的解决方案(你也帮我解决了这个问题!),所以这真的很完美。对于未来的案例,我也会牢记这种方法。希望你有一个美好的一周,编码愉快!
  • 如果我可以提出与同一问题有关的后续问题(如有必要,我可以发布一个新问题),我将如何使用这种方法访问我的 [News] 结构中的属性?我在didSelectRow 中尝试过guard let webUrl = URL(string: stock.news[1].url) else {return},当我打印出webUrl 时,它会成功打印所有webUrls,但它们都是分开的,所以我不能设置webView.load(URLRequest(url: webUrl)) 或我会得到一个超出范围崩溃的索引。再次感谢您的耐心和帮助 vadian,非常感谢!
  • 请提出一个新问题。
【解决方案2】:

你可以这样做

 var welcomes:[String:Aapl]! 

 welcomes = try? JSONDecoder().decode([String:Aapl].self,from:jsonData)

然后在numberOfRowsInSection中使用这个

return welcomes.keys.count

cellForRowAt里面

let item = welcomes[welcomes.keys[indexPath.row]]!.quote.companyName

如果您不关心排序,请使用它

【讨论】:

  • 好吧,我试了一下。如果我在 fetchData 函数中将 let object = try JSONDecoder().decode(T.self, from: data) 更改为 let object = try? JSONDecoder().decode([String:Aapl].self,from: data),然后尝试将 numberOfRows 更改为 return object.keys.count,我会收到错误 @987654332 @
  • 我编辑了我的问题以在底部包含完整的fetchData 函数
【解决方案3】:

这个问题主要是因为你的DataSource是别的东西。

例如,如果您使用 welcome.count 设置 numberForRows in section 并使用其他东西在cellForRowAt 内进行迭代,如果该东西与numberForRows 的数量不匹配,前提是它会导致

索引超出范围错误

因此,请确保您提供相同的 DataSource 并在具有相同 range 的数组内进行迭代。

现在更深入地了解您的情况,我不知道您如何设置numberOfRows,但假设是别的东西。

我会提出以下建议。

而不是迭代 quotes[indexPath.row] 而是使用相同的 DataSource 并按照自己的方式工作,我假设它是 stockData 所以假设它就像你的 structs 的样子

stockData[indexPath.row].qoutes.companyName

更新

所以对于你的问题,JSON 它本身也不是array,所以做一些工作来制作每个Aapl 的最终数组是一个选项。

您可以在stockDatadidSet 中手动创建一个,它会返回一个数组并使用它来显示数据和迭代。

这段代码应该会给你一个想法。

 var myFinalData: [Aapl] = [] {
    didSet {
        //reload your table
    }
}
var stockData = [Welcome]() {
    didSet{
        myFinalData.append(stockData.aapl)
        myFinalData.append(stockData.fb)
        // etc
    }
}
// then you can use myFinalData as you DataSource and iterate easily inside of it 

【讨论】:

  • 如果您的数据源是 stockData.count ,这将起作用,如果它是其他东西,它会抛出相同的错误,所以我希望你有这个想法,愉快的编码
  • 谢谢,我在 numberOfRows 中返回了一组行数,但我现在将其更改为返回 stockData.count - 但是即使这样做,我仍然无法将标签设置为 stockData[ indexPath.row].quotes.companyName,因为stockData[indexPath.row]. 的唯一下标成员是个别公司 - 即我可以将标签设置为全部显示一个公司的名称,但不是全部
  • 它是因为你的结构说准确地说 (aapl, fb, msft, tsla, goog) 不是 Aapl 数组更改你的结构有一个 Aapl 数组并从中迭代你需要帮助吗?
  • 它自己的响应不是一个数组,所以你可以做一些工作来将所有这些对象包装到一个数组中:)
  • 如果我在Welcome 结构中将Aapl 更改为[Aapl],我会收到错误:typeMismatch(Swift.Array&lt;Any&gt;, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "AAPL", intValue: nil)], debugDescription: "Expected to decode Array&lt;Any&gt; but found a dictionary instead.", underlyingError: nil))
猜你喜欢
  • 2020-01-14
  • 2018-01-17
  • 2021-03-02
  • 2020-09-21
  • 2021-12-30
  • 1970-01-01
  • 2021-03-28
  • 2020-06-22
  • 2021-06-26
相关资源
最近更新 更多