Swift:如何在JSONDecoder解码Struct时预计算关联字段?
实时计算优化:解码阶段完成关联数据计算
能否在解码过程中完成计算?
完全可以。因为Structure作为顶层解码对象,在自定义init(from:)方法中,我们可以先解析出所有rooms和lists的原始数据,再基于两者的roomName关联关系完成计算,最终把结果赋值给List的calculated字段。这样就能把计算逻辑提前到数据加载阶段,避免运行时重复计算。
实现代码
我们需要调整Structure的解码逻辑,同时给List增加一个内部的原始解码结构体,用于先读取roomName,再完成计算:
struct Structure: Decodable { static func withJSON(_ fileName: String) -> Structure? { guard let url = Bundle.main.url(forResource: fileName, withExtension: "json", subdirectory: "Structures") else { return nil } guard let data = try? Data(contentsOf: url) else { return nil } do { return try JSONDecoder().decode(Structure.self, from: data) } catch { print("Unexpected error decoding Structure: \(error).") return nil } } private enum CodingKeys: String, CodingKey { case id, rooms, lists } let id: Int let rooms: [Room] let lists: [List] init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) rooms = try container.decode([Room].self, forKey: .rooms) // 将rooms转成字典,通过roomName快速查找对应Room let roomLookup = Dictionary(uniqueKeysWithValues: rooms.map { ($0.roomName, $0) }) // 先解码原始list数据,再计算calculated字段 let rawLists = try container.decode([List.RawList].self, forKey: .lists) lists = rawLists.map { rawList in guard let matchedRoom = roomLookup[rawList.roomName] else { // 处理找不到对应房间的情况,可根据业务调整 return List(roomName: rawList.roomName, calculated: nil) } // 计算维度总和减去 doors*2 let dimensionTotal = matchedRoom.dimension.reduce(0, +) let calculatedValue = dimensionTotal - Double(matchedRoom.doors * 2) return List(roomName: rawList.roomName, calculated: calculatedValue) } } } extension Structure { struct Room: Decodable { let roomName: String let dimension: [Double] let doors: Int // 默认Decodable即可解析,无需自定义init private enum CodingKeys: String, CodingKey { case roomName, dimension, doors } } } extension Structure { struct List { let roomName: String var calculated: Double? // 内部结构体,用于解码JSON中的原始list数据 fileprivate struct RawList: Decodable { let roomName: String private enum CodingKeys: String, CodingKey { case roomName } } } }
解码时数据暂不可用的处理方案
如果解码时出现数据不匹配(比如lists中的roomName在rooms中不存在),可以根据业务需求选择以下处理方式:
- 默认值填充:给
calculated设置默认值(如0),简化后续业务逻辑:return List(roomName: rawList.roomName, calculated: 0) - 抛出解码错误:确保数据完整性,直接抛出错误终止解码:
guard let matchedRoom = roomLookup[rawList.roomName] else { throw DecodingError.dataCorruptedError( forKey: CodingKeys.lists, in: container, debugDescription: "Room '\(rawList.roomName)' not found in rooms array" ) } - 保留可选值:让
calculated保持Double?类型,后续业务中再处理nil的场景
方案优势
- 计算逻辑提前到解码阶段,避免运行时重复计算,提升App性能
- 数据加载完成后直接可用计算结果,无需额外处理
- 关联逻辑集中在
Structure的解码方法中,代码结构更清晰
内容的提问来源于stack exchange,提问作者Symphonic eMotions
相关产品推荐
相关产品推荐

