Swift JSON编解码:UUID为键的空字典被编码为数组致解码失败如何解决?
Swift JSONEncoder/Decoder 空字典编码异常问题
我在使用Swift的JSONEncoder/Decoder做JSON编解码时遇到问题:我的JSON里有一个以UUID为键的字典,当这个字典为空时,解码会报错:
Expected to decode Array
but found a dictionary instead.
分析后发现,Swift会根据字典键的类型对空字典生成不同的编码结果,下面的最小示例可以复现这个问题:
import Foundation typealias Key = UUID // or String struct Foo: Codable { let data: [Key: String] } let foo = Foo(data: [:]) let encodedData = try JSONEncoder().encode(foo) let foo2 = try JSONDecoder().decode(Foo.self, from: encodedData) print(String(decoding: encodedData, as: UTF8.self))
现象对比:
- 当
Key为UUID时,编码结果是:{"data":[]} - 当
Key为String时,编码结果是:{"data":{} }
我希望所有情况下编码结果都是{"data":{}},请问问题出在哪?
问题原因与解决方案
问题原因
JSON规范要求对象的键必须是字符串,Swift的默认编码逻辑中:
- 对于
String类型的键,完全符合JSON要求,空字典会被编码为空JSON对象{}; - 对于
UUID这类非字符串类型的键,默认会把字典转成[Key, Value]格式的数组来编码,空字典自然就变成了空数组[],这就导致了解码时的类型不匹配错误。
解决方案
要让UUID作为键的空字典也编码为空对象,有两种可行方案:
方案一:全局处理UUID键的字典编码
扩展KeyedEncodingContainer,强制把UUID键转成字符串后再编码为JSON对象:
extension KeyedEncodingContainer { mutating func encode<T>(_ value: [UUID: T], forKey key: Key) throws where T: Encodable { let stringKeyedDict = Dictionary(uniqueKeysWithValues: value.map { ($0.uuidString, $1) }) try encode(stringKeyedDict, forKey: key) } }
这样所有以UUID为键的字典,无论是否为空,都会被编码为JSON对象,空字典的结果就是{}。
方案二:针对特定结构体自定义编解码逻辑
如果不想全局修改UUID的编码行为,可以在目标结构体中自定义编解码方法,仅处理指定属性:
struct Foo: Codable { let data: [UUID: String] func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) // 将UUID键转换为字符串键的字典 let stringDict = data.mapKeys { $0.uuidString } try container.encode(stringDict, forKey: .data) } // 解码时对应将字符串键转回UUID init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let stringDict = try container.decode([String: String].self, forKey: .data) data = stringDict.compactMapKeys { UUID(uuidString: $0) } } enum CodingKeys: String, CodingKey { case data } }
这种方式仅对Foo结构体的data属性生效,同时保证了解码时的类型一致性。
内容的提问来源于stack exchange,提问作者Tobias Müller
相关产品推荐
相关产品推荐

