Swift 4.1与Xcode9.3中如何用JSONDecoder解析嵌套JSON数组和对象?
嘿,我来帮你搞定这个JSON解析的麻烦!先看看你遇到的错误:
typeMismatch(Swift.Array
, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "OrderDetials", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "order_details", intValue: nil)], debugDescription: "Expected to decode Array but found a string/data instead.", underlyingError: nil))
这个错误说得很清楚:**你定义的order_details是OrderItems类型(一个包含数组的对象),但后端返回的order_details其实是一个字符串!**而且这个字符串还不是标准的单个JSON对象,是两个JSON片段拼在一起的,甚至还有不符合JSON规范的单引号',这就是解析失败的核心原因。
问题根源分析
看你的JSON数据:
{ "OrderDetials": [ { "order_details": "{'allorder':[{\"date_time\":\"\",\"item\":\"2\",\"name\":\"CHICKN ROLL\",\"price\":\"360\"}]}{\"TaxDetials\":[{\"tax_per\":\"5\",\"tax_name\":\"CGST & SGST\"}]}", // 其他字段... } ] }
这里的order_details是一个字符串,内容是两个拼接的JSON对象:{"allorder": [...]}{"TaxDetials": [...]},而且开头用了单引号,完全不符合标准JSON格式,所以JSONDecoder直接懵了。
解决方案:自定义解码逻辑
我们需要先把order_details解码成字符串,再手动处理这个字符串,转换成我们需要的OrderItems对象。具体步骤如下:
1. 修改OrderDetails结构体,添加自定义解码逻辑
我们要重写init(from decoder:)方法,先处理常规字段,再单独解析order_details字符串:
struct MyOrder: Decodable { var OrderDetials: [OrderDetails] } struct OrderDetails: Decodable { var order_id: String? var order_ref_number: String? var cust_mob: String? var store_name: String? var store_id: String? var order_details: OrderItems? var order_amount: String? var order_status: String? var order_status_remark: String? var order_place_at: String? var order_status_change_at: String? // 定义解码键 enum CodingKeys: String, CodingKey { case order_id, order_ref_number, cust_mob, store_name, store_id, order_details, order_amount, order_status, order_status_remark, order_place_at, order_status_change_at } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 先解码所有常规字段 order_id = try container.decodeIfPresent(String.self, forKey: .order_id) order_ref_number = try container.decodeIfPresent(String.self, forKey: .order_ref_number) cust_mob = try container.decodeIfPresent(String.self, forKey: .cust_mob) store_name = try container.decodeIfPresent(String.self, forKey: .store_name) store_id = try container.decodeIfPresent(String.self, forKey: .store_id) order_amount = try container.decodeIfPresent(String.self, forKey: .order_amount) order_status = try container.decodeIfPresent(String.self, forKey: .order_status) order_status_remark = try container.decodeIfPresent(String.self, forKey: .order_status_remark) order_place_at = try container.decodeIfPresent(String.self, forKey: .order_place_at) order_status_change_at = try container.decodeIfPresent(String.self, forKey: .order_status_change_at) // 处理order_details字符串 guard let orderDetailsStr = try container.decodeIfPresent(String.self, forKey: .order_details) else { order_details = nil return } // 第一步:把单引号替换成双引号,符合JSON规范 let normalizedStr = orderDetailsStr.replacingOccurrences(of: "'", with: "\"") // 第二步:分割两个拼接的JSON对象 guard let firstCloseBraceIndex = normalizedStr.firstIndex(of: "}") else { order_details = nil return } let allorderStr = String(normalizedStr[...firstCloseBraceIndex]) let taxStr = String(normalizedStr[normalizedStr.index(after: firstCloseBraceIndex)...]) // 第三步:分别解析两个JSON片段 let allorderData = allorderStr.data(using: .utf8)! let allorderResult = try JSONDecoder().decode([String: [OrderItems.AllOrder]].self, from: allorderData) let allorder = allorderResult["allorder"] ?? [] let taxData = taxStr.data(using: .utf8)! let taxResult = try JSONDecoder().decode([String: [OrderItems.TaxDetails]].self, from: taxData) let taxDetials = taxResult["TaxDetials"] ?? [] // 最后组装成OrderItems对象 order_details = OrderItems(allorder: allorder, TaxDetials: taxDetials) } } struct OrderItems: Decodable { var allorder: [AllOrder] var TaxDetials: [TaxDetails] struct AllOrder: Decodable { var date_time: String? var item: String? var name: String? // 补充JSON里存在的price字段,避免数据丢失 var price: String? } struct TaxDetails: Decodable { var tax_per: String? var tax_name: String? } }
2. 优化解码代码,避免强制解包
原来的代码里用了data!,容易崩溃,改成安全的可选绑定:
if let data = data { do { let myOrder = try JSONDecoder().decode(MyOrder.self, from: data) for items in myOrder.OrderDetials { // 安全处理可选值,避免强制解包导致崩溃 let newOrder = MyOrders( order_id: items.order_id ?? "", order_ref_number: items.order_ref_number ?? "", cust_mob: items.cust_mob ?? "", store_name: items.store_name ?? "", store_id: items.store_id ?? "", order_details: items.order_details, order_amount: items.order_amount ?? "", order_status: items.order_status ?? "", order_status_remark: items.order_status_remark ?? "", order_place_at: items.order_place_at ?? "", order_status_change_at: items.order_status_change_at ) self.orders.append(newOrder) print(items.cust_mob ?? "未获取到手机号") } print("解析成功:\(myOrder)") } catch { print("解码失败:\(error.localizedDescription)") } }
3. 可选优化:改用Swift风格的命名规范
建议把结构体属性改成小驼峰命名,用CodingKeys映射JSON的蛇形命名,代码更符合Swift习惯:
struct MyOrder: Decodable { var orderDetails: [OrderDetails] enum CodingKeys: String, CodingKey { case orderDetails = "OrderDetials" } } struct OrderDetails: Decodable { var orderId: String? var orderRefNumber: String? var custMob: String? var storeName: String? var storeId: String? var orderDetails: OrderItems? var orderAmount: String? var orderStatus: String? var orderStatusRemark: String? var orderPlaceAt: String? var orderStatusChangeAt: String? enum CodingKeys: String, CodingKey { case orderId = "order_id" case orderRefNumber = "order_ref_number" case custMob = "cust_mob" case storeName = "store_name" case storeId = "store_id" case orderDetails = "order_details" case orderAmount = "order_amount" case orderStatus = "order_status" case orderStatusRemark = "order_status_remark" case orderPlaceAt = "order_place_at" case orderStatusChangeAt = "order_status_change_at" } // 自定义init(from decoder:)逻辑同上,只是属性名替换成小驼峰即可 }
总结
这个问题的核心是后端返回的order_details格式不标准,不是直接的JSON对象而是字符串,我们需要通过自定义解码逻辑先处理这个字符串,转换成我们需要的模型对象。按照上面的代码修改后,应该就能正常解析了。
内容的提问来源于stack exchange,提问作者Neck

