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)
    }
}