Swift中如何用全局CodingKeys简化结构体JSON解码
我有如下JSON字符串:
let jsonStringBird = """ { "identifier": "0001ABX", "name": "Doroty", "species_name": "Gorrion", } """ let jsonStringPlane = """ { "identifier": "PL1803", "name": "Boing 777", "number_sits_in_plane": 8, } """
同时有大量如下示例结构体:
struct Bird: Decodable { var identifier: String var name: String var species: String } struct Plane: Decodable { var identifier: String var name: String var sits: String }
我希望在不修改这些结构体的前提下完成解码(避免重写init(from:)方法,也避免添加私有CodingKeys枚举)。我设想使用如下全局CodingKey枚举,但不知道如何让JSONDecoder为所有结构体使用该CodingKey(仅知晓在init(from:)中使用的方法):
enum MyCodingKeys: String, CodingKey { case identifier case name case species = "species_name" case sits = "number_sits_in_plane" }
核心需求是尽量简化大量结构体的实现。
说明
实际项目中我使用的是CBORCoding,映射的是[Int, AnyCodable]而非[String, AnyCodable]。因此需基于JSONDecoder和CodingKeys实现,以便后续迁移至CBORCoding。
要在不修改原有结构体的前提下实现全局键映射,你可以通过自定义JSONDecoder的userInfo,结合KeyedDecodingContainer扩展来复用全局CodingKey,具体步骤如下:
1. 定义全局键映射的标识
先定义一个userInfo的专属key,用来传递全局CodingKey的类型:
extension CodingUserInfoKey { static let globalCodingKeysType = CodingUserInfoKey(rawValue: "com.yourdomain.globalCodingKeysType")! }
2. 扩展KeyedDecodingContainer,实现全局键优先解码
给KeyedDecodingContainer添加通用扩展,优先尝试使用全局CodingKey进行解码,失败则回退到默认逻辑:
extension KeyedDecodingContainer where Key == CodingKey { func decode<T: Decodable>(_ type: T.Type, forKey key: CodingKey) throws -> T { // 从userInfo中获取全局CodingKey类型 guard let globalKeysType = userInfo[.globalCodingKeysType] as? Any.Type, let globalKey = (globalKeysType as? CodingKey.Type)?.init(stringValue: key.stringValue) else { return try decode(type, forKey: key) } // 优先用全局键解码,找不到则回退到原键 do { return try decode(type, forKey: globalKey) } catch DecodingError.keyNotFound { return try decode(type, forKey: key) } } }
3. 配置解码器并完成解码
使用时,给JSONDecoder的userInfo设置全局CodingKey类型,直接解码即可:
let decoder = JSONDecoder() // 绑定全局CodingKey类型 decoder.userInfo[.globalCodingKeysType] = MyCodingKeys.self // 解码Bird let birdData = jsonStringBird.data(using: .utf8)! let bird = try decoder.decode(Bird.self, from: birdData) // 解码Plane let planeData = jsonStringPlane.data(using: .utf8)! let plane = try decoder.decode(Plane.self, from: planeData)
适配CBORCoding的注意事项
由于你实际使用的CBOR以Int为键,只需将全局CodingKey改为Int类型枚举,上述逻辑完全通用:
enum MyCBORCodingKeys: Int, CodingKey { case identifier = 1 case name = 2 case species = 3 case sits = 4 }
后续迁移时,只需替换JSONDecoder为CBOR解码器,设置对应的全局CodingKey类型即可,无需修改原有结构体。
内容的提问来源于stack exchange,提问作者Gerardo

