Decoding Json in swift - Array but now a dictionary - json

I previously decoded the following response correctly:
GET https://api.spoonacular.com/recipes/findByNutrients?minCarbs=10&maxCarbs=50&number=2
[
{
"calories": 210,
"carbs": "43g",
"fat": "3g",
"id": 90629,
"image": "https://spoonacular.com/recipeImages/90629-312x231.jpg",
"imageType": "jpg",
"protein": "1g",
"title": "Baked Apples in White Wine"
},
{
"calories": 226,
"carbs": "33g",
"fat": "10g",
"id": 284420,
"image": "https://spoonacular.com/recipeImages/284420-312x231.jpg",
"imageType": "jpg",
"protein": "2g",
"title": "Chocolate Silk Pie with Marshmallow Meringue"
}
]
Using this codable model:
struct RecipieAPI: Codable {
var calories : Int
var carbs : String
var fat : String
var id : Int
var image : String
var imageType : String
var protein : String
var title: String
}
However I am now using a different api endpoint with the following response:
GET https://api.spoonacular.com/recipes/complexSearch?query=pasta&maxFat=25&number=2
{
"offset": 0,
"number": 2,
"results": [
{
"id": 716429,
"calories": 584,
"carbs": "84g",
"fat": "20g",
"image": "https://spoonacular.com/recipeImages/716429-312x231.jpg",
"imageType": "jpg",
"protein": "19g",
"title": "Pasta with Garlic, Scallions, Cauliflower & Breadcrumbs"
},
{
"id": 715538,
"calories": 521,
"carbs": "69g",
"fat": "10g",
"image": "https://spoonacular.com/recipeImages/715538-312x231.jpg",
"imageType": "jpg",
"protein": "35g",
"title": "What to make for dinner tonight?? Bruschetta Style Pork & Pasta"
}
],
"totalResults": 86
}
And I am very unsure how I should change my code to get the array inside the dictionary appropriately. Any help would be really really appreciated :)
Note this is in my services class:
AF.request("https://api.spoonacular.com/recipes/complexSearch?apiKey=\(NetworkServices.apiKey)&diet=Whole30&minCarbs=\(minCarbs)&maxCarbs=\(maxCarbs)&number=\(number)", method: .get).responseJSON { (response) in
switch response.result {
case .success(_):
let decoder = JSONDecoder()
if let data = response.data {
do {
let nutrients = try decoder.decode([RecipieAPI].self, from: data)
completionHandler(nutrients, nil)
} catch let error {
completionHandler(nil, error)
}
}
case .failure(let error):
completionHandler(nil, error)
}
}
Link to documentation of api endpoints - sroll to bottom of page to see examples:
https://spoonacular.com/food-api/docs#Search-Recipes-by-Nutrients
https://spoonacular.com/food-api/docs#Search-Recipes-Complex

Add the new root model with the nested key results which you were decoding previously
struct Root: Codable {
var results : [RecipieAPI]
}
struct RecipieAPI: Codable {
var calories : Int?
var carbs : String
var fat : String
var id : Int
var image : String
var imageType : String
var protein : String
var title: String
}
Then
let nutrients = try decoder.decode(Root.self, from: data)
completionHandler(nutrients.results, nil)

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

No value associated with key CodingKeys with Unsplash API

