Swift中遍历从JSON初始化的OrderedDictionary问题排查
问题:SwiftUI遍历JSON菜单仅显示最后一项内容
这是之前相关提问的后续问题:尝试在SwiftUI中遍历JSON的menu字段,展示所有菜单选项和对应的过敏原,但当前ForEach循环仅能显示"Southwest Tofu"及其过敏原,无法展示全部内容。
相关代码与JSON片段
SwiftUI视图代码
struct TodaysMenuOptionsView: View { @Binding var menuItems: OrderedDictionary<String, [String]> var body: some View { ZStack(alignment: .leading) { ForEach(menuItems.keys.sorted(), id: \.self) { key in RoundedRectangle(cornerRadius: 10) .fill(Color("Menu Options Color")) .frame(height: 100) VStack(alignment: .leading) { Text(key) .font(.title3) .fontWeight(.semibold) if let value = menuItems[key] { HStack { ForEach(value, id: \.self) { item in Text(item) } } } } .padding() } .padding() } } }
JSON片段
{ "day": "Friday", "locationName": "Memorial Union Quad", "coordinate": [38.54141, -121.74845], "menu": [ "Soy Ciltrano Lime Chicken", ["🌾","🫛"], "Southwest Tofu", ["🫛","V"] ] }
Swift数据模型代码
import Foundation import OrderedCollections struct Menu : Codable, Hashable { var id: UUID { UUID() } let day: String let locationName: String let coordinate: [Double] let menu: OrderedDictionary<String, [String]> func getTodaysLocation(_ today: String)-> String{ if today == day{ return locationName } return "" } }
问题根源
JSON里的menu是交替的字符串和数组组成的混合数组,但数据模型直接将menu定义为OrderedDictionary<String, [String]>,默认JSON解码器无法自动转换这种结构,解码时会出现数据覆盖,最终只有最后一组菜名和过敏原被存入字典,导致视图仅显示这一项。
解决方案
为Menu结构体添加自定义解码逻辑,手动解析混合类型的menu数组,将其转换为OrderedDictionary<String, [String]>:
1. 修改数据模型,实现自定义解码
import Foundation import OrderedCollections struct Menu : Codable, Hashable { var id: UUID { UUID() } let day: String let locationName: String let coordinate: [Double] let menu: OrderedDictionary<String, [String]> func getTodaysLocation(_ today: String)-> String{ if today == day{ return locationName } return "" } // 定义编码键 enum CodingKeys: String, CodingKey { case day, locationName, coordinate, menu } // 自定义解码逻辑 init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) day = try container.decode(String.self, forKey: .day) locationName = try container.decode(String.self, forKey: .locationName) coordinate = try container.decode([Double].self, forKey: .coordinate) // 解析混合类型的menu数组 let menuArray = try container.decode([JSONValue].self, forKey: .menu) var menuDict = OrderedDictionary<String, [String]>() // 每两个元素为一组(菜名+过敏原),遍历数组 for index in stride(from: 0, to: menuArray.count, by: 2) { guard index + 1 < menuArray.count else { break } // 提取菜名字符串 guard let dishName = menuArray[index].stringValue else { continue } // 提取过敏原数组 guard let allergens = menuArray[index + 1].arrayValue?.compactMap({ $0.stringValue }) else { continue } menuDict[dishName] = allergens } menu = menuDict } } // 处理JSON混合类型的枚举 enum JSONValue: Codable { case string(String) case array([JSONValue]) // 提取字符串值 var stringValue: String? { guard case .string(let str) = self else { return nil } return str } // 提取数组值 var arrayValue: [JSONValue]? { guard case .array(let arr) = self else { return nil } return arr } // 自定义解码 init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let str = try? container.decode(String.self) { self = .string(str) } else if let arr = try? container.decode([JSONValue].self) { self = .array(arr) } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "不支持的JSON类型") } } // 自定义编码(如果需要编码回JSON) func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { case .string(let str): try container.encode(str) case .array(let arr): try container.encode(arr) } } }
2. 视图逻辑验证
你的SwiftUI视图代码本身无问题,只要menuItems能正确包含所有菜名与过敏原的键值对,ForEach就能遍历显示全部内容。
内容的提问来源于stack exchange,提问作者user28773992
相关产品推荐
相关产品推荐

