I have tried a handful of ways to try and get my models to populate from this Alamofire GET call. Not sure what I am missing. I will include the JSON that is coming over as "data" as well.
func fetchMeals(){
let headers:HTTPHeaders = [
"x-rapidapi-key": "XXXXXXXXX",
"x-rapidapi-host": "spoonacular-recipe-food-nutrition-v1.p.rapidapi.com"
]
let parameters = ["timeframe":"day", "targetCalories":"2000", "diet":"vegetarian"]
let request = AF.request("https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com/recipes/mealplans/generate", parameters: parameters, headers: headers).responseData { response in switch response.result {
case .success(let data):
print(JSON(data))
let recipes = try? JSONDecoder().decode(Recipes.self, from: data)
if let recipes = recipes {
print(recipes.items[0].day)
}
//debugPrint(json
//let recipes = try! JSONDecoder().decode([Recipes].self, from: data as? Any)
//print(recipes)
case .failure(let error):
print(error)
}
Here is what is returned as data:
{
"publishAsPublic" : true,
"items" : [
{
"slot" : 1,
"type" : "RECIPE",
"day" : 1,
"mealPlanId" : 0,
"value" : "{\"id\":1509199,\"imageType\":\"jpg\",\"title\":\"Our Favorite Zucchini Bread\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 1,
"mealPlanId" : 0,
"value" : "{\"id\":426849,\"imageType\":\"jpg\",\"title\":\"Quick Eggnog French Toast\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 1,
"mealPlanId" : 0,
"value" : "{\"id\":844348,\"imageType\":\"jpg\",\"title\":\"Tiramisu Cake\"}",
"position" : 0
},
{
"slot" : 1,
"type" : "RECIPE",
"day" : 2,
"mealPlanId" : 0,
"value" : "{\"id\":715569,\"imageType\":\"jpg\",\"title\":\"Strawberry Cheesecake Chocolate Crepes\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 2,
"mealPlanId" : 0,
"value" : "{\"id\":484174,\"imageType\":\"jpg\",\"title\":\"French Toast Waffles\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 2,
"mealPlanId" : 0,
"value" : "{\"id\":894915,\"imageType\":\"jpg\",\"title\":\"Paleo Lemon Bars [VIDEO]\"}",
"position" : 0
},
{
"slot" : 1,
"type" : "RECIPE",
"day" : 3,
"mealPlanId" : 0,
"value" : "{\"id\":514551,\"imageType\":\"jpg\",\"title\":\"Overnight French Toast Casserole: A perfect make-ahead breakfast\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 3,
"mealPlanId" : 0,
"value" : "{\"id\":88612,\"imageType\":\"png\",\"title\":\"Marc Vetri's Rigatoni with Swordfish, Tomato, and Eggplant Fries\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 3,
"mealPlanId" : 0,
"value" : "{\"id\":345768,\"imageType\":\"jpeg\",\"title\":\"The Neely's Caprese Tart\"}",
"position" : 0
},
{
"slot" : 1,
"type" : "RECIPE",
"day" : 4,
"mealPlanId" : 0,
"value" : "{\"id\":377285,\"imageType\":\"jpg\",\"title\":\"Blueberry Brunch Bake\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 4,
"mealPlanId" : 0,
"value" : "{\"id\":159846,\"imageType\":\"jpg\",\"title\":\"Best Eggnog\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 4,
"mealPlanId" : 0,
"value" : "{\"id\":633165,\"imageType\":\"jpg\",\"title\":\"Avocado Tomato & Mozzarella Panini\"}",
"position" : 0
},
{
"slot" : 1,
"type" : "RECIPE",
"day" : 5,
"mealPlanId" : 0,
"value" : "{\"id\":1096246,\"imageType\":\"jpg\",\"title\":\"Apple Cinnamon Quiche\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 5,
"mealPlanId" : 0,
"value" : "{\"id\":484238,\"imageType\":\"jpg\",\"title\":\"Spinach Scramble\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 5,
"mealPlanId" : 0,
"value" : "{\"id\":471864,\"imageType\":\"jpg\",\"title\":\"Teeny Lamothe's Pear & Goat Cheese Tart\"}",
"position" : 0
},
{
"slot" : 1,
"type" : "RECIPE",
"day" : 6,
"mealPlanId" : 0,
"value" : "{\"id\":1118497,\"imageType\":\"jpg\",\"title\":\"Strawberry Oatmeal\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 6,
"mealPlanId" : 0,
"value" : "{\"id\":1005954,\"imageType\":\"jpg\",\"title\":\"Simply Delicious\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 6,
"mealPlanId" : 0,
"value" : "{\"id\":555798,\"imageType\":\"jpg\",\"title\":\"Black Forest Cheesecake Trifles\"}",
"position" : 0
},
{
"slot" : 1,
"type" : "RECIPE",
"day" : 7,
"mealPlanId" : 0,
"value" : "{\"id\":525517,\"imageType\":\"jpeg\",\"title\":\"Slim & Healthy Ways to Cook Oatmeal for Breakfast\"}",
"position" : 0
},
{
"slot" : 2,
"type" : "RECIPE",
"day" : 7,
"mealPlanId" : 0,
"value" : "{\"id\":491182,\"imageType\":\"jpg\",\"title\":\"Cheesy Salsa Omelet\"}",
"position" : 0
},
{
"slot" : 3,
"type" : "RECIPE",
"day" : 7,
"mealPlanId" : 0,
"value" : "{\"id\":1130503,\"imageType\":\"jpg\",\"title\":\"Easy English Muffin Pizza\"}",
"position" : 0
}
],
"name" : "MealPlan 1607269962128"
}
}
Here are my models:
struct Value : Decodable {
let id : Int
let imageType :String
let title :String
}
struct Recipe : Decodable {
let type :String
let position :Int
let value :Value
let slot :Int
let mealPlanId :Int
let day :Int
}
struct Recipes : Decodable {
let publishAsPublic :Bool
let items: [Recipe]
let name :String
}
When I print the variable as above, it returns nil. Any help is appreciated.
First of all, never try?
the decoding process. This way you are missing critical information about the failure of the process.
The only issue with your model is that you are trying to map value
property as Value
type but it clearly is of String
type.
You can workaround this be passing in your JSONDecoder
, the nested decoder that you will use to decode value
string, using userInfo
:
extension CodingUserInfoKey {
static let nestedDecoder = CodingUserInfoKey(rawValue: "com.app.NestedDecoder")!
}
let decoder = JSONDecoder()
decoder.userInfo = [
.nestedDecoder: JSONDecoder()
]
do {
let decoded = try decoder.decode(Recipes.self, from: data)
print(decoded)
} catch {
print(error)
}
And then use that decoder in a custom implementation of init(from:)
initializer, like this:
enum RecipeDecodingError: Error {
case noNestedDecoder
}
struct Recipe: Decodable {
let type: String
let position: Int
let value: Value
let slot: Int
let mealPlanId: Int
let day: Int
enum CodingKeys: String, CodingKey {
case type
case position
case value
case slot
case mealPlanId
case day
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)
position = try container.decode(Int.self, forKey: .position)
let stringValue = try container.decode(String.self, forKey: .value)
guard let nestedDecoder = decoder.userInfo[.nestedDecoder] as? JSONDecoder else {
throw RecipeDecodingError.noNestedDecoder
}
value = try nestedDecoder.decode(Value.self, from: Data(stringValue.utf8))
slot = try container.decode(Int.self, forKey: .slot)
mealPlanId = try container.decode(Int.self, forKey: .mealPlanId)
day = try container.decode(Int.self, forKey: .day)
}
}