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