Im trying to decode this json from the Unsplash API, but the ContentView is coming up as blank, and if I print the results then I am getting the "No value associated with key CodingKeys" error. Its strange, because I'm following this very new tutorial https://www.youtube.com/watch?v=CmOe9vNopjU. I am very puzzled, because this should be simple, but I am new to Swift.
here is the full error
"No value associated with key CodingKeys(stringValue: \"total\", intValue: nil) (\"total\").", underlyingError: nil))
here is the decoding request
class SearchObjectController : ObservableObject {
static let shared = SearchObjectController()
private init() {}
var token = "my client id"
#Published var results = [Result]()
#Published var searchText : String = "Forest"
func search () {
let url = URL(string: "https://api.unsplash.com/search/photos?query=\(searchText)")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Client-ID\(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let res = try JSONDecoder().decode(Results.self, from: data)
self.results.append(contentsOf: res.results)
print(self.results)
} catch {
print(error)
}
}
task.resume()
}
}
and these are the Structs i created for that request
struct Results : Codable {
var total : Int
var results : [Result]
}
struct Result : Codable {
var id : String
var description : String?
var urls : URLs
}
struct URLs : Codable {
var small : String
}
the json format for Unplash API Requests looks like this
{
"total": 133,
"total_pages": 7,
"results": [
{
"id": "eOLpJytrbsQ",
"created_at": "2014-11-18T14:35:36-05:00",
"width": 4000,
"height": 3000,
"color": "#A7A2A1",
"blur_hash": "LaLXMa9Fx[D%~q%MtQM|kDRjtRIU",
"likes": 286,
"liked_by_user": false,
"description": "A man drinking a coffee.",
"user": {
"id": "Ul0QVz12Goo",
"username": "ugmonk",
"name": "Jeff Sheldon",
"first_name": "Jeff",
"last_name": "Sheldon",
"instagram_username": "instantgrammer",
"twitter_username": "ugmonk",
"portfolio_url": "http://ugmonk.com/",
"profile_image": {
"small": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=32&w=32&s=7cfe3b93750cb0c93e2f7caec08b5a41",
"medium": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=64&w=64&s=5a9dc749c43ce5bd60870b129a40902f",
"large": "https://images.unsplash.com/profile-1441298803695-accd94000cac?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&fit=crop&h=128&w=128&s=32085a077889586df88bfbe406692202"
},
"links": {
"self": "https://api.unsplash.com/users/ugmonk",
"html": "http://unsplash.com/#ugmonk",
"photos": "https://api.unsplash.com/users/ugmonk/photos",
"likes": "https://api.unsplash.com/users/ugmonk/likes"
}
},
"current_user_collections": [],
"urls": {
"raw": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f",
"full": "https://hd.unsplash.com/photo-1416339306562-f3d12fefd36f",
"regular": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&s=92f3e02f63678acc8416d044e189f515",
"small": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=400&fit=max&s=263af33585f9d32af39d165b000845eb",
"thumb": "https://images.unsplash.com/photo-1416339306562-f3d12fefd36f?ixlib=rb-0.3.5&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&s=8aae34cf35df31a592f0bef16e6342ef"
},
"links": {
"self": "https://api.unsplash.com/photos/eOLpJytrbsQ",
"html": "http://unsplash.com/photos/eOLpJytrbsQ",
"download": "http://unsplash.com/photos/eOLpJytrbsQ/download"
}
},
// more photos ...
]
}
It's a common practice for a lot of APIs to return a detailed description of the error in the response body together with the error code when the request can't be processed. Seems that it's your case since the JSON parsing fails on the first field of the structure. Check the error and response.statusCode in the dataTask's completion block first to be sure that the API has successfully processed your request and returned valid data.
It was a missing a space between Client-ID and (token) that was causing the 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)

How to parse JSON in Swift with dynamic filename using Codable

I am trying to parse the following JSON into a class, but don't know how to approach this particular case.
Here is the api: https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&indexpageids&titles=bird
I am trying to get the title and extract, but in order to do so, it requires that I go through the unique pageid. How would I do this using the Codable protocol?
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "bird",
"to": "Bird"
}
],
"pageids": [
"3410"
],
"pages": {
"3410": {
"pageid": 3410,
"ns": 0,
"title": "Bird",
"extract": "..."
}
}
}
}
My suggestion is to write a custom initializer:
Decode pages as [String:Page] dictionary and map the inner dictionaries according to the values in pageids
let jsonString = """
{
"batchcomplete": "",
"query": {
"normalized": [
{
"from": "bird",
"to": "Bird"
}
],
"pageids": [
"3410"
],
"pages": {
"3410": {
"pageid": 3410,
"ns": 0,
"title": "Bird",
"extract": "..."
}
}
}
}
"""
struct Root : Decodable {
let query : Query
}
struct Query : Decodable {
let pageids : [String]
let pages : [Page]
private enum CodingKeys : String, CodingKey { case pageids, pages }
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.pageids = try container.decode([String].self, forKey: .pageids)
let pagesData = try container.decode([String:Page].self, forKey: .pages)
self.pages = self.pageids.compactMap{ pagesData[$0] }
}
}
struct Page : Decodable {
let pageid, ns : Int
let title, extract : String
}
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Root.self, from: data)
print(result)
} catch {
print(error)
}

