I'm trying to use the Swift Decoder protocol to parse some JSON, but I'm getting errors that I can't make sense of. The JSON I'm getting back is problematic because not all of the keys are certain to be present, so I've given the properties of the corresponding struct default values and setup an initializer, but I suspect my approach is wrong.
In the "foods" array, some of the items have the property "ndbNumber" (String), and some have fdcId (Int) and gtinUpc (String) properties. Otherwise the items are identical and represent food items returned from a search.
This is the struct for the outermost JSON level.
public struct ReturnedFoods: Decodable {
public let count: Int
public let all: [ReturnedFood]
enum CodingKeys: String, CodingKey {
case count = "totalHits"
case all = "foods"
}
}
And this is for the the next level where the food items are:
public struct ReturnedFood: Codable {
public var ndbNumber: String = ""
public var fdcId : Int = -1
public var description: String = ""
public var dataType : String = ""
public var brandOwner: String = ""
public var gtinUpc : String = ""
public var ingredients : String = ""
public var publishedDate : String = ""
public var score : Float = -1
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let ndbNumber = try values.decodeIfPresent(String.self, forKey: .ndbNumber) {
self.ndbNumber = ndbNumber
}
if let fdcId = try values.decodeIfPresent(Int.self, forKey: .ndbNumber) {
self.fdcId = fdcId
}
if let description = try values.decodeIfPresent(String.self, forKey: .description) {
self.description = description
}
if let dataType = try values.decodeIfPresent(String.self, forKey: .dataType) {
self.dataType = dataType
}
if let brandOwner = try values.decodeIfPresent(String.self, forKey: .brandOwner) {
self.brandOwner = brandOwner
}
if let gtinUpc = try values.decodeIfPresent(String.self, forKey: .gtinUpc) {
self.gtinUpc = gtinUpc
}
if let ingredients = try values.decodeIfPresent(String.self, forKey: .ingredients) {
self.ingredients = ingredients
}
if let publishedDate = try values.decodeIfPresent(String.self, forKey: .publishedDate) {
self.publishedDate = publishedDate
}
if let score = try values.decodeIfPresent(Float.self, forKey: .score) {
self.score = score
}
}
}
I've double checked to make sure all the types are correct, so I suspect that the problem is with missing keys.
This is the error I'm getting: typeMismatch(Swift.Int, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "foods", intValue: nil), _JSONKey(stringValue: "Index 4", intValue: 4), CodingKeys(stringValue: "ndbNumber", intValue: nil)], debugDescription: "Expected to decode Int but found a string/data instead.", underlyingError: nil))
The JSON is very long, so I'm just posting a short portion of it:
"foodSearchCriteria": {
"dataType": [
"Foundation",
"Branded"
],
"query": "cheddar",
"generalSearchInput": "cheddar",
"pageNumber": 1,
"requireAllWords": false
},
"totalHits": 12839,
"currentPage": 1,
"totalPages": 257,
"foods": [
{
"fdcId": 566851,
"description": "CHEDDAR",
"dataType": "Branded",
"gtinUpc": "828280001766",
"publishedDate": "2019-04-01",
"brandOwner": "FISCALINI",
"ingredients": "PASTEURIZED COW'S MILK, CHEESE CULTURES, ENZYMES (MICROBIAL RENNET), SALT.",
"foodNutrients": [
{
"nutrientId": 1079,
"nutrientName": "Fiber, total dietary",
"nutrientNumber": "291",
"unitName": "G",
"derivationCode": "LCCD",
"derivationDescription": "Calculated from a daily value percentage per serving size measure",
"value": 0
},
{
"nutrientId": 1087,
"nutrientName": "Calcium, Ca",
"nutrientNumber": "301",
"unitName": "MG",
"derivationCode": "LCCD",
"derivationDescription": "Calculated from a daily value percentage per serving size measure",
"value": 714
},
{
"nutrientId": 1089,
"nutrientName": "Iron, Fe",
"nutrientNumber": "303",
"unitName": "MG",
"derivationCode": "LCCD",
"derivationDescription": "Calculated from a daily value percentage per serving size measure",
"value": 0
},
{
"nutrientId": 1104,
"nutrientName": "Vitamin A, IU",
"nutrientNumber": "318",
"unitName": "IU",
"derivationCode": "LCCD",
"derivationDescription": "Calculated from a daily value percentage per serving size measure",
"value": 1071
},
{
"nutrientId": 1162,
"nutrientName": "Vitamin C, total ascorbic acid",
"nutrientNumber": "401",
"unitName": "MG",
"derivationCode": "LCCD",
"derivationDescription": "Calculated from a daily value percentage per serving size measure",
"value": 0
},
{
"nutrientId": 1003,
"nutrientName": "Protein",
"nutrientNumber": "203",
"unitName": "G",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 26.79
},
{
"nutrientId": 1004,
"nutrientName": "Total lipid (fat)",
"nutrientNumber": "204",
"unitName": "G",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 32.14
},
{
"nutrientId": 1005,
"nutrientName": "Carbohydrate, by difference",
"nutrientNumber": "205",
"unitName": "G",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 3.57
},
{
"nutrientId": 1008,
"nutrientName": "Energy",
"nutrientNumber": "208",
"unitName": "KCAL",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 400
},
{
"nutrientId": 1093,
"nutrientName": "Sodium, Na",
"nutrientNumber": "307",
"unitName": "MG",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 679
},
{
"nutrientId": 1253,
"nutrientName": "Cholesterol",
"nutrientNumber": "601",
"unitName": "MG",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 111
},
{
"nutrientId": 1257,
"nutrientName": "Fatty acids, total trans",
"nutrientNumber": "605",
"unitName": "G",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 1.25
},
{
"nutrientId": 1258,
"nutrientName": "Fatty acids, total saturated",
"nutrientNumber": "606",
"unitName": "G",
"derivationCode": "LCCS",
"derivationDescription": "Calculated from value per serving size measure",
"value": 21.43
}
],
"allHighlightFields": "",
"score": 721.74396
And here is a link with the full JSON: https://api.nal.usda.gov/fdc/v1/foods/search?query=cheddar&dataType=Foundation,Branded&api_key=DEMO_KEY
I'd appreciate any help on solving this.
ndbNumber
in the 5th item of the arrayfoods
is not anInt
. But there is an obvious copy&paste error: You have to decodefdcId
for key.fdcId
rather than for.ndbNumber
– vadianpublic var description: String?
– paulRick