在 swift 4 和 Xcode 9 中解码 ExpandableTableView 的嵌套 json 数组

Posted

技术标签:

【中文标题】在 swift 4 和 Xcode 9 中解码 ExpandableTableView 的嵌套 json 数组【英文标题】:Decoding nested json array for ExpandableTableView in swift 4 and Xcode 9 【发布时间】:2018-05-02 10:42:36 【问题描述】:

我是 iOS 新手,并使用 JSON 数据制作 ExpandableTableView。数据采用嵌套数组的形式。我想将父数组的数据设置为 TableView 标头,将子数组的数据设置为 ExpandableTable 元素。我正在通过 structenums 执行 JSONDecoding,并将响应存储在 DataModelClass 中。 解码时抛出错误

typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

没关系,因为我知道我遗漏了一些非常小的东西。我已经经历了很多次,但我无法理解这个问题。

堆栈上有许多类似类型的问题,但没有一个符合我的标准。请帮帮我。

我的 JSON 是

    [
    
        "category_id": "1",
        "category_name": "ROLL",
        "category_start_time": "10:00:00",
        "category_end_time": "22:59:59",
        "data": [
            
                "id": "301",
                "item_code": null,
                "item_name": "CHICKN ROLL",
                "main_item_id": "1",
                "item_price": null,
                "tax_id": "0",
                "tax_per": "0",
                "tax_amount": "0",
                "item_net_price": "180",
                "sub_item_price": "0",
                "sub_tax_amount": "0",
                "sub_item_net_price": "0",
                "created_date": "2018-02-28 12:20:53",
                "created_by": null,
                "image_name": "",
                "image_path": null,
                "quantity": null,
                "notify_quant": null,
                "active": "1",
                "today_avilable": "1",
                "kot": "1",
                "counter_id": "0",
                "store_id": "1"
            
        ]
    
]

模型类是

    import Foundation

class AllStoreItems 
    var category_id: String
    var category_name: String
    var category_start_time: String
    var category_end_time: String
    var data: [data]
    var expanded: Bool

    init(category_id: String, category_name: String, category_start_time: String, category_end_time: String, data: [data], expanded: Bool = false) 
        self.category_id = category_id
        self.category_name = category_name
        self.category_start_time = category_start_time
        self.category_end_time = category_end_time
        self.data = data
        self.expanded = expanded
    



class ItemsData 
    var id: String
    var item_code: String
    var item_name: String
    var main_item_id: String
    var item_price: String
    var tax_id: String
    var tax_per: String
    var tax_amount: String
    var item_net_price: String
    var sub_item_price: String
    var sub_tax_amount: String
    var sub_item_net_price: String
    var created_date: String
    var created_by: String
    var image_name: String
    var image_path: String
    var quantity: String
    var notify_quant: String
    var active: String
    var today_avilable: String
    var kot: String
    var counter_id: String
    var store_id: String

    init(id: String, item_code: String, item_name: String, main_item_id: String, item_price: String, tax_id: String, tax_per: String, tax_amount: String, item_net_price: String, sub_item_price: String, sub_tax_amount: String, sub_item_net_price: String, created_date: String, created_by: String, image_name: String, image_path: String, quantity: String, notify_quant: String, active: String, today_avilable: String, kot: String, counter_id: String, store_id: String) 
        self.id = id
        self.item_code = item_code
        self.item_name = item_name
        self.main_item_id = main_item_id
        self.item_price = item_price
        self.tax_id = tax_id
        self.tax_per = tax_per
        self.tax_amount = tax_amount
        self.item_net_price = item_net_price
        self.sub_item_price = sub_item_price
        self.sub_tax_amount = sub_tax_amount
        self.sub_item_net_price = sub_item_net_price
        self.created_date = created_date
        self.created_by = created_by
        self.image_name = image_name
        self.image_path = image_path
        self.quantity = quantity
        self.notify_quant = notify_quant
        self.active = active
        self.today_avilable = today_avilable
        self.kot = kot
        self.counter_id = counter_id
        self.store_id = store_id
    

我的结构和枚举是

import Foundation

struct AllItem: Decodable 
    var category_id: String
    var category_name: String
    var category_start_time: String
    var category_end_time: String
    var data: [data]

    enum AllItem: String 
        case category_id = "category_id"
        case category_name = "category_name"
        case category_start_time = "category_start_time"
        case category_end_time = "category_end_time"
        case data = "data"
    


