如何在Swift中使用自定义解码器init和递增键解析JSON

问题描述

我有一些饭食json,我想将其转换为MealData结构

let randomMealJson = """
{
    "meals": [{
        "idMeal": "52812","strMeal": "Beef Brisket Pot Roast","strDrinkAlternate": null,"strCategory": "Beef","strArea": "American","strInstructions": "Meal instructions","strMealThumb": "https://www.themealdb.com/images/media/meals/ursuup1487348423.jpg","strTags": "Meat","strYoutube": "https://www.youtube.com/watch?v=gh48wM6bPWQ","strIngredient1": "Beef Brisket","strIngredient2": "Salt","strIngredient3": "Onion","strIngredient4": "Garlic","strIngredient5": "Thyme","strIngredient6": "Rosemary","strIngredient7": "Bay Leaves","strIngredient8": "beef stock","strIngredient9": "Carrots","strIngredient10": "Mustard","strIngredient11": "Potatoes","strIngredient12": null,"strIngredient13": null,"strIngredient14": null,"strIngredient15": null,"strIngredient16": null,"strIngredient17": null,"strIngredient18": null,"strIngredient19": null,"strIngredient20": null,"strMeasure1": "4-5 pound","strMeasure2": "Dash","strMeasure3": "3","strMeasure4": "5 cloves","strMeasure5": "1 Sprig","strMeasure6": "1 sprig ","strMeasure7": "4","strMeasure8": "2 cups","strMeasure9": "3 Large","strMeasure10": "1 Tbsp","strMeasure11": "4 Mashed","strMeasure12": "","strMeasure13": "","strMeasure14": "","strMeasure15": "","strMeasure16": "","strMeasure17": "","strMeasure18": "","strMeasure19": "","strMeasure20": "","strSource": "http://www.simplyrecipes.com/recipes/beef_brisket_pot_roast/","dateModified": null
    }]
}
"""

这是我要制作的结构...

struct MealData: Decodable {
    let meals: [Meal]
}

struct Meal: Decodable {
    let items: [Item]
}

extension Meal {
    struct Item: Decodable {
        let ingredient: String
        let measure: String
    }
}

extension Meal {
    init(from decoder: Decoder) throws {
//        var items: [Item] = (0...20).map { num in      
//           what to do here?
//        }
    }
}

我想仔细研究一下这些要素和措施,并根据它们创建一个Meal.Item,但是我不知道如何实现这一目标。我在代码中指出的方式有可能吗?

解决方法

我将分两步执行此操作。首先解码成字典

struct MealData: Decodable {
    let meals: [[String: String?]]
}

然后我将在单独的步骤中转换为另一种类型

struct Recipie {
    var ingredients: [Ingredient]
}

struct Ingredient {
    let ingredient: String
    let measure: String
}

这是完整的代码

do {
    let result = try JSONDecoder().decode(MealData.self,from: randomMealJson)

    var recipies = [Recipie]()
    for meal in result.meals {
        var recipie = Recipie(ingredients: [])
        var index = 1
        while true {
            guard let ingredientValue = meal["strIngredient\(index)"],let measureValue = meal["strMeasure\(index)"],let ingredient = ingredientValue,let measure = measureValue,!measure.isEmpty
            else { break }

            recipie.ingredients.append(Ingredient(ingredient: ingredient,measure: measure))
            recipies.append(recipie)
            index += 1
        }
    }
} catch {
    print(error)
}
,

@ joakim-danielson的回答。您可以在init(from:)初始化程序中执行相同的操作,就像您的初衷一样。

只需将每个Meal的成分和度量解码为[String: String?],然后编写自定义的Item s:

extension Meal {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        let mealDict = try container.decode([String: String?].self)
        var index = 1
        var items = [Item]()
        while
            let ingredient = mealDict["strIngredient\(index)"] as? String,let measure = mealDict["strMeasure\(index)"] as? String,!measure.isEmpty
        {
            items.append(Item(ingredient: ingredient,measure: measure))
            index += 1
        }
        self.items = items
    }
}