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