Alamofire 和 Codable 问题解析响应
Alamofire & Codable Issue Parsing Responses
我已经尝试了几种方法来尝试让我的模型从这个 Alamofire GET 调用中填充。不知道我错过了什么。我还将包括作为“数据”过来的 JSON。
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)
}
这是作为数据返回的内容:
{
"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"
}
}
这是我的模型:
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
}
当我打印上面的变量时,它 returns 为零。感谢任何帮助。
首先,从不try?
解码过程。这样你就错过了关于进程失败的关键信息。
您的模型的唯一问题是您试图将 value
属性 映射为 Value
类型,但它显然是 String
类型。
您可以通过传递 JSONDecoder
嵌套解码器来解决此问题,您将使用 userInfo
:
来解码 value
字符串
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)
}
然后在 init(from:)
初始值设定项的自定义实现中使用该解码器,如下所示:
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)
}
}
我已经尝试了几种方法来尝试让我的模型从这个 Alamofire GET 调用中填充。不知道我错过了什么。我还将包括作为“数据”过来的 JSON。
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)
}
这是作为数据返回的内容:
{
"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"
}
}
这是我的模型:
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
}
当我打印上面的变量时,它 returns 为零。感谢任何帮助。
首先,从不try?
解码过程。这样你就错过了关于进程失败的关键信息。
您的模型的唯一问题是您试图将 value
属性 映射为 Value
类型,但它显然是 String
类型。
您可以通过传递 JSONDecoder
嵌套解码器来解决此问题,您将使用 userInfo
:
value
字符串
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)
}
然后在 init(from:)
初始值设定项的自定义实现中使用该解码器,如下所示:
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)
}
}