Home > database >  Swift Decoding Error - key not found when decoding JSON
Swift Decoding Error - key not found when decoding JSON

Time:04-16

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