I'm decoding a JSON response in my Swift App, and the code decided to not work when I switched the api URL.
This is my json response
{
"foodSearchCriteria":{
"query":"eggs",
"generalSearchInput":"eggs",
"pageNumber":1,
"numberOfResultsPerPage":50,
"pageSize":50,
"requireAllWords":false
},
"foods":[
{
"fdcId":577532,
"description":"EGGS",
"lowercaseDescription":"eggs",
"dataType":"Branded",
"gtinUpc":"021130049134",
"publishedDate":"2019-04-01",
"brandOwner":"Safeway, Inc.",
"ingredients":"",
"marketCountry":"United States",
"foodCategory":"Eggs & Egg Substitutes",
"modifiedDate":"2017-07-14",
"dataSource":"LI",
"servingSizeUnit":"g",
"servingSize":44.0,
"householdServingFullText":"1 EGG",
"allHighlightFields":"",
"score":848.0136,
}
I'm being met with this error
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "foods", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"foods\", intValue: nil) (\"foods\").", underlyingError: nil))
I figured it was because I wasn't matching my "foods" var correctly, but It doesn't appear that way. Below I attached snippets of my call and structs
struct APISearchResults: Codable {
let currentPage, totalPages: Int?
let pageList: [Int]?
//let foodSearchCriteria: FoodSearchCriteria
let foods: [Food]
}
// MARK: - Food
struct Food: Codable { //core
let fdcID: Int
let foodDescription, lowercaseDescription, commonNames, additionalDescriptions: String?
let dataType: String?
let ndbNumber: Int?
let publishedDate, foodCategory, allHighlightFields: String?
let score: Double?
let foodNutrients: [FoodNutrientInformation]
let gtinUpc: Double?
let brandOwner: String?
let ingredients: String?
let marketCountry: String?
let modifiedDate: String?
let dataSource: String?
let servingSize: Double?
let householdServingFullText: String?
enum CodingKeys: String, CodingKey {
case fdcID = "fdcId"
case foodDescription = "description"
case lowercaseDescription, commonNames, additionalDescriptions, dataType, ndbNumber, publishedDate, foodCategory, allHighlightFields, score, foodNutrients, gtinUpc, brandOwner, ingredients, marketCountry, modifiedDate, dataSource, servingSize, householdServingFullText
}
}
// MARK: - FoodNutrient
struct FoodNutrientInformation: Codable {
let nutrientID: Int?
let nutrientName, nutrientNumber, unitName, derivationCode: String
let derivationDescription: String?
let derivationID: Int?
let value: Double?
let foodNutrientSourceID: Int?
let foodNutrientSourceCode, foodNutrientSourceDescription: String?
let rank, indentLevel, foodNutrientID, dataPoints: Int?
enum CodingKeys: String, CodingKey {
case nutrientID = "nutrientId"
case nutrientName, nutrientNumber, unitName, derivationCode, derivationDescription
case derivationID = "derivationId"
case value
case foodNutrientSourceID = "foodNutrientSourceId"
case foodNutrientSourceCode, foodNutrientSourceDescription, rank, indentLevel
case foodNutrientID = "foodNutrientId"
case dataPoints
}
}
For detail purposes I also will attached the API call itself in case it is in relation to that
class FoodApiSearch: ObservableObject{
@Published var foodDescription = ""
@Published var foodUnit = ""
@Published var calories = ""
//will search for user Input
func searchFood(userItem: String){
//calls api search
guard let url = URL(string: "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=***********?query=\(userItem)") else {return}
URLSession.shared.dataTask(with: url) { (data, _,_) in
let searchResults = try! JSONDecoder().decode(APISearchResults.self, from: data!)
DispatchQueue.main.async {
for item in searchResults.foods{
self.foodDescription = item.lowercaseDescription?.firstCapitalized ?? "food not valid"
self.calories = String(Double(round(item.foodNutrients[3].value!)).removeZerosFromEnd())
}
}
}
.resume()
}
}
CodePudding user response:
If you have extra variables that are not decoded, you need to include a codingKeys
enum with just the JSON you need to decode. Your Codable should be:
struct APISearchResults: Codable {
var currentPage, totalPages: Int?
var pageList: [Int]?
//let foodSearchCriteria: FoodSearchCriteria
let foods: [Food]
enum CodingKeys: String, CodingKey {
case foods
}
}
The decoder is looking for currentPage
, totalPages
& pageList
which don't exist in the JSON. When you don't include your own codingKeys
, the compiler synthesizes them, so you have to explicitly define them in this case.
CodePudding user response:
based on the json data that you show, the following struct models worked for me. Here is the code I used in my tests:
struct Food: Codable {
let fdcID: Int
let foodDescription, lowercaseDescription, commonNames, additionalDescriptions: String?
let dataType: String?
let ndbNumber: Int?
let publishedDate, foodCategory, allHighlightFields: String?
let score: Double?
let foodNutrients: [FoodNutrientInformation]? // <-- here optional
let gtinUpc: String? // <-- here not Double
let brandOwner: String?
let ingredients: String?
let marketCountry: String?
let modifiedDate: String?
let dataSource: String?
let servingSize: Double?
let householdServingFullText: String?
enum CodingKeys: String, CodingKey {
case fdcID = "fdcId"
case foodDescription = "description"
case lowercaseDescription, commonNames, additionalDescriptions, dataType, ndbNumber, publishedDate, foodCategory, allHighlightFields, score, gtinUpc, foodNutrients, brandOwner, ingredients, marketCountry, modifiedDate, dataSource, servingSize, householdServingFullText
}
}
struct FoodNutrientInformation: Codable {
let nutrientID: Int?
let nutrientName, nutrientNumber, unitName, derivationCode: String
let derivationDescription: String?
let derivationID: Int?
let value: Double?
let foodNutrientSourceID: Int?
let foodNutrientSourceCode, foodNutrientSourceDescription: String?
let rank, indentLevel, foodNutrientID, dataPoints: Int?
enum CodingKeys: String, CodingKey {
case nutrientID = "nutrientId"
case nutrientName, nutrientNumber, unitName, derivationCode, derivationDescription
case derivationID = "derivationId"
case value
case foodNutrientSourceID = "foodNutrientSourceId"
case foodNutrientSourceCode, foodNutrientSourceDescription, rank, indentLevel
case foodNutrientID = "foodNutrientId"
case dataPoints
}
}
struct APISearchResults: Codable {
var currentPage, totalPages: Int? // <-- here
var pageList: [Int]? // <-- here
let foods: [Food]
}
struct ContentView: View {
var body: some View {
Text("testing")
.onAppear {
let json = """
{
"foodSearchCriteria": {
"query":"eggs",
"generalSearchInput":"eggs",
"pageNumber":1,
"numberOfResultsPerPage":50,
"pageSize":50,
"requireAllWords":false
},
"foods":[
{
"fdcId":577532,
"description":"EGGS",
"lowercaseDescription":"eggs",
"dataType":"Branded",
"gtinUpc":"021130049134",
"publishedDate":"2019-04-01",
"brandOwner":"Safeway, Inc.",
"ingredients":"",
"marketCountry":"United States",
"foodCategory":"Eggs & Egg Substitutes",
"modifiedDate":"2017-07-14",
"dataSource":"LI",
"servingSizeUnit":"g",
"servingSize":44.0,
"householdServingFullText":"1 EGG",
"allHighlightFields":"",
"score":848.0136
}
]
}
"""
let data = json.data(using: .utf8)!
do {
let searchResults = try JSONDecoder().decode(APISearchResults.self, from: data)
print("\n ---> searchResults:\n \(searchResults) \n")
} catch {
print("\n---> ERROR \(error) \n")
}
}
}
}