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

Swift解析Menu.json报错:期望解码数组却发现字典的问题排查

解码Menu.json时的类型不匹配问题排查

问题背景

尝试解析名为Menu.json的JSON数据,结构如下:

[{
    "day": "Monday",
    "locationName": "Memorial Union Quad",
    "coordinate": [38.54141, -121.74845],
    "menu": {
        "Penne Pasta w/ Bolognese" : ["🌾","🐷"],
        "Penne Pasta w/ Roasted Mushrooms" : ["🌾","V"]
    }
},{
    "day": "Tuesday",
    "locationName": "International Center",
    "coordinate": [38.54540, -121.75494],
    "menu": {
        "Beef & Lamb Gyro" : ["🌾","🫛", "🥛"],
        "Edamame Hummus w/ Veggies" : ["🫛","🥛", "SE", "VE"]
    }
},{
    "day": "Wednesday",
    "locationName": "Storer Hall",
    "coordinate": [38.54114, -121.75461],
    "menu": {
        "Seafood Salad Tostada" : ["🥚","🦐", "🫛", "🐟"],
        "Southwest Black Bean Tostada" : ["V"]
    }
},{
    "day": "Thursday",
    "locationName": "Orchard Park",
    "coordinate": [38.544828, -121.765170],
    "menu": {
        "Teriyaki Beef w/ Stir Fry Noodles" : ["🌾","🫛", "SE"],
        "Teriyaki Tofu w/ Veggie Stir Fry" : ["🌾","🫛", "SE","V"]
    }
},{
    "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 ""
    }
}

使用Bundle扩展的decode函数解码:

import Foundation

extension Bundle {
    func decode(_ file: String) -> [Menu] {
        guard let url = self.url(forResource: file, withExtension: nil) else {
            fatalError("Failed to locate \(file) in bundle.")
        }

        guard let data = try? Data(contentsOf: url) else {
            fatalError("Failed to load \(file) from bundle.")
        }

        let decoder = JSONDecoder()

        do {
            return try decoder.decode([Menu].self, from: data)
        } catch DecodingError.keyNotFound(let key, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing key '\(key.stringValue)' – \(context.debugDescription)")
        } catch DecodingError.typeMismatch(_, let context) {
            fatalError("Failed to decode \(file) from bundle due to type mismatch – \(context.debugDescription)")
        } catch DecodingError.valueNotFound(let type, let context) {
            fatalError("Failed to decode \(file) from bundle due to missing \(type) value – \(context.debugDescription)")
        } catch DecodingError.dataCorrupted(_) {
            fatalError("Failed to decode \(file) from bundle because it appears to be invalid JSON.")
        } catch {
            fatalError("Failed to decode \(file) from bundle: \(error.localizedDescription)")
        }
    }
}

运行后出现错误:

"Failed to decode Menu.json from bundle due to type mismatch – Expected to decode Array but found a dictionary instead."

错误原因

问题出在OrderedDictionary<String, [String]>的默认Codable实现上。OrderedDictionary默认会将自身编码/解码为键值对数组(格式类似[{"key": "...", "value": "..."}]),但你的JSON中menu字段是标准的JSON对象(键值对直接嵌套),两者格式不匹配,导致解码器期望数组却收到字典,触发类型不匹配错误。

解决方案

方案1:改用普通Dictionary(无需保持顺序)

如果不需要保留菜单条目的顺序,直接将menu字段类型改为Dictionary<String, [String]>即可,Swift标准Dictionary默认支持JSON对象的解码:

import Foundation

struct Menu : Codable, Hashable {
    var id: UUID { UUID() }
    let day: String
    let locationName: String
    let coordinate: [Double]
    // 替换OrderedDictionary为普通Dictionary
    let menu: [String: [String]]
    
    func getTodaysLocation(_ today: String)-> String{
        if today == day{
            return locationName
        }
        return ""
    }
}

方案2:自定义OrderedDictionary的Codable实现(需保持顺序)

如果必须保留菜单条目的顺序,需要为OrderedDictionary扩展自定义的Codable逻辑,使其能从JSON对象解码:

import Foundation
import OrderedCollections

// 扩展OrderedDictionary,支持从JSON对象解码/编码
extension OrderedDictionary: Codable where Key: CodingKey & Hashable, Value: Codable {
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)
        self.init()
        for key in container.allKeys {
            let value = try container.decode(Value.self, forKey: key)
            self.updateValue(value, forKey: key)
        }
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: Key.self)
        for (key, value) in self {
            try container.encode(value, forKey: key)
        }
    }
}

// 原模型保持不变
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 ""
    }
}

注意:此扩展要求Key类型遵循CodingKey,String默认符合该协议,所以OrderedDictionary<String, [String]>可以正常使用。

验证修改

修改完成后,重新调用Bundle.decode("Menu.json")即可正常解码JSON数据。

内容的提问来源于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:13:16