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

Swift中JSON对象/数组二义性解码的错误处理优化方案

解决Swift中JSON字段兼具字典/数组类型时的精准解码错误问题

问题背景

在JSON数据中,部分字段(如示例中的name)有时表现为单个字典,有时表现为字典数组。现有Swift解码方案虽能适配两种格式,但错误提示不准确:当子字段(如@language)缺失时,错误信息显示为类型不匹配而非字段缺失,给嵌套数据的调试造成极大困扰。

场景示例

单个值(字典)格式

{
  "id": "item123",
  "name": {
    "@language": "en",
    "@value": "Test Item"
  }
}

多个值(数组)格式

{
  "id": "item123",
  "name": [
    {
      "@language": "en",
      "@value": "Test Item"
    },
    {
      "@language": "de",
      "@value": "Testartikel"
    }
  ]
}

现有实现的问题

现有代码通过try?尝试解码单个对象,失败后再解码数组,但try?会吞掉单个对象解码时的字段缺失错误,直接进入数组解码分支,最终抛出“预期数组却找到字典”的错误,掩盖了真实问题。

现有核心代码:

struct Item: Codable {
    var id: String
    var name: [LocalizedString]

    enum CodingKeys: String, CodingKey {
        case id
        case name
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)

        if let singleName = try? container.decode(LocalizedString.self, forKey: .name) {
            name = [singleName]
        } else {
            name = try container.decodeIfPresent([LocalizedString].self, forKey: .name) ?? []
        }
    }
}

struct LocalizedString: Codable {
    var language: String
    var value: String

    enum CodingKeys: String, CodingKey {
        case language = "@language"
        case value = "@value"
    }
}

当遇到缺失@language的错误JSON时,错误信息为:

Error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "name", intValue: nil)], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))

解决方案

核心思路:先判断字段的实际类型,再针对性调用解码逻辑,避免用try?吞掉真实错误。具体步骤:

  1. 针对name字段获取独立的解码器
  2. 先尝试解码为单个LocalizedString,若仅因类型不匹配失败,则尝试解码为数组
  3. 若单个解码时出现字段缺失等错误,直接抛出真实错误,不进入数组分支

修改后的核心代码:

struct Item: Codable {
    var id: String
    var name: [LocalizedString]

    enum CodingKeys: String, CodingKey {
        case id
        case name
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(String.self, forKey: .id)
        
        let nameKey = CodingKeys.name
        guard container.contains(nameKey) else {
            // 若name字段完全缺失,可根据需求抛出错误或设为空数组
            name = []
            return
        }
        
        // 获取name字段的独立解码器,用于判断类型
        let nameDecoder = try container.superDecoder(forKey: nameKey)
        do {
            // 先尝试解码为单个LocalizedString
            let singleLocalized = try LocalizedString(from: nameDecoder)
            name = [singleLocalized]
        } catch DecodingError.typeMismatch {
            // 类型不匹配,说明是数组,重新创建解码器并解码数组
            let recreatedNameDecoder = try container.superDecoder(forKey: nameKey)
            name = try [LocalizedString](from: recreatedNameDecoder)
        }
        // 其他错误(如字段缺失)会直接抛出,不会进入catch分支
    }
}

struct LocalizedString: Codable {
    var language: String
    var value: String

    enum CodingKeys: String, CodingKey {
        case language = "@language"
        case value = "@value"
    }
}

验证效果

测试缺失@language的JSON时,错误信息将变为精准的字段缺失提示:

Error: keyNotFound(CodingKeys(stringValue: "@language", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "name", intValue: nil), CodingKeys(stringValue: "@language", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"@language\", intValue: nil) (\"@language\").", underlyingError: nil))

该方案同时兼容单个字典和数组格式的正常解码,且错误信息精准指向真实问题,大幅提升调试效率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 08:35:10