You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 18:01:08