Декодирование вложенного массива json для ExpandableTableView в Swift 4 и Xcode 9

Я новичок в iOS и делаю ExpandableTableView с данными JSON. Данные представлены в виде вложенных массивов. Я хочу установить данные родительского массива как Заголовок TableView и данные дочернего массива как элементы ExpandableTable. Я выполняю 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 02.05.2018 12:48

@vadian спасибо, я пробовал это, но он дает «Значение типа '[AllItem]' не имеет элемента 'category_id'»

Neck 02.05.2018 12:53

Конечно, поскольку объект представляет собой массив, список из нескольких объектов. Вам нужна петля. И почему вы используете две разные модели для одних и тех же данных? Кстати перечисление AllItem не действует.

vadian 02.05.2018 12:58

спасибо @vadian Я понял. Я знал, что не хватает чего-то очень маленького.

Neck 02.05.2018 13:12
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
456
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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))
}

Кроме того, нет необходимости в перечислениях AllItem и data, поскольку имена свойств уже совпадают с ключами JSON.

спасибо @Mukesh, что немного помог, теперь он выдает ошибку "valueNotFound (Swift.String, Swift.DecodingError.Context (codingPath: [_JSONKey (stringValue:" Index 0 ", intValue: 0), CodingKeys (stringValue:" data " , intValue: nil), _JSONKey (stringValue: «Индекс 0», intValue: 0), CodingKeys (stringValue: «item_code», intValue: nil)], debugDescription: «Ожидаемое значение String, но вместо этого найдено null.», lowerError: nil )) "

Neck 02.05.2018 13:09

поэтому вы сделали все свойства необязательными, но в вашем ответе есть нулевые значения для некоторых ключей, поэтому вы должны сделать их необязательными

Mukesh 02.05.2018 13:11

Если у вас есть ключи, которые могут быть нулевыми, вам нужно сделать это свойство Optional.

Mukesh 02.05.2018 13:13

@Mukesh Перечисление AllItem в любом случае не используется.

vadian 02.05.2018 13:16

Конечно, просто сделайте свойства необязательными, и он будет работать. Как var item_code: String?

Mukesh 02.05.2018 13:18

@Mukesh большое спасибо за вашу помощь, вы спасли положение. Это сработало.

Neck 02.05.2018 13:20

Другие вопросы по теме