I'm trying to decode this JSON called Menu.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"]
}
}
]
This is how I'm modeling the data in 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 ""
}
}
And this is how I'm decoding the data:
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)")
}
}
}
But I get the following error: "Failed to decode Menu.json from bundle due to type mismatch – Expected to decode Array<Any> but found a dictionary instead."
Am I modeling the data correctly? Or is it an issue with the decode function in the Bundle?
I tried modifying the way I'm modeling the data, but I new to JSON, so I'm not totally sure if the issue is the way I modeled the JSON in Swift.
OrderedDictionary
(from swift-collections presumably) is expected to be decoded from a JSON like this:
[
"key1",
"value1",
"key2",
"value2"
]
This is why the error is saying that an Array<Any>
is expected.
JSONDecoder
does not guarantee that the keys of in KeyedDecodingContainer.allKeys
are ordered in the same way they appear in the JSON string. From a related issue in swift-collections,
OrderedDictionary
needs to guarantee that the order of its items will not change during serialization/deserialization. This is not guaranteed byCodable
's keyed containers -- therefore they aren't suitable for use byOrderedDictionary
.Note that
Codable
is a common denominator archival solution, primarily intended for use where you don't care about the precise details of the serialized format. It is the Swift version ofNSCoding
, or Python'spickle
.Codable
is emphatically not a flexible JSON encoder/decoder that's designed to interoperate with arbitrary JSON APIs.
So you either need to use a custom JSON parsing library (I don't know of any that preserves the order) instead of Codable
, or change the structure of your JSON to what the OrderedDictionary
expects:
"menu": [
"Penne Pasta w/ Bolognese",
["🌾","🐷"],
"Penne Pasta w/ Roasted Mushrooms",
["🌾","V"]
]