Neck
Neck

Reputation: 620

Decoding nested json array for ExpandableTableView in swift 4 and Xcode 9

I'm new to iOS and making an ExpandableTableView with JSON data. The data is in form of nested arrays. I want to set data of parent array as TableView header and data of child array as ExpandableTable elements. I'm performing JSONDecoding through struct and enums and storing the response in DataModelClass. While Decoding it's throwing error

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

that is ok because I know that I'm missing something very small. I've gone through it many times but I'm unable to understand the problem.

There are many similar types of questions on stack but none of then are matching my criteria. Please help me out.

My JSON is

    [
    {
        "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"
            }
        ]
    }
]

and the Model class is

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

My struct and enums are

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"
    }

}

and my ViewController is

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

if anyone need anything more, please comment...

Upvotes: 1

Views: 610

Answers (1)

Mukesh
Mukesh

Reputation: 2902

Try changing these two lines:

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

to

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

And also there is no need of the enums AllItem and data as the property names are already same as JSON keys.

Upvotes: 1

Related Questions