Swift中如何对含未知/可变键的字典进行Codable编码?
问题翻译
我需要向服务器发送如下格式的数据:
struct Event: Codable { let title: String let params: [String:Any]? // 这里不被允许 }
比如这些事件可以是以下任意一种形式:
let event = Event(title: "cat was pet", params:["age": 7.2]) let event = Event(title: "cat purred", params:["time": 9]) let event = Event(title: "cat drank", params:["water": "salty", "amount": 3.3])
我需要给事件添加任意数量的键值对作为params,请问能不能通过Codable实现?如果不行,该如何将params编码为JSON字符串?
问题解答
直接用[String:Any]?作为Codable结构体的属性没法实现自动编码解码,因为Any类型不遵循Codable协议。不过有几种实用的解决办法:
方案一:替换成[String:Codable]类型
如果你的params里的所有值都是JSON支持的基础类型(比如字符串、数字、布尔值),或者本身就遵循Codable,直接把属性类型改成[String:Codable]?就行,结构体可以自动实现Codable:
struct Event: Codable { let title: String let params: [String:Codable]? }
这是最简单的方案,能覆盖绝大多数场景,毕竟服务器接收的JSON数据本来就是这些类型。
方案二:手动实现Codable的编码解码逻辑
如果一定要保留[String:Any]?的类型,可以自己写encode(to:)和init(from:)方法,手动处理params的编码:
struct Event: Codable { let title: String let params: [String:Any]? func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(title, forKey: .title) guard let params = params else { return } // 先把params转成JSON数据,再解码成可编码的字典 let paramsData = try JSONSerialization.data(withJSONObject: params) let codableParams = try JSONDecoder().decode([String:Codable].self, from: paramsData) try container.encode(codableParams, forKey: .params) } // 如果需要解码的话,还要实现init(from:)方法 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) title = try container.decode(String.self, forKey: .title) if let codableParams = try container.decodeIfPresent([String:Codable].self, forKey: .params) { // 把Codable字典转回[String:Any] let data = try JSONEncoder().encode(codableParams) params = try JSONSerialization.jsonObject(with: data) as? [String:Any] } else { params = nil } } enum CodingKeys: String, CodingKey { case title, params } }
注意这里params里的内容必须是JSON支持的类型,不然JSONSerialization会报错。
方案三:把params编码成JSON字符串
如果服务器要求params字段是一个JSON格式的字符串(而非JSON对象),可以先把[String:Any]转成JSON字符串,再存入结构体:
struct Event: Codable { let title: String let params: String? // 便捷初始化方法,接收[String:Any]? init(title: String, params: [String:Any]?) throws { self.title = title guard let params = params else { self.params = nil return } let jsonData = try JSONSerialization.data(withJSONObject: params) guard let jsonString = String(data: jsonData, encoding: .utf8) else { throw NSError(domain: "EventError", code: 0, userInfo: [NSLocalizedDescriptionKey: "无法将params转为JSON字符串"]) } self.params = jsonString } }
使用的时候直接调用这个初始化方法即可:
do { let event = try Event(title: "cat was pet", params: ["age": 7.2]) // 后续可以正常把Event编码成JSON发送给服务器 } catch { print("初始化失败:\(error)") }
内容的提问来源于stack exchange,提问作者soleil
相关产品推荐
相关产品推荐

