如何在Swift解码失败时获取问题对象的原始JSON数据?
解码失败时获取损坏对象的原始JSON数据
问题背景
现有Swift解码逻辑中,通过OptionalObject结构体跳过数组中解码失败的元素,但需要捕获这些损坏元素的原始JSON数据用于问题排查。相关代码与场景如下:
现有结构与解码逻辑
后端返回数据结构:
struct DummyItem: Decodable { let number: Int let name: String } struct DummyResponse { let items: [DummyItem] let meta: String }
用于跳过失败元素的辅助结构体:
struct OptionalObject<Base: Decodable>: Decodable { let value: Base? init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { self.value = try container.decode(Base.self) } catch { self.value = nil } } }
响应解码实现:
extension DummyResponse: Decodable { enum CodingKeys: String, CodingKey { case items, meta } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let nullableItems = try container.decode([OptionalObject<DummyItem>].self, forKey: .items) self.items = nullableItems.compactMap({ $0.value }) self.meta = try container.decode(String.self, forKey: .meta) } }
当下游返回包含损坏元素的JSON时(如下示例),现有逻辑会跳过损坏项,但无法获取其原始JSON:
{ "items": [ { "number": 1, "name": "first" }, { "number": 2, "name": "second" }, { "number": 3, "name": null } ], "meta": "Metadata" }
解决方案
修改OptionalObject结构体,新增存储原始JSON的属性,并在解码失败的catch块中捕获当前元素的原始JSON数据。需借助通用的AnyCodable结构体处理任意类型的JSON元素:
1. 实现AnyCodable结构体
用于解码任意JSON类型,为后续序列化为原始数据提供支持:
struct AnyCodable: Decodable { let value: Any init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let intValue = try? container.decode(Int.self) { value = intValue } else if let doubleValue = try? container.decode(Double.self) { value = doubleValue } else if let stringValue = try? container.decode(String.self) { value = stringValue } else if let boolValue = try? container.decode(Bool.self) { value = boolValue } else if let arrayValue = try? container.decode([AnyCodable].self) { value = arrayValue.map { $0.value } } else if let dictValue = try? container.decode([String: AnyCodable].self) { value = dictValue.mapValues { $0.value } } else if container.decodeNil() { value = NSNull() } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "无法解码为任意JSON类型") } } }
2. 修改OptionalObject结构体
新增rawJSON属性存储损坏元素的原始JSON数据,并在catch块中完成捕获:
struct OptionalObject<Base: Decodable>: Decodable { let value: Base? let rawJSON: Data? // 存储损坏元素的原始JSON二进制数据 init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() do { // 解码成功时,value为元素实例,rawJSON置空 self.value = try container.decode(Base.self) self.rawJSON = nil } catch { // 解码失败时,用AnyCodable解码当前元素,再序列化为Data let anyCodable = try container.decode(AnyCodable.self) self.rawJSON = try JSONSerialization.data(withJSONObject: anyCodable.value) self.value = nil } } }
3. 提取损坏元素的原始JSON
在DummyResponse的解码逻辑中,可从nullableItems中筛选出损坏元素的原始JSON:
extension DummyResponse: Decodable { enum CodingKeys: String, CodingKey { case items, meta } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let nullableItems = try container.decode([OptionalObject<DummyItem>].self, forKey: .items) self.items = nullableItems.compactMap({ $0.value }) // 提取并处理损坏元素的原始JSON(转换为字符串示例) let corruptedItems = nullableItems .compactMap { item -> String? in guard let rawData = item.rawJSON else { return nil } return String(data: rawData, encoding: .utf8) } // 可根据需求打印或上报损坏元素 print("检测到损坏的元素:\(corruptedItems)") self.meta = try container.decode(String.self, forKey: .meta) } }
说明
AnyCodable支持解码所有标准JSON类型(数字、字符串、布尔值、数组、字典、null),确保能捕获任意格式的损坏元素。- 捕获的
rawJSON为Data类型,可根据需求转换为字符串或直接用于序列化操作。 - 该方案在不影响正常元素解码的前提下,完整保留了损坏元素的原始数据,便于后续问题排查。
内容的提问来源于stack exchange,提问作者Danylo Kurylo
相关产品推荐
相关产品推荐

