WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

Trying to add new elements to my API Struct, but not working.

Forums > SwiftUI

Hey, I am trying to add new information I added to my JSONs to my Struct in SwiftUI but every time I do it then comes with an error that the struct doesn't conform to codable but when I remove it, it works? Here is the struct:

struct Recipe: Identifiable, Codable {
    let id: Int
    let name: String
    let creator: String
    let serves: Int
    let ingredients: [Ingredient]
    let methods: [Method]
    let imageURL: URL

    enum CodingKeys: String, CodingKey {
        case id, name, creator, serves, ingredients
        case methods = "method"
        case imageURL = "imageurl"
    }
}

struct Ingredient: Codable {
    let name: String
    let quantity: Double
    let measurement: String

}

struct Method: Codable {
    let step: Int
    let text: String
}

enum RecipeSelection: String, CaseIterable, Identifiable {
    case vegan
    case breakfast
    case lunch
    case dinner

    var id: String { self.rawValue }
    var name: String { self.rawValue.capitalized }

    var menu: String {
        switch self {
        case .vegan: return "Vegan"
        case .breakfast: return "Breakfast"
        case .lunch: return "Lunch"
        case .dinner: return "Dinner"
        }
    }
}

Hope you can help.

Best, Imran

   

Imran has a problem with Codable and JSON:

it then comes with an error that the struct doesn't conform to codable

I pasted your code into Playgrounds to test. (I removed the enum, this has nothing to do with your question!) Then I created test cases. I hope you are also using Playgrounds to test portions of your code and to try variations? Yes?

This code works fine. If all else fails, try a clean build? I cannot see issues in your definition. Everything in your struct definitions are codable data types. If this persists, please copy the error message and all its gory detail.

struct Recipe: Identifiable, Codable {
    let id:          Int
    let name:        String
    let creator:     String
    let serves:      Int
    let ingredients: [Ingredient]
    let methods:     [Method]
    let imageURL:    URL

    enum CodingKeys: String, CodingKey {
        case id, name, creator, serves, ingredients
        case methods  = "method"   // mapping struct let to JSON name
        case imageURL = "imageurl" // mapping struct let to JSON name
    }
}

struct Ingredient: Codable {
    let name:        String
    let quantity:    Double
    let measurement: String
}

struct Method: Codable {
    let step: Int
    let text: String
}

// Paste into Playgrounds
// Setup some test values
let cheeseURL   = URL(string: "https://cheese.com") // glorious cheese
let grateCheese = Method(step: 1, text: "Shred lots of cheese.")
let stuffing    = Ingredient(name: "cheese", quantity: 4, measurement: "cups")
let taco        = Recipe(id: 5, name: "Taco", creator: "Imran", serves: 2, ingredients: [stuffing, stuffing], methods: [grateCheese], imageURL: cheeseURL!)

// Verify
taco.imageURL
taco.serves
taco.creator
taco.ingredients[1].name

// try encoding this recipe
let tacoEncoder = JSONEncoder()
let tacoAsJSON = try? tacoEncoder.encode(taco)  // this might fail.

// try decoding this recipe
let tacoDecoder = JSONDecoder()
let decodedTaco = try? tacoDecoder.decode(Recipe.self, from: tacoAsJSON!) // Now try to Decode

// verify the decoded JSON
decodedTaco!.imageURL
decodedTaco!.serves                 // serves 2 !
decodedTaco!.creator                // Imran
decodedTaco!.ingredients[1].name    // cheese, of course!

   

Hi,

I think the issue is that I want to add new elements to the struct: so I want to add

let dateAdded: String
let difficulty: String
let category: String

problem is that that when I do that it all comes crashing down (no pun intended towards WeWork) with the error and when I remove these it works.

If it helps here is the full gist: link

   

See my answer to Day 60 - Milestone Challenge - JSONDecoder Question. Add new file called URLSession-Codable

/// A URLSession extension that fetches data from a URL and decodes to some Decodable type.
/// Usage: let user = try await URLSession.shared.decode(UserData.self, from: someURL)
/// Note: this requires Swift 5.5.
extension URLSession {
    func decode<T: Decodable>(
        _ type: T.Type = T.self,
        from url: URL,
        keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys,
        dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .deferredToData,
        dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate
    ) async throws  -> T {
        let (data, _) = try await data(from: url)

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = keyDecodingStrategy
        decoder.dataDecodingStrategy = dataDecodingStrategy
        decoder.dateDecodingStrategy = dateDecodingStrategy

        let decoded = try decoder.decode(T.self, from: data)
        return decoded
    }
}

The Data struct

struct Recipe: Codable, Identifiable {
    struct Ingredient: Codable {
        let name: String
        let quantity: Int
        let measurement: String
    }

    struct Method: Codable {
        let step: Int
        let text: String
    }

    let id: Int
    let name: String
    let creator: String
    let serves: Int
    let ingredients: [Ingredient]
    let method: [Method]
    let imageURL: URL

    enum CodingKeys: String, CodingKey {
        case id, name, creator, serves, ingredients, method
        case imageURL = "imageurl"
    }
}

enum for menu selection

enum MenuSelection: String, CaseIterable {
    case vegan, breakfast, lunch, dinner
}

Then the ContentView

struct ContentView: View {
    @State private var recipes = [Recipe]()
    @State private var menu = MenuSelection.breakfast

    var body: some View {
        NavigationView {
            List {
                Section {
                    Picker("Select menu", selection: $menu) {
                        ForEach(MenuSelection.allCases, id: \.self) {
                            Text($0.rawValue.capitalized)
                        }
                    }
                }

                ForEach(recipes) { recipe in
                    Text(recipe.name)
                }
            }
            .task { await fetch() }
        }
    }

    /// Call for get JSON data from URL
    /// requires `@State private var name = [Decodable]()`
    /// and `.task { await fetch() }`
    func fetch() async {
        do  {
            let userURL = URL(string: "https://recipesstore.s3.eu-west-2.amazonaws.com/\(menu.rawValue.capitalized).json")!
            async let userItems = try await URLSession.shared.decode([Recipe].self, from: userURL)
            recipes = try await userItems
        } catch {
            print("Failed to fetch data!")
        }
    }
}

   

When you add new paramaters to a struct, and you are manually handling codingKeys, as you are, you need to add those names to the CodingKeys enum. As in

    case id, name, creator, serves, ingredients, dateAdded, difficulty, category

   

Can you change the Fetched JSON because if you add to the Recipe to CodingKeys enum then it will fail to fetch the data. You can just give the new properties a value.

struct Recipe: Codable, Identifiable {
    struct Ingredient: Codable {
        let name: String
        let quantity: Int
        let measurement: String
    }

    struct Method: Codable {
        let step: Int
        let text: String
    }

    let id: Int
    let name: String
    let creator: String
    let serves: Int
    let ingredients: [Ingredient]
    let method: [Method]
    let imageURL: URL

    var dateAdded = Date.now
    var difficulty = ""
    var category = ""

    enum CodingKeys: String, CodingKey {
        case id, name, creator, serves, ingredients, method
        case imageURL = "imageurl"
    }
}

   

Save 50% in my Black Friday sale.

SAVE 50% To celebrate WWDC22, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.