Swift中如何将嵌套JSON结构解码为Data类型延迟存储
Swift中如何将嵌套JSON结构解码为Data类型延迟存储
嘿,这个需求我之前也碰到过,其实不难解决——核心就是把嵌套的JSON先转成Data存起来,等需要的时候再解码成具体模型。下面给你两种实用的实现方式,选哪种看你场景:
方案一:给Data扩展全局的Codable适配
默认情况下,Data的Codable实现是用来处理base64编码数据的,所以我们得给它写个自定义的解码/编码逻辑,让它能直接把JSON对象转成Data存储。
这里需要一个辅助类型AnyCodable来处理任意结构的JSON(因为JSON的类型不确定,可能是字典、数组或者基础类型),代码如下:
struct Message: Codable { let id: Int let data: Data } // 辅助类型:处理任意JSON类型的Codable适配 struct AnyCodable: Codable { let value: Any init(_ value: Any) { self.value = value } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() // 依次尝试解码成常见的JSON类型 if let bool = try? container.decode(Bool.self) { value = bool } else if let int = try? container.decode(Int.self) { value = int } else if let double = try? container.decode(Double.self) { value = double } else if let string = try? container.decode(String.self) { value = string } else if let array = try? container.decode([AnyCodable].self) { value = array.map { $0.value } } else if let dict = try? container.decode([String: AnyCodable].self) { value = dict.mapValues { $0.value } } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "无法识别的JSON类型") } } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() // 根据value的实际类型编码回JSON switch value { case let bool as Bool: try container.encode(bool) case let int as Int: try container.encode(int) case let double as Double: try container.encode(double) case let string as String: try container.encode(string) case let array as [Any]: try container.encode(array.map(AnyCodable.init)) case let dict as [String: Any]: try container.encode(dict.mapValues(AnyCodable.init)) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "无法编码的JSON类型")) } } } // 给Data扩展Codable,利用AnyCodable实现JSON转Data extension Data: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let jsonObj = try container.decode(AnyCodable.self) self = try JSONSerialization.data(withJSONObject: jsonObj.value, options: []) } public func encode(to encoder: Encoder) throws { let jsonObj = try JSONSerialization.jsonObject(with: self, options: []) var container = encoder.singleValueContainer() try container.encode(AnyCodable(jsonObj)) } }
这个方案的好处是全局生效,所有需要把JSON转成Data存储的地方都能用。解码后Message.data里就是原始的JSON二进制数据,以后需要的时候直接用JSONDecoder解码成具体模型就行。
方案二:仅给Message自定义解码逻辑
如果你不想修改全局的Data行为,也可以只在Message的自定义初始化方法里处理data字段的解码,这样更针对性:
struct Message: Codable { let id: Int let data: Data // 自定义解码逻辑 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.id = try container.decode(Int.self, forKey: .id) // 先把data对应的JSON转成AnyCodable,再转成Data let jsonObj = try container.decode(AnyCodable.self, forKey: .data) self.data = try JSONSerialization.data(withJSONObject: jsonObj.value, options: []) } // 自定义编码逻辑 func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) // 把Data转成JSON对象再编码回去 let jsonObj = try JSONSerialization.jsonObject(with: data, options: []) try container.encode(AnyCodable(jsonObj), forKey: .data) } enum CodingKeys: String, CodingKey { case id, data } } // 同样需要这个AnyCodable辅助类型(和方案一的一样) struct AnyCodable: Codable { let value: Any init(_ value: Any) { self.value = value } public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let bool = try? container.decode(Bool.self) { value = bool } else if let int = try? container.decode(Int.self) { value = int } else if let double = try? container.decode(Double.self) { value = double } else if let string = try? container.decode(String.self) { value = string } else if let array = try? container.decode([AnyCodable].self) { value = array.map { $0.value } } else if let dict = try? container.decode([String: AnyCodable].self) { value = dict.mapValues { $0.value } } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "无法识别的JSON类型") } } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch value { case let bool as Bool: try container.encode(bool) case let int as Int: try container.encode(int) case let double as Double: try container.encode(double) case let string as String: try container.encode(string) case let array as [Any]: try container.encode(array.map(AnyCodable.init)) case let dict as [String: Any]: try container.encode(dict.mapValues(AnyCodable.init)) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "无法编码的JSON类型")) } } }
这个方案的优势是不会影响其他地方的Data解码,只针对Message生效,更灵活。
使用示例
不管用哪种方案,解码后的Message都能正常存储data,之后需要解码的时候直接用JSONDecoder就行:
// 示例JSON数据 let jsonStr = """ { "id": 3, "data": { "name": "Bob", "age": 32 } } """ guard let jsonData = jsonStr.data(using: .utf8) else { fatalError("JSON转换失败") } do { // 解码Message let message = try JSONDecoder().decode(Message.self, from: jsonData) print("解码得到ID:\(message.id)") // 后续需要解码data时,定义对应的模型 struct User: Codable { let name: String let age: Int } let user = try JSONDecoder().decode(User.self, from: message.data) print("用户姓名:\(user.name),年龄:\(user.age)") } catch { print("解码错误:\(error)") }
总结
- 如果你的项目中有多处需要把JSON转成
Data存储,选方案一,全局扩展更方便; - 如果只是
Message这个结构体需要这个功能,选方案二,避免全局修改带来的潜在问题。
两种方案都能完美实现“延迟解码”的需求,先把原始JSON数据存成Data,等需要的时候再解码成具体模型。
备注:内容来源于stack exchange,提问作者wheatset
相关产品推荐
相关产品推荐

