在 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 元素。我正在通过 struct 和 enums 执行 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))
而且也不需要枚举 AllItem
和 data
,因为属性名称已经与 JSON 键相同。
【讨论】:
因此您已将所有属性设为非可选,但在您的响应中某些键有空值,因此您必须将它们设为可选 如果您有一些可以为空的键,您需要将该属性设为Optional
。
@Mukesh 枚举 AllItem
无论如何都没有被使用。
当然,只需将属性设为可选,它就会起作用。就像var item_code: String?
@Mukesh 非常感谢您的帮助,您挽救了这一天。它奏效了。以上是关于在 swift 4 和 Xcode 9 中解码 ExpandableTableView 的嵌套 json 数组的主要内容,如果未能解决你的问题,请参考以下文章
将代码更新到 xcode 9 和 swift 4 后应用程序崩溃
使用 Swift 4 和 Xcode 9 获取 JSON 数据