struct data: Decodable 
    var id: String
    var item_code: String
    var item_name: String
    var main_item_id: String
    var item_price: String
    var tax_id: String
    var tax_per: String
    var tax_amount: String
    var item_net_price: String
    var sub_item_price: String
    var sub_tax_amount: String
    var sub_item_net_price: String
    var created_date: String
    var created_by: String
    var image_name: String
    var image_path: String
    var quantity: String
    var notify_quant: String
    var active: String
    var today_avilable: String
    var kot: String
    var counter_id: String
    var store_id: String

    enum data: String 
        case id = "id"
        case item_code = "item_code"
        case item_name = "item_name"
        case main_item_id = "main_item_id"
        case item_price = "item_price"
        case tax_id = "tax_id"
        case tax_per = "tax_per"
        case tax_amount = "tax_amount"
        case item_net_price = "item_net_price"
        case sub_item_price = "sub_item_price"
        case sub_tax_amount = "sub_tax_amount"
        case sub_item_net_price = "sub_item_net_price"
        case created_date = "created_date"
        case created_by = "created_by"
        case image_name = "image_name"
        case image_path = "image_path"
        case quantity = "quantity"
        case notify_quant = "notify_quant"
        case active = "active"
        case today_avilable = "today_avilable"
        case kot = "kot"
        case counter_id = "counter_id"
        case store_id = "store_id"
    


我的 ViewController 是

class CustomItemTableView: UITableViewCell 

    @IBOutlet weak var textLabelOne: UILabel!
    @IBOutlet weak var textLabelTwo: UILabel!



class AllItemsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AllItemsHeaderViewDelegate 

    @IBOutlet weak var headerStoreLogoIV: UIImageView!
    @IBOutlet weak var tableView: UITableView!

    var storeId: String = ""
    var storeCatId: String = ""
    var storeName: String = ""
    var storeLogoLink: String = ""

    @IBOutlet weak var storeImage: UIImageView!
    @IBOutlet weak var storeNameLabel: UILabel!


    let mainUrl = BaseURL()

    var items = [AllStoreItems]()
    var subItems = [ItemsData]()

    override func viewDidLoad() 
        super.viewDidLoad()
        ExpandItemsApi()
        let imgUrl: String = mainUrl.MainUrl + "logo/" + storeLogoLink
        let imgUrlStr: String = imgUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        //let imgUrl2: URL = URL(string: imgUrlStr)!

        //let imgUrl3 = URLRequest(url: imgUrl2!)

        storeNameLabel.text = storeName
//        storeNames.text = storeName
//        storeCategory.text = storeCatId
//        storeIds.text = storeId
//        imgLink.text = storeLogoLink

        storeImage.downloadedFrom(link: imgUrlStr)

    

    @IBAction func backButton(_ sender: Any) 
        self.dismiss(animated: false, completion: nil)
    

    func ExpandItemsApi() 

        let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)

        myActivityIndicator.center = view.center
        myActivityIndicator.hidesWhenStopped = false
        myActivityIndicator.startAnimating()
        view.addSubview(myActivityIndicator)

        let itemsUrl = URL(string: mainUrl.MainUrl + "viewmaincat")
        var itemUrls = URLRequest(url: itemsUrl!)
        itemUrls.httpMethod = "POST"

        itemUrls.addValue("application/json", forHTTPHeaderField: "content-type")
        itemUrls.addValue("application/json", forHTTPHeaderField: "Accept")

        let storeDetails = ["store_id": storeId] as [String: String]

        do 
            itemUrls.httpBody = try JSONSerialization.data(withJSONObject: storeDetails, options: .prettyPrinted)
        catch let error 
            toastNeck(message: "\(error)")
            myActivityIndicator.stopAnimating()
            myActivityIndicator.hidesWhenStopped = true
        

        URLSession.shared.dataTask(with: itemUrls) 
            (datas, response, error) in
            if datas != nil 
                do 
                    let itemDetails = try JSONDecoder().decode(AllItem.self, from: datas!)

                    self.items.append(AllStoreItems(category_id: itemDetails.category_id, category_name: itemDetails.category_name, category_start_time: itemDetails.category_start_time, category_end_time: itemDetails.category_end_time, data: itemDetails.data))

                    print(itemDetails.data)
