SwiftUI中字典类型JSON解码及列表展示问题求解
问题定位
你的代码共有4处核心错误:
- 多余的根结构体:接口返回的根JSON本身就是
[String: Crystal]格式的字典,不存在外层的crystals字段,你定义的Response结构体完全多余,直接把根对象解码为[String: Crystal]即可。 - 字段名拼写不匹配:
Crystal结构体里定义的属性是color,但接口返回的JSON键是英式拼写colour,未做映射的情况下会触发键不存在的解码错误。 - 字段类型不匹配:你将
metaphysical属性定义为[String]数组类型,但接口返回数据中clear quartz对应的metaphysical是单个字符串,类型不一致会直接导致解码失败。 - 列表遍历逻辑错误:SwiftUI的
List要求遍历的元素遵守Identifiable协议,直接遍历字典得到的是(key: String, value: Crystal)元组,不满足协议要求,无法正常渲染。
修正方案
首先调整Crystal结构体,自定义解码逻辑兼容metaphysical的两种返回类型,同时排除本地生成的id属性的解码逻辑,删掉多余的Response结构体,调整数据解码和列表渲染逻辑即可,完整可运行代码如下:
struct Crystal: Codable, Identifiable { let id = UUID() let composition, formation, colour: String let metaphysical: [String] // 仅解码JSON中存在的字段,排除本地生成的id enum CodingKeys: String, CodingKey { case composition, formation, colour, metaphysical } // 自定义解码,兼容metaphysical字段单字符串/字符串数组两种返回格式 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) composition = try container.decode(String.self, forKey: .composition) formation = try container.decode(String.self, forKey: .formation) colour = try container.decode(String.self, forKey: .colour) if let singleStr = try? container.decode(String.self, forKey: .metaphysical) { metaphysical = [singleStr] } else { metaphysical = try container.decode([String].self, forKey: .metaphysical) } } } struct ContentView: View { @State private var crystals: [String: Crystal] = [:] var body: some View { // 字典转键值对数组遍历,用唯一的水晶名作为id List(Array(crystals), id: \.key) { name, crystal in VStack(alignment: .leading, spacing: 4) { Text(name) .font(.headline) Text("成分:\(crystal.composition)") .font(.subheadline) Text("颜色:\(crystal.colour)") .font(.subheadline) // 其余属性可按需自行渲染 } .padding(.vertical, 8) }.onAppear(perform: loadData) } func loadData() { guard let url = URL(string: "https://lit-castle-74820.herokuapp.com/api/crystals") else { return } URLSession.shared.dataTask(with: url) { data, _, error in guard let data = data, error == nil else { print("请求失败:\(error?.localizedDescription ?? "未知错误")") return } do { // 直接解码为字典类型,不需要套Response结构体 let decodedCrystals = try JSONDecoder().decode([String: Crystal].self, from: data) DispatchQueue.main.async { self.crystals = decodedCrystals } } catch { print("JSON解码失败: \(error)") } }.resume() } }
补充说明
- 字典类型的JSON解码逻辑和数组完全一致,只要目标类型和JSON结构匹配,
JSONDecoder可以直接解码为Swift原生字典,不需要额外包裹根结构体。 - 碰到同个字段可能返回多种类型的不规范接口,写自定义
init(from:)方法做兼容即可,无需调整接口逻辑。 - 遍历字典时将其转为键值对数组,指定唯一的字典key作为List的id参数,即可满足List的遍历要求,不需要额外让元组遵守协议。
内容的提问来源于stack exchange,提问作者josh
相关产品推荐
相关产品推荐

