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.
2
Answers
The documentation of
OrderedCollection
describes which structure is expected for this type:I.e. if you want to use
OrderedCollection
, you have to use a different JSON structure:In general, you should not use a JSON object but an array for
menu
if the order of the items is important. According to the JSON specification, the order of the properties of an object should not matter.If you can’t control how the JSON gets structured:
Note that, as described above, there is the problem that the order of the JSON object properties should not matter.
So the following solution is not recommended if the order matters!
You can decode the JSON you have posted with the following object structure:
If you have control over the JSON structure:
There are several ways to structure or decode the data differently. Here is one alternative:
With the JSON having the following structure:
Testing your code:
I generally recommend writing a unit test for this type of problem / code, as this is the easiest way to test the functionality:
OrderedDictionary
(from swift-collections presumably) is expected to be decoded from a JSON like this:This is why the error is saying that an
Array<Any>
is expected.JSONDecoder
does not guarantee that the keys of inKeyedDecodingContainer.allKeys
are ordered in the same way they appear in the JSON string. From a related issue in swift-collections,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 theOrderedDictionary
expects: