Learn Courses My Dashboard

Sum nested JSON data swiftUI

Hi Guys,

I am trying to create a function that will sum json items from within separate nodes/ nested arrays, specifically the components.totalCalories data in the sample below. It is my understanding that I need to first get the data I want into a flat array and then use the .reduce modifier. I have tried the .map and .flatMap modifiers for this without any success so far. I have also tried using the .reduce within a ForEach loop in my view code to achieve the same result but again without any success.

Any help would be greatly appreciated, thanks

[{

"catagory": "Full Meals",
"items": [{
    "name": "Lassagne",
    "totalCalories": 390,
    "isFavourite": true,
    "addedToday": 1,
    "components": [{
            "name": "Pasta",
            "varietyName": "Sheets",
            "varietyCalories": 3,
            "totalCalories": 90,
            "quantity": 30,
            "unit": "g"

        },
        {
            "name": "Beef",
            "varietyName": "Mince",
            "varietyCalories": 10,
            "totalCalories": 300,
            "quantity": 30,
            "unit": "g"
        }
    ]

}]

},

One potential solution for this could be drilling down through the JSON with loops.

static func totalCalories(data:[TestData]) -> Int{

    var calories = 0
    
    for i in data{
        
        for t in i.items{
            
            for c in t.components{
                
                calories += c.totalCalories
                
            }
            
        }
    }
    return calories
    
}

My question is why would you do this if you already have the totalCalories of the ingredients with in the parent object?

Thanks Lowgy, I’m get the ’ Type “” does not conform to protocol “Sequence” ’ error message, I will have a play around t see if I can overcome that.

The reason for needing this function is so that the totalCalories in the parent object will change its value depending on the user interactions. The user will be able to modify the quantity and variety of the components which will alter the totalCalories value.

Making several assumptions about how your JSON is being decoded, reduce would work something like this:

struct Menu: Decodable {
    let catagory: String
    let items: [Recipe]
}

struct Recipe: Decodable, Identifiable {
    var id: String { name }
    let name: String
    let totalCalories: Int
    let isFavourite: Bool
    let addedToday: Int
    let components: [RecipeComponent]
}

struct RecipeComponent: Decodable {
    let name: String
    let varietyName: String
    let varietyCalories: Int
    let totalCalories: Int
    let quantity: Int
    let unit: String
}

struct RecipeSumView: View {
    @State private var recipes: [Recipe] = []
    var totalCalories: Int {
        return recipes.reduce(0) { cals, currentItem in
            cals + currentItem.totalCalories
        }
    }
    
    var body: some View {
        VStack(spacing: 40) {
            Text("Total Calories = \(totalCalories)")
            VStack(spacing: 20) {
                ForEach(recipes) { recipe in
                    Text(recipe.name).font(.headline)
                    Text(String(recipe.totalCalories))
                }
            }
        }
        .onAppear { loadJSON() }
    }
    
    func loadJSON() {
        let decoder = JSONDecoder()
        do {
            let menu = try decoder.decode([Menu].self, from: menuJSON)
            recipes = menu[0].items
        } catch {
            print(error)
        }
    }
}

Note the totalCalories computed property.

1 Like

@roosterboy thanks that works great!

The only change I had to make was to change the computed var to;
@State private var recipes = Recipe
var totalCalories: Int {
return recipes.RecipeComponent.reduce(0) { cals, currentItem in
cals + currentItem.totalCalories
}
}