You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 4.1与Xcode9.3中如何用JSONDecoder解析嵌套JSON数组和对象?

解决iOS多层嵌套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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:22:28