Issue with Codable and JSON - json

I'm trying to avoid use all the boilerplate code stuff without codable.
I apologize if this a dumb question, but why am I getting this error when I'm trying to parse json with codable?
keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"name\", intValue: nil) (\"name\").", underlyingError: nil))
The json endpoint is structured as follows:
{
"businesses": [
{
"id": "FmGF1B-Rpsjq1f5b56qMwg",
"alias": "molinari-delicatessen-san-francisco",
"name": "Molinari Delicatessen",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/6He-NlZrAv2mDV-yg6jW3g/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/molinari-delicatessen-san-francisco?adjust_creative=Js10QwuboHe9ZMZF31mwuw&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=Js10QwuboHe9ZMZF31mwuw",
"review_count": 1058,
"categories": [
{
"alias": "delis",
"title": "Delis"
}
],
"rating": 4.5,
"coordinates": {
"latitude": 37.79838,
"longitude": -122.40782
},
"transactions": [
"pickup",
"delivery"
],
"price": "$$",
"location": {
"address1": "373 Columbus Ave",
"address2": "",
"address3": "",
"city": "San Francisco",
"zip_code": "94133",
"country": "US",
"state": "CA",
"display_address": [
"373 Columbus Ave",
"San Francisco, CA 94133"
]
},
"phone": "+14154212337",
"display_phone": "(415) 421-2337",
"distance": 1453.998141679007
}
}
I have a struct I created
struct Businesses: Codable {
var name:String
var image_url:String
var is_closed:Bool
var location:[String:String]
var display_phone:String
var url:String
}
Here's how I'm trying to use Codable:
do{
let jsonData = response.data
//created the json decoder
let decoder = JSONDecoder()
//using the array to put values
self.searchResults = [try decoder.decode(Businesses.self, from: jsonData!)]

It's a very common mistake, you are ignoring the root object which – of course – does not have a key name.
And you'll get another error, location is not [String:String]
struct Root : Decodable {
let businesses : [Business]
}
// Name this kind of struct in singular form
struct Business : Decodable {
let name: String
let imageUrl: URL
let isClosed: Bool
let location: Location
let displayPhone: String
let url: URL
}
struct Location : Decodable {
let address1, address2, address3: String
let city, zipCode, country, state: String
let displayAddress: [String]
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let jsonData = response.data
let result = try decoder.decode(Root.self, from: jsonData!)
self.searchResults = result.businesses
} catch { print(error) }

Related

How to parse this type of data to a JSON in Swift?

I have called an API to get all holidays in a year, it came out a Json type. But I only can extract it like below (it is one of many elements of "items")
"items": [
{
"kind": "calendar#event",
"etag": "\"3235567993214000\"",
"id": "20200101_1814eggq09ims8ge9pine82pclgn49rj41262u9a00oe83po05002i01",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=MjAyMDAxMDFfMTgxNGVnZ3EwOWltczhnZTlwaW5lODJwY2xnbjQ5cmo0MTI2MnU5YTAwb2U4M3BvMDUwMDJpMDEgZW4udWsjaG9saWRheUB2",
"created": "2021-04-07T08:26:36.000Z",
"updated": "2021-04-07T08:26:36.607Z",
"summary": "New Year's Day",
"creator": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"organizer": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"start": {
"date": "2020-01-01"
},
"end": {
"date": "2020-01-02"
},
"transparency": "transparent",
"visibility": "public",
"iCalUID": "20200101_1814eggq09ims8ge9pine82pclgn49rj41262u9a00oe83po05002i01#google.com",
"sequence": 0,
"eventType": "default"
},
{
"kind": "calendar#event",
"etag": "\"3235567993214000\"",
"id": "20200412_1814eggq09ims8gd8lgn6t35e8g56tbechgniag063i0ue048064g0g",
"status": "confirmed",
"htmlLink": "https://www.google.com/calendar/event?eid=MjAyMDA0MTJfMTgxNGVnZ3EwOWltczhnZDhsZ242dDM1ZThnNTZ0YmVjaGduaWFnMDYzaTB1ZTA0ODA2NGcwZyBlbi51ayNob2xpZGF5QHY",
"created": "2021-04-07T08:26:36.000Z",
"updated": "2021-04-07T08:26:36.607Z",
"summary": "Easter Sunday",
"creator": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"organizer": {
"email": "en.uk#holiday#group.v.calendar.google.com",
"displayName": "Holidays in United Kingdom",
"self": true
},
"start": {
"date": "2020-04-12"
},
"end": {
"date": "2020-04-13"
},
"transparency": "transparent",
"visibility": "public",
"iCalUID": "20200412_1814eggq09ims8gd8lgn6t35e8g56tbechgniag063i0ue048064g0g#google.com",
"sequence": 0,
"eventType": "default"
}
I try to get the value in key "start" and key "summary" but I can't.
Xcode told me that "items" is a __NSArrayI type.
What I've tried so far is create a class simple like this (just use to try first, so I didn't make all variable)
class API_Info {
var kind: String?
var etag: String?
var id: String?
var status: String?
var htmlLink: String?
var created: String?
var updated: String?
var summary: String?
init(items: [String:Any]){
self.kind = items["kind"] as? String
self.etag = items["etag"] as? String
self.id = items["id"] as? String
self.status = items["status"] as? String
self.htmlLink = items["htmlLink"] as? String
self.created = items["created"] as? String
self.updated = items["updated"] as? String
self.summary = items["summary"] as? String
}
}
And I parse like this:
guard let items = json!["items"]! as? [API_Info] else{
print("null")
return
}
In this way, else statement was run.
What am I doing wrong, and how can I get the data I want?
Thanks in advance.
Codable is the solution here. Below is the struct I used rather than your class
struct ApiInfo: Codable {
let kind: String
let etag: String
let id: String
let status: String
let htmlLink: String
let created: Date
let updated: Date
let summary: String
}
Then I created a root type to hold the array
struct Result: Codable {
let items: [ApiInfo]
}
And then the decoding
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let result = try decoder.decode(Result.self, from: data)
print(result.items)
} catch {
print(error)
}
Notice that the data values are decoded to Date objects.
Optionally you can skip the root type and decode as a dictionary
do {
let items = try decoder.decode([String: [ApiInfo]].self, from: data)
if let values = items["items"] {
print(values)
}
} catch {
print(error)
}

Parsing local json file on Swift

I have a local JSON and try to decode but got "Expected to decode Array but found a dictionary instead" error. The json file and two structs below:
{
"Stanford University": [{
"type": "government",
"name": "Stanford University",
"city": "Santa Clara",
"major": "Computer Engineering"
},
{
"type": "government",
"name": "Stanford University",
"city": "Santa Clara",
"major": "Economics"
}
],
"Berkeley University": [{
"type": "foundation",
"name": "Berkeley University",
"city": "Alameda",
"major": "Communication"
},
{
"type": "foundation",
"name": "Berkeley University",
"city": "Alameda",
"major": "Physics"
}
]
}
two structs:
struct Universite4: Codable {
let name: String?
let major:[Major]?
}
struct Major: Codable {
let type: String?
let name: String?
let major: String? }
And this is code for data load and decode;
public class DataLoader {
#Published var universite4 = [Universite4]()
init() {
load()
}
func load() {
if let unv4json = Bundle.main.url(forResource: "unv4", withExtension: "json") {
do {
let data = try Data(contentsOf: unv4json)
let jsonDecoder = JSONDecoder()
let dataFromJson = try jsonDecoder.decode([Universite4].self, from:data)
self.universite4 = dataFromJson
} catch {
print("Error: \(error)")
}
}
}
}
Does anybody know how can I fix above code? Regards.
Try to change, the issue here is that actually your keys are sort of "Dynamic keys" which I don't recommend but if you have to use them, so try this.
let dataFromJson = try jsonDecoder.decode([Universite4].self, from:data)
to
let dataFromJson = try jsonDecoder.decode([String:[Major]].self, from:data)

Nested JSON in SWIFT 5

even though there are many articles about nested json decoding, I'm struggling with following one:
sample from json:
{
"00AK": {
"icao": "00AK",
"iata": "",
"name": "Lowell Field",
"city": "Anchor Point",
"state": "Alaska",
"country": "US",
"elevation": 450,
"lat": 59.94919968,
"lon": -151.695999146,
"tz": "America\/Anchorage"
},
"00AL": {
"icao": "00AL",
"iata": "",
"name": "Epps Airpark",
"city": "Harvest",
"state": "Alabama",
"country": "US",
"elevation": 820,
"lat": 34.8647994995,
"lon": -86.7703018188,
"tz": "America\/Chicago"
},
"00AZ": {
"icao": "00AZ",
"iata": "",
"name": "Cordes Airport",
"city": "Cordes",
"state": "Arizona",
"country": "US",
"elevation": 3810,
"lat": 34.3055992126,
"lon": -112.1650009155,
"tz": "America\/Phoenix"
},
....
}
I start with mapping the airport specifics with following struct:
struct Airport : Codable {
var icao:String
var iata:String
var name:String
var city:String
var state:String
var country:String
var elevation: Double
var lat: Double
var lon: Double
var tz: Double
enum CodingKeys : String, CodingKey {
case icao
case iata
case name
case city
case state
case country
case elevation
case lat
case lon
case tz
}
}
but I'm stuck - unable to find out how to map a "mother" struct of airport code (00AK, 00AL, 00AZ) and how to look up in decoded data parsed here:
let decodedData = try JSONDecoder().decode(AirportsStructToBeCreated.self,
from: jsonData)
(e.g. how to look up "elevation" of "00AZ")
You have to decode a [String:Airport] dictionary
let decodedData = try JSONDecoder().decode([String:Airport].self,
from: jsonData)
or you could implement init(from decoder and map the dictionaries to an array as the key is also included in the struct.
And you can omit the CodingKeys if the struct members match exactly the dictionary keys.
And tz is not Double

Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath:)

I'm trying to decode the string values for alias in categories into an enum. And I'm getting the below error. What am I doing wrong?
Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "businesses", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "categories", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "alias", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a string/data instead.", underlyingError: nil))))
Below is the code:
let json = """
{
"businesses": [
{
"id": "2iwT3iutZvmqzmu7oOkWFw",
"alias": "dos-caminos-new-york-7",
"name": "Dos Caminos",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/eHvXPqv6iLcTXfT486z6JA/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/dos-caminos-new-york-7?adjust_creative=RzPd81IBxDPwaWBeNhRk8w&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=RzPd81IBxDPwaWBeNhRk8w",
"review_count": 598,
"categories": [
{
"alias": "mexican",
"title": "Mexican"
},
{
"alias": "bars",
"title": "Bars"
}
],
"rating": 3.5,
"coordinates": {
"latitude": 40.7593727,
"longitude": -73.9853281
},
"transactions": [
"pickup",
"delivery"
],
"price": "$$",
"location": {
"address1": "1567 Broadway",
"address2": "",
"address3": "",
"city": "New York",
"zip_code": "10036",
"country": "US",
"state": "NY",
"display_address": [
"1567 Broadway",
"New York, NY 10036"
]
},
"phone": "+12129181330",
"display_phone": "(212) 918-1330",
"distance": 55.57057440108928
}
]
}
""".data(using: .utf8)!
struct Restaurant: Decodable {
let name: String
let price: String?
let phone: String
let distance: Double
let imageUrl: String
let url: String
let categories: [Category]
let coordinates: Coordinates
enum CodingKeys: String, CodingKey {
case name
case price
case phone
case distance
case url
case imageUrl = "image_url"
case coordinates
case categories
}
struct Coordinates: Decodable {
let latitude: Double
let longitude: Double
}
struct Category: Decodable {
let alias: CategoryType
enum CategoryKeys: String, CodingKey {
case alias
}
enum CategoryType: Decodable {
case pizza
case burgers
case chinese
case mexican
case other(alias: String)
init(alias: String) {
switch alias {
case "pizza": self = .pizza
case "burgers": self = .burgers
case "chinese": self = .chinese
case "mexican": self = .mexican
default: self = .other(alias: alias)
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CategoryKeys.self)
if let alias = try container.decodeIfPresent(String.self, forKey: .alias) {
self = CategoryType(alias: alias)
} else {
self = .other(alias: "")
}
}
}
}
}
struct Response: Decodable {
let businesses: [Restaurant]
}
do {
let result = try JSONDecoder().decode(Response.self, from: json)
print(result.businesses.first?.name)
} catch {
print("error")
}
The problem is how you are decoding CategoryType. You are using a KeyedDecodingContainer when you should be using a SingleValueDecodingContainer. At this point in the decoding process, you are trying to decode the value of alias, which is just a single value (a String). KeyedDecodingContainers are only used for decoding key-based data structures like dictionaries.
Just change the init(from:) initializer of your CategoryType a bit, like so:
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let aliasRawValue = try container.decode(String.self)
self.init(alias: aliasRawValue)
}
... and remove the unnecessary CategoryKeys enum since we're not using a KeyedDecodingContainer anymore.