//                   let subItem = try JSONDecoder().decode(data.self, from: datas!)
//                   self.subItems.append(ItemsData(id: subItem.id, item_code: subItem.item_code, item_name: subItem.item_name, main_item_id: subItem.main_item_id, item_price: subItem.item_price, tax_id: subItem.tax_id, tax_per: subItem.tax_per, tax_amount: subItem.tax_amount, item_net_price: subItem.item_net_price, sub_item_price: subItem.sub_item_price, sub_tax_amount: subItem.sub_tax_amount, sub_item_net_price: subItem.sub_item_net_price, created_date: subItem.created_date, created_by: subItem.created_by, image_name: subItem.image_name, image_path: subItem.image_path, quantity: subItem.quantity, notify_quant: subItem.notify_quant, active: subItem.active, today_avilable: subItem.today_avilable, kot: subItem.kot, counter_id: subItem.counter_id, store_id: subItem.store_id))
                catch let errors 
                    self.toastNeck(message: "\(errors)")
                    print(errors)
                    DispatchQueue.main.async
                        myActivityIndicator.stopAnimating()
                        myActivityIndicator.hidesWhenStopped = true
                    
            
                DispatchQueue.main.async 
                    self.tableView.reloadData()
                
            
        .resume()

    

    func numberOfSections(in tableView: UITableView) -> Int 
        return items.count
    

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        return items[section].data.count
    

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat 
        return 50
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
        if items[indexPath.section].expanded 
            return 50
        else 
            return 0
        
    

    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat 
        return 5
    

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? 
        let header = AllItemsHeaderView()
        header.customInit(title: items[section].category_name, section: section, delegate: self)
        return header
    

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
        let cell: CustomItemTableView = tableView.dequeueReusableCell(withIdentifier: "customItems") as! CustomItemTableView
        cell.textLabelOne.text = items[indexPath.section].data[indexPath.row].item_name
        cell.textLabelTwo.text = items[indexPath.section].data[indexPath.row].item_price
        return cell
    

    func toggleSection(header: AllItemsHeaderView, section: Int) 
        items[section].expanded = !items[section].expanded
        tableView.beginUpdates()

        for i in 0 ..< items[section].data.count 
            tableView.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
        
        tableView.endUpdates()
    

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) 
        toastNeck(message: items[indexPath.section].data[indexPath.row].item_name)
    

    func toastNeck(message: String) 
        DispatchQueue.main.async 
            let toastLabel = UILabel(frame: CGRect(x: self.view.frame.size.width/2-100, y: self.view.frame.size.height/2, width: 200, height: 40))
            toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.4)
            toastLabel.textColor = UIColor.white
            toastLabel.textAlignment = NSTextAlignment.center
            toastLabel.text = message
            toastLabel.layer.cornerRadius = 15
            toastLabel.clipsToBounds = true
            self.view.addSubview(toastLabel)
            UIView.animate(withDuration: 8, animations: 
                toastLabel.alpha = 0, completion: (isCompleted) in toastLabel.removeFromSuperview())
        
    

如果有人需要更多,请评论...

【问题讨论】:

翻译成您的情况,错误消息是Expected to decode AllItem but found [AllItem] instead。请学习阅读 JSON,它以 [ 开头,它代表一个数组。 @vadian 谢谢,我试过了,但它给出了“'[AllItem]' 类型的值没有成员'category_id'” 当然,因为对象是一个数组,是多个对象的列表。你需要一个循环。为什么你对相同的数据使用两种不同的模型?顺便说一下枚举AllItem 没有效果。 谢谢@vadian 我明白了。我知道错过了一些非常小的东西。 【参考方案1】:

尝试更改这两行:

let itemDetails = try JSONDecoder().decode(AllItem.self, from: datas!)

self.items.append(AllStoreItems(category_id: itemDetails.category_id, category_name: itemDetails.category_name, category_start_time: itemDetails.category_start_time, category_end_time: itemDetails.category_end_time, data: itemDetails.data))

let itemDetails = try JSONDecoder().decode([AllItem].self, from: datas!)

for item in itemDetails 
     self.items.append(AllStoreItems(category_id: item.category_id, category_name: item.category_name, category_start_time: item.category_start_time, category_end_time: item.category_end_time, data: item.data))

而且也不需要枚举 AllItemdata,因为属性名称已经与 JSON 键相同。

【讨论】:

因此您已将所有属性设为非可选,但在您的响应中某些键有空值,因此您必须将它们设为可选 如果您有一些可以为空的键,您需要将该属性设为Optional @Mukesh 枚举 AllItem 无论如何都没有被使用。 当然,只需将属性设为可选,它就会起作用。就像var item_code: String? @Mukesh 非常感谢您的帮助,您挽救了这一天。它奏效了。

以上是关于在 swift 4 和 Xcode 9 中解码 ExpandableTableView 的嵌套 json 数组的主要内容,如果未能解决你的问题,请参考以下文章

Swift 4 Xcode 使用 JSON 解码的问题

如何在 Xcode 9.3 中切换到 Swift 4.0?

将代码更新到 xcode 9 和 swift 4 后应用程序崩溃

使用 Swift 4 和 Xcode 9 获取 JSON 数据

Xcode 存档验证错误 - Xcode 9 和 Swift 4

无法在 Alamofire 中禁用缓存(Xcode 9 - swift 4)