Swift 4 json decoder error flickr

I am trying out the new JSONDecoder() in swift 4 and am trying to parse the following flickr API response : (https://api.flickr.com/services/rest/?api_key=api_key&method=flickr.photos.search&format=json&per_page=25&text=hello&nojsoncallback=1). However I get an error saying that "error processing json data: The data couldn’t be read because it isn’t in the correct format." However the data seems to be in the correct format to me. Am I doing anything wrong?
let dataTask = session.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("json error: \(error.localizedDescription)")
return
} else if let data = data {
print(response)
do {
let decoder = JSONDecoder()
print(data)
let flickrPhotos = try decoder.decode(FlickrImageResult.self, from: data)
} catch {
dump(data)
print("json error: \(error.localizedDescription)")
}
}
}
dataTask.resume()
My data models are
struct FlickrImageResult : Codable {
var photos : FlickrPhoto? = nil
}
struct FlickrPhoto : Codable {
var photo : [FlickrURLs]? = nil
}
struct FlickrURLs: Codable {
var id : String? = nil
var owner: String? = nil
var secret: String? = nil
var server: String? = nil
var farm: String? = nil
}
I changed the number of images requested to 2 and Printing out the data returns
Optional({"photos":{"page":1,"pages":120197,"perpage":2,"total":"240393","photo":[{"id":"36729752762","owner":"152440263#N02","secret":"e62ba3e18b","server":"4432","farm":5,"title":"Hello there","ispublic":1,"isfriend":0,"isfamily":0},{"id":"36729384952","owner":"141041947#N06","secret":"bc0e5af630","server":"4380","farm":5,"title":"POST\ud83d\udd25 #891 | Hello Tuesday | Krescendo","ispublic":1,"isfriend":0,"isfamily":0}]},"stat":"ok"})
The only problem with your model is that farm is actually an Int. Here's a more complete version of your model accoirding to the docs (https://www.flickr.com/services/api/flickr.photos.search.html):
struct FlickrImageResult: Codable {
let photos : FlickrPagedImageResult?
let stat: String
}
struct FlickrPagedImageResult: Codable {
let photo : [FlickrURLs]
let page: Int
let pages: Int
let perpage: Int
let total: String
}
struct FlickrURLs: Codable {
let id : String
let owner: String
let secret: String
let server: String
let farm: Int
// let title: String
// If needed, camel-case and use CodingKeys enum
//let ispublic: Int
//let isfriend: Int
//let isfamily: Int
}
let jsonData = """
{
"photos": {
"page": 1,
"pages": 13651,
"perpage": 25,
"total": "341263",
"photo": [
{
"id": "36499638920",
"owner": "55126206#N07",
"secret": "7e82dee0ba",
"server": "4346",
"farm": 5,
"title": "[BREATHE]-Ririko,Sayaka&Chiyoko",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36724435032",
"owner": "92807782#N04",
"secret": "6d830d4a75",
"server": "4354",
"farm": 5,
"title": "Serendipity Designs # SWANK August 2017",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36087089863",
"owner": "152685136#N08",
"secret": "a4a3f2fe0a",
"server": "4365",
"farm": 5,
"title": "Hello Kitty Scooter",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36086949593",
"owner": "151818203#N02",
"secret": "fc1207d373",
"server": "4334",
"farm": 5,
"title": "Hip, Hip! It's Chip!",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
},
{
"id": "36498504410",
"owner": "148300038#N02",
"secret": "5c7f6ff3e1",
"server": "4391",
"farm": 5,
"title": "Hello Kotti",
"ispublic": 1,
"isfriend": 0,
"isfamily": 0
}
]
},
"stat": "ok"
}
""".data(using: .utf8)!
let flickrPhotos = try! JSONDecoder().decode(FlickrImageResult.self, from: jsonData)
print(flickrPhotos)
P.S: The message error processing json data: The data couldn’t be read because it isn’t in the correct format. is the localized error message, use print(error) instead of print(errorlocalizedDescription) to obtain all the error data available (in your case, it will print that there's an issue when trying to decode the farm key).