'Swift Decoding Error - key not found when decoding JSON

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

   {
   "totalHits":396990,
   "currentPage":1,
   "totalPages":396990,
   "pageList":[
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10
   ],
   "foodSearchCriteria":{
      "dataType":[
         
      ],
      "pageNumber":1,
      "numberOfResultsPerPage":50,
      "pageSize":1,
      "requireAllWords":false
   },
   "foods":[
      {
         "fdcId":1104067,
         "description":"100 GRAND Bar",
         "lowercaseDescription":"100 grand bar",
         "commonNames":"",
         "additionalDescriptions":"$ 100,000 Bar",
         "dataType":"Survey (FNDDS)",
         "foodCode":91715300,
         "publishedDate":"2020-10-30",
         "foodCategory":"Candy containing chocolate",
         "foodCategoryId":999786,
         "allHighlightFields":"",
         "score":1.0,
         "foodNutrients":[
            {
               "nutrientId":1003,
               "nutrientName":"Protein",
               "nutrientNumber":"203",
               "unitName":"G",
               "value":2.5,
               "rank":600,
               "indentLevel":1,
               "foodNutrientId":13301423
            },
   
            {
               "nutrientId":1280,
               "nutrientName":"PUFA 22:5 n-3 (DPA)",
               "nutrientNumber":"631",
               "unitName":"G",
               "value":0.0,
               "rank":15200,
               "indentLevel":2,
               "foodNutrientId":13301485
            },
            {
               "nutrientId":1292,
               "nutrientName":"Fatty acids, total monounsaturated",
               "nutrientNumber":"645",
               "unitName":"G",
               "value":6.04,
               "rank":11400,
               "indentLevel":1,
               "foodNutrientId":13301486
            },
            {
               "nutrientId":1293,
               "nutrientName":"Fatty acids, total polyunsaturated",
               "nutrientNumber":"646",
               "unitName":"G",
               "value":1.39,
               "rank":12900,
               "indentLevel":1,
               "foodNutrientId":13301487
            }
         ],
         "finalFoodInputFoods":[
            {
               "foodDescription":"Candies, NESTLE, 100 GRAND Bar",
               "gramWeight":100,
               "id":102104,
               "portionCode":"0",
               "portionDescription":"NONE",
               "unit":"GM",
               "rank":1,
               "srCode":19144,
               "value":100
            }
         ],
         "foodMeasures":[
            {
               "disseminationText":"1 bar (1.5 oz)",
               "gramWeight":43,
               "id":246540,
               "modifier":"60362",
               "rank":1,
               "measureUnitAbbreviation":"undetermined",
               "measureUnitName":"undetermined",
               "measureUnitId":9999
            },
            {
               "disseminationText":"Quantity not specified",
               "gramWeight":21,
               "id":266827,
               "modifier":"90000",
               "rank":2,
               "measureUnitAbbreviation":"undetermined",
               "measureUnitName":"undetermined",
               "measureUnitId":9999
            }
         ],
         "foodAttributes":[
            
         ],
         "foodAttributeTypes":[
            {
               "name":"Additional Description",
               "description":"Additional descriptions for the food.",
               "id":1001,
               "foodAttributes":[
                  {
                     "value":"$ 100,000 Bar",
                     "id":978293,
                     "sequenceNumber":1
                  }
               ]
            },
            {
               "name":"Attribute",
               "description":"Generic attributes",
               "id":999,
               "foodAttributes":[
                  {
                     "value":"5702",
                     "name":"WWEIA Category number",
                     "id":991595
                  },
                  {
                     "value":"Candy containing chocolate",
                     "name":"WWEIA Category description",
                     "id":999786
                  }
               ]
            }
         ],
         "foodVersionIds":[
            
         ]
      }
   ],
   "aggregations":{
      "dataType":

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


Solution 1:[1]

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.

Solution 2:[2]

based on the json data that you show, the following struct models worked for me. Here is the code I used in my tests:

EDIT-1: given the "new" json data provided, changed FoodNutrient:

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: [FoodNutrient]?  // <-- 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 FoodNutrient: Codable {
    let nutrientID: Int?
    let nutrientName, nutrientNumber, unitName: String
    let value: Double?
    let rank, indentLevel, foodNutrientID: Int?

    enum CodingKeys: String, CodingKey {
        case nutrientID = "nutrientId"
        case nutrientName, nutrientNumber, unitName, value, rank, indentLevel
        case foodNutrientID = "foodNutrientId"
    }
}

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 = """
   {
   "totalHits":396990,
   "currentPage":1,
   "totalPages":396990,
   "pageList":[
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10
   ],
   "foodSearchCriteria":{
      "dataType":[ ],
      "pageNumber":1,
      "numberOfResultsPerPage":50,
      "pageSize":1,
      "requireAllWords":false
   },
   "foods":[
      {
         "fdcId":1104067,
         "description":"100 GRAND Bar",
         "lowercaseDescription":"100 grand bar",
         "commonNames":"",
         "additionalDescriptions":"$ 100,000 Bar",
         "dataType":"Survey (FNDDS)",
         "foodCode":91715300,
         "publishedDate":"2020-10-30",
         "foodCategory":"Candy containing chocolate",
         "foodCategoryId":999786,
         "allHighlightFields":"",
         "score":1.0,
         "foodNutrients":[
            {
               "nutrientId":1003,
               "nutrientName":"Protein",
               "nutrientNumber":"203",
               "unitName":"G",
               "value":2.5,
               "rank":600,
               "indentLevel":1,
               "foodNutrientId":13301423
            },
            {
               "nutrientId":1280,
               "nutrientName":"PUFA 22:5 n-3 (DPA)",
               "nutrientNumber":"631",
               "unitName":"G",
               "value":0.0,
               "rank":15200,
               "indentLevel":2,
               "foodNutrientId":13301485
            },
            {
               "nutrientId":1292,
               "nutrientName":"Fatty acids, total monounsaturated",
               "nutrientNumber":"645",
               "unitName":"G",
               "value":6.04,
               "rank":11400,
               "indentLevel":1,
               "foodNutrientId":13301486
            },
            {
               "nutrientId":1293,
               "nutrientName":"Fatty acids, total polyunsaturated",
               "nutrientNumber":"646",
               "unitName":"G",
               "value":1.39,
               "rank":12900,
               "indentLevel":1,
               "foodNutrientId":13301487
            }
         ],
         "finalFoodInputFoods":[
            {
               "foodDescription":"Candies, NESTLE, 100 GRAND Bar",
               "gramWeight":100,
               "id":102104,
               "portionCode":"0",
               "portionDescription":"NONE",
               "unit":"GM",
               "rank":1,
               "srCode":19144,
               "value":100
            }
         ],
         "foodMeasures":[
            {
               "disseminationText":"1 bar (1.5 oz)",
               "gramWeight":43,
               "id":246540,
               "modifier":"60362",
               "rank":1,
               "measureUnitAbbreviation":"undetermined",
               "measureUnitName":"undetermined",
               "measureUnitId":9999
            },
            {
               "disseminationText":"Quantity not specified",
               "gramWeight":21,
               "id":266827,
               "modifier":"90000",
               "rank":2,
               "measureUnitAbbreviation":"undetermined",
               "measureUnitName":"undetermined",
               "measureUnitId":9999
            }
         ],
         "foodAttributes":[ ],
         "foodAttributeTypes":[
            {
               "name":"Additional Description",
               "description":"Additional descriptions for the food.",
               "id":1001,
               "foodAttributes":[
                  {
                     "value":"$ 100,000 Bar",
                     "id":978293,
                     "sequenceNumber":1
                  }
               ]
            },
            {
               "name":"Attribute",
               "description":"Generic attributes",
               "id":999,
               "foodAttributes":[
                  {
                     "value":"5702",
                     "name":"WWEIA Category number",
                     "id":991595
                  },
                  {
                     "value":"Candy containing chocolate",
                     "name":"WWEIA Category description",
                     "id":999786
                  }
               ]
            }
         ],
         "foodVersionIds":[ ]
      }
   ]
}
"""
                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")
                }
            }
    }
}

Note: the "new" json data is not correct, the end "aggregations":{ "dataType": is missing some parts.

Add print(String(data: data!, encoding: .utf8)) just before let searchResults = try! JSONDecoder().... and show us really what it prints, instead of showing bits of json that you think the response giving you. Then it would be very easy to help you solve your decoding issue.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2