【问题标题】:Getting an error when my app tries to download JSON当我的应用尝试下载 JSON 时出现错误
【发布时间】:2018-12-15 14:15:22
【问题描述】:

我终于开始更新我的应用使用的 BonusData.json 文件。现在,当它尝试加载数据时出现错误。完整代码如下,但我收到“JSON 下载失败”,它包含在 downloadJSON 函数中。

如果我正确阅读了我的代码,那意味着我在

中遇到了错误
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)

部分,但我不确定如何进一步排除故障。应该发生的是应用程序查看服务器,下载 JSON,然后将其保存在本地以用于填充 UITableView。如果没有数据连接,那就不用管了,直接使用本地保存的版本即可。由于应用程序加载空白,我假设它也没有按预期工作。

完整代码如下:

import UIKit
import os.log
import Foundation

class BonusListViewController: UITableViewController {
    var bonuses = [JsonFile.JsonBonuses]()
    var filteredBonuses = [JsonFile.JsonBonuses]()
    var detailViewController: BonusDetailViewController? = nil

    let defaults = UserDefaults.standard

    let searchController = UISearchController(searchResultsController: nil)

    override func viewDidLoad() {
        super.viewDidLoad()

        // MARK: Search Support
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Enter two letter state to filter"
        navigationItem.searchController = searchController
        definesPresentationContext = true

        // MARK: Settings Data Struct
        struct Constants {
            struct RiderData {
                let riderNumToH = "riderNumToH"
                let pillionNumToH = "pillionNumToH"
            }
            struct RallyData {
                let emailDestinationToH = "emailDestinationToH"
            }
        }
        //MARK: Load the bonuses
        print("About to call loadBonuses")
        loadBonuses { [weak self] bonuses in
            self?.bonuses = bonuses ?? []
            DispatchQueue.main.async {
                self?.tableView.reloadData()
            }
            print("loadBonuses called")
        }
    }

    // MARK: - Table View Configuration
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if isFiltering() {
            print("Showing \(filteredBonuses.count) Filtered Results")
            return filteredBonuses.count
        }

        print("Found \(bonuses.count) rows in section.")
        return bonuses.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "BonusListViewCell"
        guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? BonusListViewCell else {
            fatalError("The dequeued cell is not an instance of BonusListViewCell.")
        }
        // let bonus = bonuses[indexPath.row]
        let bonus: JsonFile.JsonBonuses
        if isFiltering() {
            bonus = filteredBonuses[indexPath.row]
        } else {
            bonus = bonuses[indexPath.row]
        }

        let urlString = "http://tourofhonor.com/appimages/"+(bonus.imageName)
        let url = URL(string: urlString)
        cell.primaryImage.downloadedFrom(url: url!)
        cell.nameLabel.text = bonus.name.capitalized
        cell.bonusCodeLabel.text = bonus.bonusCode.localizedUppercase
        cell.categoryLabel.text = bonus.category
        cell.valueLabel.text = "\(bonus.value)"
        cell.cityLabel.text = "\(bonus.city.capitalized),"
        cell.stateLabel.text = bonus.state.localizedUppercase

        return cell
    }

    // MARK: Functions
    // MARK: - Fetch JSON from ToH webserver

    func downloadJSON(completed: @escaping ([JsonFile.JsonBonuses]?) -> ()) {
        let url = URL(string: "http://tourofhonor.com/BonusData.json")!
        URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error == nil, let data = data {
                do {
                    let posts = try JSONDecoder().decode(JsonFile.self, from: data)
                    completed(posts.bonuses)
                    print("URLSession did not fail")
                } catch {
                    print("JSON Download Failed")
                }
            } else {
                print("downloadJSON completed")
                completed(nil)
            }
        }.resume()
    }

    func saveBonuses(_ bonuses: [JsonFile.JsonBonuses], to url: URL) {
        try? FileManager.default.removeItem(at: url)
        do {
            let data = try JSONEncoder().encode(bonuses)
            try data.write(to: url)
            print("saveBonuses successful")
        } catch {
            print("Error saving bonuses to file:", error)
        }
    }

    func loadBonusesFromFile(_ url: URL) -> [JsonFile.JsonBonuses]? {
        do {
            let data = try Data(contentsOf: url)
            let bonuses = try JSONDecoder().decode([JsonFile.JsonBonuses].self, from: data)
            print("loadBonusesFromFile successful")
            return bonuses
        } catch {
            print("Error loading bonuses from file:", error)
            return nil
        }
    }

    func loadBonuses(completion: @escaping ([JsonFile.JsonBonuses]?) -> Void) {
        let localBonusesURL = try! FileManager.default
            .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
            .appendingPathComponent("BonusData.json")
        downloadJSON { bonuses in
            if let bonuses = bonuses {
                completion(bonuses)
                self.saveBonuses(bonuses, to: localBonusesURL)
            } else {
                print("versions did not match")
                completion(self.loadBonusesFromFile(localBonusesURL))
            }
        }
    }

    func searchBarIsEmpty() -> Bool {
        // Returns true if the text is empty or nil
        return searchController.searchBar.text?.isEmpty ?? true
    }

    func filterContentForSearchText(_ searchText: String, scope: String = "All") {
        filteredBonuses = bonuses.filter({( bonus: JsonFile.JsonBonuses) -> Bool in
            return bonus.state.localizedCaseInsensitiveContains(searchText)
        })
        tableView.reloadData()
    }

    func isFiltering() -> Bool {
        return searchController.isActive && !searchBarIsEmpty()
    }

    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? BonusDetailViewController {
            destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
        }
    }
}

extension BonusListViewController: UISearchResultsUpdating {
    // MARK: - UISearchResultsUpdating Delegate
    func updateSearchResults(for searchController: UISearchController) {
        filterContentForSearchText(searchController.searchBar.text!)
    }
}

JSON 托管在这里:http://tourofhonor.com/BonusData.json

【问题讨论】:

  • 顺便说一句 - 您的 JSON Download Failed 消息具有误导性。成功下载后会打印,但数据无法解码。而且您的downloadJSON completed 消息也是错误的。下载失败时会打印出来。
  • 要查看您的问题,请将print("JSON Download Failed") 更改为print("Can't decode JSON: \(error)")

标签: ios json swift decoder


【解决方案1】:

我将专注于我认为是您问题的核心而不是技术修复。

我不确定如何进一步排除故障。

do {
  // ...
  let posts = try JSONDecoder().decode(JsonFile.self, from: data)
  // ...
} catch let error {
  // Do something with this error.
}

decode 抛出有关异常的详细信息,您可以在收到错误时执行这些操作。

【讨论】:

    【解决方案2】:

    您尝试下载的 JSON 格式似乎不正确。它在对象之间缺少逗号,并且在列表末尾有一个额外的逗号。

    有许多工具可以验证 JSON,但可以使用的工具之一是 https://jsonlint.com/。如果您将http://tourofhonor.com/BonusData.json 的输出粘贴到那里,它将为您突出显示格式错误,并为您提供一些有关如何修复它们的指导。

    【讨论】:

      猜你喜欢
      • 2022-06-14
      • 2011-07-12
      • 2013-05-31
      • 1970-01-01
      • 2018-12-31
      • 2023-02-04
      • 1970-01-01
      • 2020-09-25
      • 2015-04-13
      相关资源
      最近更新 更多