So, I have tried looking for the answer, I have seen many questions similar to mine. I have tried adding an enum codingKey, I have tried reworking the JSON, but nothing seems to work. I know it's probably something very simple too. (I'm a noob.) Please help.
I am making a Quotes app as part of a project for a course I'm taking.
Here's the code for the Model:
import Foundation
class AllQuotes: Identifiable, Decodable {
var id:UUID?
var quoteTopic:String
var topicImage:String
var featured:Bool
var QuotesList:[Quotes]
}
class Quotes: Identifiable, Decodable {
var id:UUID?
var name:String
var actualQuote:String
var image:String?
private enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case actualQuote = "actualQuote"
case image = "image"
}
}
Here's my JSON code:
[
{
"quoteTopic": "Wise Quotes",
"topicImage": "wise quotes",
"featured": false,
"QuotesList": [
{
"name": "Lao Tzu",
"actualQuote": "The journey of a thousand miles begins with one step.",
"image": null
},
{
"name": "Mark Twain",
"actualQuote": "It is better to keep your mouth closed and let people think you are a fool than to open it and remove all doubt.",
"image": null
},
{
"name": "Mark Twain",
"actualQuote": "The secret of getting ahead is getting started.",
"image": null
},
{
"name": "Babe Ruth",
"actualQuote": "It’s hard to beat a person who never gives up.",
"image": null
}
]
},
{
"quoteTopic": "Motivational Quotes",
"topicImage": "motivational quotes",
"featured": true,
"QuotesList": [
{
"name": "Mark Twain",
"actualQuote": "Age is an issue of mind over matter. If you don't mind, it doesn't matter.",
"image": null
},
{
"name": "Mahatma Gandhi",
"actualQuote": "Learn as if you will live forever, live like you will die tomorrow.",
"image": null
},
{
"name": "Mary Kay Ash",
"actualQuote": "Don’t limit yourself. Many people limit themselves to what they think they can do. You can go as far as your mind lets you. What you believe, remember, you can achieve.",
"image": null
},
{
"name": "Unknown",
"actualQuote": "Hold the vision, trust the process.",
"image": null
}
]
},
{
"quoteTopic": "Success Quotes",
"topicImage": "success quotes",
"featured": false,
"QuotesList": [
{
"name": "Estee Lauder",
"actualQuote": "I never dreamed about success. I worked for it.",
"image": null
},
{
"name": "Thomas Edison",
"actualQuote": "Opportunity is missed by most people because it is dressed in overalls and looks like work.",
"image": null
},
{
"name": "Tom Lehrer",
"actualQuote": "Life is like a sewer… what you get out of it depends on what you put into it.",
"image": null
},
{
"name": "Walt Disney",
"actualQuote": "All our dreams can come true, if we have the courage to pursue them.",
"image": null
}
]
}
]
and here's the error I'm getting
Couldn't decode json, try again (to get the actual quote)! keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "name", intValue: nil) ("name").", underlyingError: nil))
(NOTE: It RUNS/builds fine, but it just won't show the 'QuotesDetailView' page when I try to run it. It's SwiftUI.
Please let me know if I need to provide anymore information, thank you!
EDIT: here's how I decode my JSON
class DataService {
// Return an array of Quote Objects
static func getLocalData() -> [AllQuotes] {
// Begin the process of parsing the JSON File
// Get a URL path to json file
let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
// Check if pathString is nil, otherwise return empty Quotes List if it is.
guard pathString != nil else{
return [AllQuotes]()
}
// Create URL Object
let url = URL(fileURLWithPath: pathString!)
// Create Data Object
do {
let data = try Data(contentsOf: url)
// Parse the data
let decoder = JSONDecoder()
do {
let quoteData = try decoder.decode([AllQuotes].self, from: data)
// Set unique IDs for each instance
for newQuote in quoteData {
newQuote.id = UUID()
}
// Return the Quote
return quoteData
} catch {
// Couldn't decode json
print("Couldn't decode json, try again (to get the quotes TOPIC)!")
print(error)
}
} catch {
// Error fetching data from file
print("There was an error fetching the data from the file. - with the quote list!")
print(error)
}
// It didn't work, return an empty Quotes List
return [AllQuotes]()
}
// Return an array of ACTUAL Quotes Objects
static func getActualQuote() -> [Quotes] {
// Begin the process of parsing the JSON File
// Get a URL path to json file
let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
// Check if pathString is nil, otherwise return empty Quotes List if it is.
guard pathString != nil else{
return [Quotes]()
}
// Create URL Object
let url = URL(fileURLWithPath: pathString!)
// Create Data Object
do {
let data = try Data(contentsOf: url)
// Parse the data
let decoder = JSONDecoder()
do {
let actualQuoteData = try decoder.decode([Quotes].self, from: data)
// Set unique IDs for each instance
for actualQuote in actualQuoteData {
actualQuote.id = UUID()
}
// Return the Quote
return actualQuoteData
} catch {
// Couldn't decode json
print("Couldn't decode json, try again (to get the actual quote)!")
print(error)
}
} catch {
// Error fetching data from file
print("There was an error fetching the data from the file. - with the actual quote!")
print(error)
}
// It didn't work, return an empty Quotes List
return [Quotes]()
}
}
CodePudding user response:
try this sample code, it shows how to decode your json data, and then display some info:
Since you don't show how you decode your json data, I'm gessing that
the error you get is due to decoding AllQuotes.self
instead of [AllQuotes].self
as required.
struct ContentView: View {
@State var quoteList = [Quotes]()
var body: some View {
List(quoteList) { quote in
Text(quote.name)
}
.onAppear {
let json = """
[
{
"quoteTopic": "Wise Quotes",
"topicImage": "wise quotes",
"featured": false,
"QuotesList": [
{
"name": "Lao Tzu",
"actualQuote": "The journey of a thousand miles begins with one step.",
"image": null
},
{
"name": "Mark Twain",
"actualQuote": "It is better to keep your mouth closed and let people think you are a fool than to open it and remove all doubt.",
"image": null
},
{
"name": "Mark Twain",
"actualQuote": "The secret of getting ahead is getting started.",
"image": null
},
{
"name": "Babe Ruth",
"actualQuote": "It’s hard to beat a person who never gives up.",
"image": null
}
]
},
{
"quoteTopic": "Motivational Quotes",
"topicImage": "motivational quotes",
"featured": true,
"QuotesList": [
{
"name": "Mark Twain",
"actualQuote": "Age is an issue of mind over matter. If you don't mind, it doesn't matter.",
"image": null
},
{
"name": "Mahatma Gandhi",
"actualQuote": "Learn as if you will live forever, live like you will die tomorrow.",
"image": null
},
{
"name": "Mary Kay Ash",
"actualQuote": "Don’t limit yourself. Many people limit themselves to what they think they can do. You can go as far as your mind lets you. What you believe, remember, you can achieve.",
"image": null
},
{
"name": "Unknown",
"actualQuote": "Hold the vision, trust the process.",
"image": null
}
]
},
{
"quoteTopic": "Success Quotes",
"topicImage": "success quotes",
"featured": false,
"QuotesList": [
{
"name": "Estee Lauder",
"actualQuote": "I never dreamed about success. I worked for it.",
"image": null
},
{
"name": "Thomas Edison",
"actualQuote": "Opportunity is missed by most people because it is dressed in overalls and looks like work.",
"image": null
},
{
"name": "Tom Lehrer",
"actualQuote": "Life is like a sewer… what you get out of it depends on what you put into it.",
"image": null
},
{
"name": "Walt Disney",
"actualQuote": "All our dreams can come true, if we have the courage to pursue them.",
"image": null
}
]
}
]
"""
if let data = json.data(using: .utf8) {
do {
let apiResponse = try JSONDecoder().decode([AllQuotes].self, from: data)
// print something
for quote in apiResponse {
print("---> quoteTopic: \(quote.quoteTopic)")
// all quotes
quoteList.append(contentsOf: quote.QuotesList)
}
} catch {
print("decode error: \(error)")
}
}
}
}
}
class AllQuotes: Identifiable, Decodable {
let id = UUID() // <-- here
var quoteTopic:String
var topicImage:String
var featured:Bool
var QuotesList:[Quotes]
// -- here
private enum CodingKeys: String, CodingKey {
// <-- here remove id
case quoteTopic, topicImage, featured, QuotesList
}
}
class Quotes: Identifiable, Decodable {
let id = UUID() // <-- here
var name:String
var actualQuote:String
var image:String?
// -- here
private enum CodingKeys: String, CodingKey {
// <-- here remove id
case name = "name"
case actualQuote = "actualQuote"
case image = "image"
}
}
EDIT-1: in view of the "new" code
struct ContentView: View {
@State var quoteList = [Quotes]()
var body: some View {
List(quoteList) { quote in
Text(quote.name)
}
.onAppear {
quoteList = DataService.getActualQuote()
print("---> quoteList: \(quoteList)")
}
}
}
class DataService {
// Return an array of Quote Objects
static func getLocalData() -> [AllQuotes] {
// Begin the process of parsing the JSON File
// Get a URL path to json file
let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
// Check if pathString is nil, otherwise return empty Quotes List if it is.
guard pathString != nil else{
return [AllQuotes]()
}
let url = URL(fileURLWithPath: pathString!)
do {
let data = try Data(contentsOf: url)
do {
let quoteData = try JSONDecoder().decode([AllQuotes].self, from: data)
return quoteData
} catch {
// Couldn't decode json
print("Couldn't decode json, try again (to get the quotes TOPIC)!")
print(error)
}
} catch {
// Error fetching data from file
print("There was an error fetching the data from the file. - with the quote list!")
print(error)
}
// It didn't work, return an empty Quotes List
return []
}
// Return an array of ACTUAL Quotes Objects
static func getActualQuote() -> [Quotes] {
// Begin the process of parsing the JSON File
// Get a URL path to json file
let pathString = Bundle.main.path(forResource: "quotes", ofType: "json")
// Check if pathString is nil, otherwise return empty Quotes List if it is.
guard pathString != nil else{
return [Quotes]()
}
// Create URL Object
let url = URL(fileURLWithPath: pathString!)
// Create Data Object
do {
let data = try Data(contentsOf: url)
do {
// -- here
let quoteData = try JSONDecoder().decode([AllQuotes].self, from: data)
// -- here
var actualQuoteData = [Quotes]()
for quote in quoteData {
actualQuoteData.append(contentsOf: quote.QuotesList)
}
// Return the Quotes
return actualQuoteData
} catch {
// Couldn't decode json
print("Couldn't decode json, try again (to get the actual quote)!")
print(error)
}
} catch {
// Error fetching data from file
print("There was an error fetching the data from the file. - with the actual quote!")
print(error)
}
// It didn't work, return an empty Quotes List
return []
}
}
EDIT-2: you can shorten your code, such as:
class DataService {
// Return an array of Quote Objects
static func getLocalData() -> [AllQuotes] {
if let pathString = Bundle.main.path(forResource: "quotes", ofType: "json") {
let url = URL(fileURLWithPath: pathString)
do {
let data = try Data(contentsOf: url)
let quoteData = try JSONDecoder().decode([AllQuotes].self, from: data)
return quoteData
} catch {
print(error)
}
}
return []
}
// Return an array of ACTUAL Quotes Objects
static func getActualQuote() -> [Quotes] {
var actualQuoteData = [Quotes]()
for quote in getLocalData() {
actualQuoteData.append(contentsOf: quote.QuotesList)
}
return actualQuoteData
}
}