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

如何用单个Swift CodingKeys解析含重复键的JSON数据?

问题:Swift Decodable解析含重复"name"键的JSON

Unsplash API返回的JSON片段如下:

{
    "exif": {
        "name": "Canon, EOS 6D"
    },
    "location": {
        "name": "Antelope Canyon, United States"
    },
    "user": {
        "name": "Joe Gardner"
    }
}

尝试解析为Swift Decodable结构体时,编写了如下代码,但遇到CodingKeys不允许重复键名的问题:

struct WallpaperResponse: Decodable {
    
    var location: String?
    var camera: String?
    var username: String

    enum CodingKeys: String, CodingKey {
        case locationName = "name"
        case cameraName = "name"
        case username = "name"
        
    }
    
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // Decode location
        let locationContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .location)
        location = try locationContainer.decodeIfPresent(String.self, forKey: .locationName)
        // ...
        
        // Decode camera details
        let exifContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .exif)
        camera = try exifContainer.decode(String.self, forKey: .cameraName)
        
        // Decode user data
        let userContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
        username = try userContainer.decode(String.self, forKey: .username)
    }
    
}

请问是否可以在不创建CodingKeys2这类额外枚举的情况下,使用单个CodingKeys解析这类含重复键的JSON?


解决方案

完全可以不用额外的CodingKeys枚举,以下两种实现方式都能解决问题:

方式一:利用动态CodingKey直接解析

只在CodingKeys里定义外层的键,解析嵌套容器时使用通用的动态编码键获取name字段,无需重复枚举:

struct WallpaperResponse: Decodable {
    var location: String?
    var camera: String?
    var username: String

    enum CodingKeys: String, CodingKey {
        case exif, location, user
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // 解析location的name
        if let locationContainer = try container.nestedContainerIfPresent(keyedBy: DynamicCodingKey.self, forKey: .location) {
            location = try locationContainer.decode(String.self, forKey: DynamicCodingKey(stringValue: "name")!)
        }
        
        // 解析exif的name
        let exifContainer = try container.nestedContainer(keyedBy: DynamicCodingKey.self, forKey: .exif)
        camera = try exifContainer.decode(String.self, forKey: DynamicCodingKey(stringValue: "name")!)
        
        // 解析user的name
        let userContainer = try container.nestedContainer(keyedBy: DynamicCodingKey.self, forKey: .user)
        username = try userContainer.decode(String.self, forKey: DynamicCodingKey(stringValue: "name")!)
    }
}

// 通用动态编码键,无需额外枚举
struct DynamicCodingKey: CodingKey {
    let stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    let intValue: Int?
    init?(intValue: Int) {
        self.intValue = intValue
        self.stringValue = String(intValue)
    }
}

方式二:嵌套小型结构体(更简洁)

通过定义私有嵌套结构体匹配JSON子结构,利用Decodable自动解码能力,再通过计算属性映射到外层属性:

struct WallpaperResponse: Decodable {
    // 计算属性映射嵌套结构体的name值
    var location: String? { locationInfo?.name }
    var camera: String? { exif?.name }
    var username: String { user.name }

    // 私有嵌套结构体,自动匹配JSON子结构
    private struct Exif: Decodable { let name: String }
    private struct Location: Decodable { let name: String }
    private struct User: Decodable { let name: String }

    private let exif: Exif?
    private let locationInfo: Location?
    private let user: User

    enum CodingKeys: String, CodingKey {
        case exif
        case locationInfo = "location"
        case user
    }
}

这种方式完全无需自定义解码逻辑,代码可读性和维护性更高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 00:42:47