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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 15:14:52