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

如何创建模型解码含字符串格式字典的API响应?JsonDecoder映射失败

问题排查与解决方案

核心错误原因

你遇到的解码失败问题,首要原因是缺少根模型:API响应的最外层是一个包含additional_data键的对象,但你直接尝试解码AdditionalData类型,JSONDecoder无法匹配根层级的结构,导致解码失败。

其次,虽然checklist_done和work_with_price是字符串化的JSON,理论上用String?类型可以接收,但如果没有正确处理外层结构,同样会失败。

正确的模型实现方案

1. 新增根模型

首先创建对应API最外层结构的根模型:

struct APIResponse: Codable {
    let additionalData: AdditionalData
    
    enum CodingKeys: String, CodingKey {
        case additionalData = "additional_data"
    }
}

2. 优化AdditionalData模型

将字符串化的JSON字段直接转为字典类型([String: String]?),通过自定义解码逻辑自动解析,比直接存字符串更实用:

struct AdditionalData: Codable {
    var id, bookingID: Int?
    var checklistDone: [String: String]?
    var workWithPrice: [String: String]?
    var workPrice: String?
    var estimatedPrice: Int?
    var discountCode, mechanicProfit: String?
    var discount, ccavenueCharge, ccavenueGst: Int?
    var totalPrice: String?
    var paymentMethod, gateway, paymentResponse: String?
    var status: String?
    var paymentmode, bankname: String?
    var txnid, banktxnid: String?
    var txndate: String?
    var createdAt, updatedAt: String?
    var adminDiscount: Int?

    enum CodingKeys: String, CodingKey {
        case id
        case bookingID = "booking_id"
        case checklistDone = "checklist_done"
        case workWithPrice = "work_with_price"
        case workPrice = "work_price"
        case estimatedPrice = "estimated_price"
        case discountCode = "discount_code"
        case mechanicProfit = "mechanic_profit"
        case discount
        case ccavenueCharge = "ccavenue_charge"
        case ccavenueGst = "ccavenue_gst"
        case totalPrice = "total_price"
        case paymentMethod = "payment_method"
        case gateway
        case paymentResponse = "payment_response"
        case status
        case paymentmode, bankname, txnid, banktxnid, txndate
        case createdAt = "created_at"
        case updatedAt = "updated_at"
        case adminDiscount = "admin_discount"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 解码常规字段
        id = try container.decodeIfPresent(Int.self, forKey: .id)
        bookingID = try container.decodeIfPresent(Int.self, forKey: .bookingID)
        workPrice = try container.decodeIfPresent(String.self, forKey: .workPrice)
        estimatedPrice = try container.decodeIfPresent(Int.self, forKey: .estimatedPrice)
        discountCode = try container.decodeIfPresent(String.self, forKey: .discountCode)
        mechanicProfit = try container.decodeIfPresent(String.self, forKey: .mechanicProfit)
        discount = try container.decodeIfPresent(Int.self, forKey: .discount)
        ccavenueCharge = try container.decodeIfPresent(Int.self, forKey: .ccavenueCharge)
        ccavenueGst = try container.decodeIfPresent(Int.self, forKey: .ccavenueGst)
        totalPrice = try container.decodeIfPresent(String.self, forKey: .totalPrice)
        paymentMethod = try container.decodeIfPresent(String.self, forKey: .paymentMethod)
        gateway = try container.decodeIfPresent(String.self, forKey: .gateway)
        paymentResponse = try container.decodeIfPresent(String.self, forKey: .paymentResponse)
        status = try container.decodeIfPresent(String.self, forKey: .status)
        paymentmode = try container.decodeIfPresent(String.self, forKey: .paymentmode)
        bankname = try container.decodeIfPresent(String.self, forKey: .bankname)
        txnid = try container.decodeIfPresent(String.self, forKey: .txnid)
        banktxnid = try container.decodeIfPresent(String.self, forKey: .banktxnid)
        txndate = try container.decodeIfPresent(String.self, forKey: .txndate)
        createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt)
        updatedAt = try container.decodeIfPresent(String.self, forKey: .updatedAt)
        adminDiscount = try container.decodeIfPresent(Int.self, forKey: .adminDiscount)
        
        // 解码字符串化的JSON字段,添加错误处理避免整体解码失败
        if let checklistStr = try container.decodeIfPresent(String.self, forKey: .checklistDone) {
            do {
                checklistDone = try JSONDecoder().decode([String: String].self, from: checklistStr.data(using: .utf8)!)
            } catch {
                print("checklist_done 解析失败: \(error)")
                checklistDone = nil
            }
        }
        
        if let workPriceStr = try container.decodeIfPresent(String.self, forKey: .workWithPrice) {
            do {
                workWithPrice = try JSONDecoder().decode([String: String].self, from: workPriceStr.data(using: .utf8)!)
            } catch {
                print("work_with_price 解析失败: \(error)")
                workWithPrice = nil
            }
        }
    }
}

3. 解码示例

使用根模型进行解码:

do {
    let apiResponse = try JSONDecoder().decode(APIResponse.self, from: yourJsonData)
    // 访问解析后的数据
    print("完成的检查项: \(apiResponse.additionalData.checklistDone ?? [:])")
    print("带价格的工单: \(apiResponse.additionalData.workWithPrice ?? [:])")
} catch {
    print("整体解码失败: \(error)")
}

备选方案:坚持用String接收字符串化JSON

如果必须保留checklistDone和workWithPrice为String?类型,只需新增根模型,无需修改AdditionalData的解码逻辑,解码时同样使用根模型即可:

// 修改后的AdditionalData(保留String类型)
struct AdditionalData: Codable {
    var id, bookingID: Int?
    var checklistDone: String?
    var workWithPrice: String?
    // ... 其他字段保持不变,CodingKeys也不变
}

// 解码方式
do {
    let apiResponse = try JSONDecoder().decode(APIResponse.self, from: yourJsonData)
    // 手动解析字符串化JSON
    if let checklistStr = apiResponse.additionalData.checklistDone {
        let checklistDict = try JSONDecoder().decode([String: String].self, from: checklistStr.data(using: .utf8)!)
        print(checklistDict)
    }
} catch {
    print(error)
}

内容的提问来源于stack exchange,提问作者Arish

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:44:58