Swift JSOn Decoding Error: Expected to decode Array<Any> but found a dictionary instead

I have the following problem.
I send a request to FatSecret API to get details of a food.
If I decode a generic food it works, but if I get a branded food it doesn't work.
My struct looks like:
struct fatsecretFood : Decodable {
let food : Food
struct Food : Decodable {
let brand_name: String?
let food_id: String?
let food_name: String?
let food_type: String?
let food_url: String?
let servings : Servings
struct Servings : Decodable {
let serving : [Serving]
struct Serving : Decodable {
let serving_id: String?
let serving_description: String?
let serving_url: String?
let metric_serving_amount: String?
let metric_serving_unit: String?
let number_of_units: String?
let measurement_description: String?
let calories: String?
let carbohydrate: String?
let protein: String?
let fat: String?
let saturated_fat: String?
let polyunsaturated_fat: String?
let monounsaturated_fat: String?
let trans_fat: String?
let cholesterol: String?
let sodium: String?
let potassium: String?
let fiber: String?
let sugar: String?
let vitamin_a: String?
let vitamin_c: String?
let calcium: String?
let iron: String?
}
}
}
}
The code line with the error:
let data = Data(requestResponse.utf8)
var foodDetails: fatsecretFood!
var errors: fatsecretError!
do {
let result = try JSONDecoder().decode(fatsecretFood.self, from: data)
foodDetails = result
} catch { print(error) }
return foodDetails!
At that return I get the error:
typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "food", intValue: nil), CodingKeys(stringValue: "servings", intValue: nil), CodingKeys(stringValue: "serving", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))
The JSON of the example generic food (works) looks like:
{
"food": {
"food_id": "38884",
"food_name": "Chocolate Chip Cookies (Soft Type)",
"food_type": "Generic",
"food_url": "https://www.fatsecret.com/calories-nutrition/usda/chocolate-chip-cookies-(soft-type)",
"servings": {
"serving": [
{
"calcium": "0",
"calories": "130",
"carbohydrate": "16.75",
"cholesterol": "0",
"fat": "6.89",
"fiber": "0.9",
"iron": "4",
"measurement_description": "oz",
"metric_serving_amount": "28.350",
"metric_serving_unit": "g",
"monounsaturated_fat": "3.695",
"number_of_units": "1.000",
"polyunsaturated_fat": "0.992",
"potassium": "26",
"protein": "0.99",
"saturated_fat": "2.101",
"serving_description": "1 oz",
"serving_id": "38785",
"serving_url": "https://www.fatsecret.com/calories-nutrition/usda/chocolate-chip-cookies-(soft-type)?portionid=38785&portionamount=1.000",
"sodium": "92",
"vitamin_a": "0",
"vitamin_c": "0"
},
{
"calcium": "0",
"calories": "69",
"carbohydrate": "8.86",
"cholesterol": "0",
"fat": "3.64",
"fiber": "0.5",
"iron": "2",
"measurement_description": "cookie",
"metric_serving_amount": "15.000",
"metric_serving_unit": "g",
"monounsaturated_fat": "1.955",
"number_of_units": "1.000",
"polyunsaturated_fat": "0.525",
"potassium": "14",
"protein": "0.52",
"saturated_fat": "1.112",
"serving_description": "1 cookie",
"serving_id": "38786",
"serving_url": "https://www.fatsecret.com/calories-nutrition/usda/chocolate-chip-cookies-(soft-type)?portionid=38786&portionamount=1.000",
"sodium": "49",
"vitamin_a": "0",
"vitamin_c": "0"
},
{
"calcium": "2",
"calories": "458",
"carbohydrate": "59.10",
"cholesterol": "0",
"fat": "24.30",
"fiber": "3.2",
"iron": "13",
"measurement_description": "g",
"metric_serving_amount": "100.000",
"metric_serving_unit": "g",
"monounsaturated_fat": "13.034",
"number_of_units": "100.000",
"polyunsaturated_fat": "3.500",
"potassium": "93",
"protein": "3.50",
"saturated_fat": "7.411",
"serving_description": "100 g",
"serving_id": "61615",
"serving_url": "https://www.fatsecret.com/calories-nutrition/usda/chocolate-chip-cookies-(soft-type)?portionid=61615&portionamount=100.000",
"sodium": "326",
"vitamin_a": "0",
"vitamin_c": "0"
}
]
}
}
}
The JSON of the example branded food (doesn't work) looks like:
{
"food": {
"brand_name": "Pepperidge Farm",
"food_id": "61348",
"food_name": "Soft Baked Sugar Cookies",
"food_type": "Brand",
"food_url": "https://www.fatsecret.com/calories-nutrition/pepperidge-farm/soft-baked-sugar-cookies",
"servings": {
"serving": {
"calcium": "0",
"calories": "140",
"carbohydrate": "22",
"cholesterol": "10",
"fat": "5",
"fiber": "0",
"iron": "4",
"measurement_description": "serving",
"metric_serving_amount": "31.000",
"metric_serving_unit": "g",
"monounsaturated_fat": "1.5",
"number_of_units": "1.000",
"polyunsaturated_fat": "0.5",
"protein": "2",
"saturated_fat": "2.5",
"serving_description": "1 cookie",
"serving_id": "103910",
"serving_url": "https://www.fatsecret.com/calories-nutrition/pepperidge-farm/soft-baked-sugar-cookies",
"sodium": "90",
"sugar": "11",
"trans_fat": "0",
"vitamin_a": "0",
"vitamin_c": "0"
}
}
}
}
I don't find the error. Can someone help me here?
Thanks
Please learn to understand the error message, it's very easy to read
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "food", intValue: nil), CodingKeys(stringValue: "servings", intValue: nil), CodingKeys(stringValue: "serving", intValue: nil)], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
typeMismatch means you declared a wrong type (Swift.Array<Any>).
The codingPath array represents the key path to the affected location food/servings/serving.
The Expected part of the debugDescription describes your mistake (array) the found part describes the actual type (dictionary which is a struct in terms of Codable).
To be able to decode both array and dictionary types you have to add a custom initializer. The final type of serving is always an array.
struct Servings : Decodable {
let serving : [Serving]
private enum CodingKeys : String, CodingKey { case serving }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
let servingDictionary = try container.decode(Serving.self, forKey: .serving)
serving = [servingDictionary]
} catch DecodingError.typeMismatch {
serving = try container.decode([Serving].self, forKey: .serving)
}
}
}
Notes:
Name structs always with a starting uppercase letter.
Don't use snake_cased variable names. Add .convertFromSnakeCase strategy and declare camelCased names.
Don't declare carelessly everything as optional. It seems that at least the Serving object sends always all keys.
serving is not an Array as you suggest in your decoder. It is another dictionary as shown in the JSON file. Try to replace your code line:
let serving : [Serving] // before
let serving : Serving // after
If serving was an array in the JSON, it would look something like this:
"serving":[
{
"calcium":"8",
// ...
},
{
"calcium":"10",
// ...
}
]