Getting objects from JSON - ios

My problem:
I use the site API - https://www.themealdb.com/api.php .
I want to get a list of all products. For this purpose, the link is https://www.themealdb.com/api/json/v1/1/categories.php
In my code, I created a structure:
struct Category: Decodable {
var idCategory: Int?
var strCategory: String?
var strCategoryDescription: String?
var strCategoryThumb: String?
}
Then I try to get to the address and get the data. I can convert the incoming data to JSON. It works.
Next, I want to convert the data and write it into an array of structures.
func load(url: String, completion: #escaping (_ objects: [Category])->()) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
//let json = try? JSONSerialization.jsonObject(with: data, options: [])
//print("JSONSerialization" + "\(json)")
let object = try JSONDecoder().decode([Category].self, from: data)
print("JSONDecoder" + "\(object)")
completion(object)
} catch {
print(error.localizedDescription)
}
}.resume()
}
But in this line I get an error in the console:
The data couldn’t be read because it isn’t in the correct format.
Probably a mistake in my structure. I can not deal with this problem.

There are two mistakes.
The actual error
Type 'Array' mismatch: Expected to decode Array but found a dictionary instead.
indicates that you are ignoring the root object, the dictionary with key categories
The value for key id is String not Int, note the double quotes in the JSON
Declare all struct members as non-optional constants as the JSON provides all keys the in dictionaries. And please map the horrible dictionary keys to more meaningful member names.
And print all errors and never .localizedDescription in a Decodable catch block.
struct Response: Decodable {
let categories: [Category]
}
struct Category: Decodable {
let id: String
let name: String
let description: String
let thumbnailURL: URL
private enum CodingKeys: String, CodingKey {
case id = "idCategory"
case name = "strCategory"
case description = "strCategoryDescription"
case thumbnailURL = "strCategoryThumb"
}
}
func load(url: String, completion: #escaping ([Category]) -> Void) {
guard let url = URL(string: url) else { return }
let session = URLSession.shared
session.dataTask(with: url) { (data, _, error) in
if let error = error { print(error); return }
do {
let response = try JSONDecoder().decode(Response.self, from: data!)
print("JSONDecoder", response)
completion(response.categories)
} catch {
print(error)
completion([])
}
}.resume()
}

You need two codables
struct MyData: Codable {
var categories: [Category]?
}
And
let object = try JSONDecoder().decode(MyData.self, from: data)

With a wrapper class you can fetch your categories. The following code works fine in Playground:
let json = """
{
"categories": [
{"idCategory": "1"},
{"idCategory": "2"}
]
}
"""
struct CategoryHolder: Codable {
var categories: [Category]
}
struct Category: Codable {
let idCategory: String?
let strCategory: String?
let strCategoryDescription: String?
let strCategoryThumb: String?
}
let jsonData = Data(json.utf8)
let categories = try JSONDecoder().decode(CategoryHolder.self, from: jsonData).categories

Related

How do I map API?

I'm new to swift language and trying to work on the API below..
https://api.foursquare.com/v2/venues/search?ll=40.7484,-73.9857&oauth_token=NPKYZ3WZ1VYMNAZ2FLX1WLECAWSMUVOQZOIDBN53F3LVZBPQ&v=20180616
I'm trying to parse the data and then serialise, however I'm not able to map the data.
struct Venue: Codable {
let id: String
let name: String
let contact: Location
}
struct Location: Codable {
let address: String
let postalCode: String
}
class DataService {
private init() {}
static let shared = DataService()
func getdata() {
guard let url = URL(string: "https://api.foursquare.com/v2/venues/search?ll=40.7484,-73.9857&oauth_token=NPKYZ3WZ1VYMNAZ2FLX1WLECAWSMUVOQZOIDBN53F3LVZBPQ&v=20180616") else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
guard let venues = try? JSONDecoder().decode([Venue].self, from: data) else { return }
print(venues[0].id)
} catch let jsonError {
print(jsonError)
}
}
}
task.resume()
}
}
I need to work on venues array ( mainly "id", "name", "location" ( "address", "postalCode" ))
I'm trying to use the codable and decodable, how do I get the the results, please help.
This is a very common mistake.
You ignore the root object (the dictionary containing the meta and response keys). And the venues are in the dictionary for key response, a sub-dictionary of the root object
struct Root : Decodable {
let response : Response
}
struct Response : Decodable {
let venues : [Venue]
}
struct Venue: Decodable {
let id: String
let name: String
let location : Location
}
struct Location: Decodable { // both struct members must be optional
let address: String?
let postalCode: String?
}
And – as Joakim already said in the comments – never ignore Decoding errors. Decoding errors are very descriptive. They contain the specific error message as well as the CodingPath of the error.
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let result = try JSONDecoder().decode(Root.self, from: data)
result.response.venues.forEach{print($0.id)}
} catch {
print(error)
}
}
}
task.resume()

Passing JSON result into a struct model

I am receiving a result from an API, I can iterate through the result. My understanding is I can pass the value into a model immediately.
Apple Developer article on struct models
My issue is I am not doing it properly and am receiving a nil value. Perhaps someone can see where I need to change. I am using Swift 4.2
Here is my struct model.
import Foundation
struct ProfileModel {
//MARK: Properties
var name: String
var email: String
var profileURL: String
//MARK: Initialization
}
extension ProfileModel{
init?(json: [String:AnyObject]) {
guard
let name = json["name"] as? String,
let email = json["email"] as? String,
let profileURL = json["profileURL"] as? String
else { return nil }
self.name = name
self.email = email
self.profileURL = profileURL
}
}
Here is my result code from my urlConnection. Let me know if we want to see the entire swift file
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:AnyObject] {
self.onSuccess(data: json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func onSuccess(data: [String:AnyObject]){
print("onSuccess")
let myProfile = ProfileModel(json: data)
//myProfile is nil while unwrapping
let title: String = myProfile!.name
print(title)
}
I could just iterate through the strings since I am able to print 'data'. I just figured it would be cleaner to put everything into a ProfileModel and manage that object as a whole.
This json is my more simple one which is why I used it for this question. I also can't remember but I had to use "[String:AnyObject]" to get the json properly. This was pulled directly from my terminal, this was the data being passed in my JsonResponse. The output json from Xcode has [] on the outside instead.
{
'detail': 'VALID',
‘name’: ‘Carson,
'email': ‘carson.skjerdal#somethingelselabs.com',
'pic_url': None
}
EDIT:
So my problem is solved, and ultimately moving to Codable was the key. Here is my fixed code for anyone who might need a working solution.
URLSession.shared.dataTask(with: request as URLRequest) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let gitData = try decoder.decode(ProfileModel.self, from: data)
print(gitData.name)
self.onSuccess(data: gitData)
} catch let err {
print("Err", err)
}
}.resume()
}
func onSuccess(data: ProfileModel){
print("onSuccess")
print(data.email)
}
My Codable Struct - slightly simplified
import Foundation
struct ProfileModel: Codable {
let detail, name, email: String
private enum CodingKeys: String, CodingKey {
case detail, email
case name = "firstname"
//case picUrl = "pic_url"
}
}
After "Codable" has been introduced I always uses that.
You can take your JSON ans pars it in to QuickType.io, and you will get a Struct that confirms to the codadable
// To parse the JSON, add this file to your project and do:
//
// let aPIResponse = try? newJSONDecoder().decode(APIResponse.self, from: jsonData)
import Foundation
struct APIResponse: Codable {
let detail, name, email, picUrl: String
enum CodingKeys: String, CodingKey {
case detail, name, email
case picUrl = "pic_url"
}
}

struct for nested dictionary API in swift

i'm trying to import JSON data from the v2 of coinmarketcap API. I had it working with v1 as it was an array, however the new version is a dictionary and i cant quite get my struct correct.
The API im using is : https://api.coinmarketcap.com/v2/ticker/?convert=AUD
My struct is set up as below:
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
var id: String
var symbol : String
var name : String
var priceAUD : quoteStruct
}
struct quoteStruct{
let aud : priceStruct
}
struct priceStruct{
let price : String
}
My code for fetching the data is:
var coins = [Coin]()
func getCoinData() {
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
self.coins = try JSONDecoder().decode([Coin].self, from: data)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
My code for fetching the data i have used the same as previously which worked with v1 of the API, however i don't think i made my struct correctly.
Thanks in advance!
Your response Changed i try to configure it by converting it to array of dictionary you will need to change quotes to be [String:priceStruct]
struct Coin: Decodable {
private enum CodingKeys: String, CodingKey {
case id,rank,symbol, name, priceAUD = "quotes"
}
var id: Int
var rank: Int
var symbol : String
var name : String
var priceAUD : [String: priceStruct]
}
struct priceStruct : Decodable{
let price : Double
}
func getCoinData() {
var coins = [Coin]()
let jsonURL = "https://api.coinmarketcap.com/v2/ticker/?convert=AUD"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
guard let data = data else { return }
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any], let resultData = json["data"] as? [String:Any] {
let dataObject = try JSONSerialization.data(withJSONObject: resultData.values.map({$0}) , options: .prettyPrinted)
coins = try JSONDecoder().decode([Coin].self, from: dataObject)
print(coins.count)
}
} catch {
print("Error is : \n\(error)")
}
}.resume()
}
You response in data parameter should be an array rather than a dictionary. You will not be able to iterate a dictionary over undefined keys. It would be good to get your response of API updated first.
But, If you wish to continue with the existing API response, first you need to convert your response in an array and use your Decodable structs as:
struct Coin: Decodable {
var id: String
var symbol : String
var name : String
var priceAUD : QuoteStruct
private enum CodingKeys: String, CodingKey {
case id = "rank", symbol, name, priceAUD = "quotes"
}
}
struct QuoteStruct: Decodable {
let aud : PriceStruct
}
struct PriceStruct: Decodable {
let price : String
}
Update your data parsing in API block as:
guard let responseData = data else { return }
do {
let json = try? JSONSerialization.jsonObject(with: responseData, options: [])
if let jsonData = json as? [String: Any], let dataObject = jsonData["data"] as? [Int: Any] {
let coinArray = dataObject.map { $0.1 }
if let jsonData = try? JSONSerialization.data(withJSONObject: coinArray, options: .prettyPrinted) {
coins = try JSONDecoder().decode([Coin].self, from: jsonData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
} catch {
print("Error is : \n\(error)")
}

JSONDecoder Not Parsing Data

I am trying to get data from this URL
https://api.opendota.com/api/heroStats
I have made a struct
struct HeroStats : Decodable {
let localized_name: String
let primary_attr: String
let attack_type: String
let legs: Int
let image: String
}
Top of my View Controller
var heros = [HeroStats]()
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://api.opendota.com/api/heroStats")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print(error.debugDescription)
}
do {
guard let data = data else { return}
self.heros = try JSONDecoder().decode([HeroStats].self, from: data)
DispatchQueue.main.async {
completed()
}
print(self.heros)
} catch {
print("JSON ERROR")
return
}
}.resume()
}
I always return JSON ERROR for some reason, although everything seems to be correct.
Try to read more on Codable/Encodable in Swift
Encoding and Decoding custom types
You may want to improve your code by making Swift names that differs from JSON names
struct HeroStats: Codable {
let name: String
let primaryAttribute: String
let attackType: String // Better to be an enum also
let legs: Int
let image: String?
enum CodingKeys: String, CodingKey {
case name = "localized_name"
case primaryAttribute = "primary_attr"
case attackType = "attack_type"
case legs
case image = "img"
}
}

Swift 4 decodable json arrays

So I am currently woking on a program to parse a JSON Api which is linked at the bottom
When I run the code I get some output but not all
Mainly for the optional type of Anime which is good because it shows that it works but I also want to access the name and release date and languages however I have no idea how to work with JSON arrays like this in swift 4. I will attach my current code below.
import UIKit
struct AnimeJsonStuff: Decodable {
let data: [AnimeDataArray]
}
struct AnimeDataArray: Decodable {
let type: String?
}
class OsuHomeController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
func jsonDecoding() {
let jsonUrlString = "https://kitsu.io/api/edge/anime"
guard let url = URL(string: jsonUrlString) else {return} //
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let animeJsonStuff = try JSONDecoder().decode(AnimeJsonStuff.self, from: data)
print(animeJsonStuff.data)
let animeDataArray = try JSONDecoder().decode(AnimeDataArray.self, from: data)
print(animeDataArray.type as Any)
} catch let jsonErr {
print("Error serializing json", jsonErr)
}
}.resume()
}
}
I have more code after that but it's just for setting up custom collectionViewCells.
Also here is the link to the api
Answer for title "Swift 4 decodable json arrays"
let decoder = JSONDecoder()
do {
let array = try decoder.decode([YouCodableStruct].self, from: response.data!)
debugPrint(array)
} catch {
debugPrint("Error occurred")
}
http://andrewmarinov.com/parsing-json-swift-4/
Please check below :
I din't add for all the keys. I added for some in attributes.
struct AnimeJsonStuff: Decodable {
let data: [AnimeDataArray]
}
struct AnimeLinks: Codable {
var selfStr : String?
private enum CodingKeys : String, CodingKey {
case selfStr = "self"
}
}
struct AnimeAttributes: Codable {
var createdAt : String?
private enum CodingKeys : String, CodingKey {
case createdAt = "createdAt"
}
}
struct AnimeRelationships: Codable {
var links : AnimeRelationshipsLinks?
private enum CodingKeys : String, CodingKey {
case links = "links"
}
}
struct AnimeRelationshipsLinks: Codable {
var selfStr : String?
var related : String?
private enum CodingKeys : String, CodingKey {
case selfStr = "self"
case related = "related"
}
}
struct AnimeDataArray: Codable {
let id: String?
let type: String?
let links: AnimeLinks?
let attributes: AnimeAttributes?
let relationships: [String: AnimeRelationships]?
private enum CodingKeys: String, CodingKey {
case id = "id"
case type = "type"
case links = "links"
case attributes = "attributes"
case relationships = "relationships"
}
}
Json parsing :
func jsonDecoding() {
let jsonUrlString = "https://kitsu.io/api/edge/anime"
guard let url = URL(string: jsonUrlString) else {return} //
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {return}
do {
let animeJsonStuff = try JSONDecoder().decode(AnimeJsonStuff.self, from: data)
for anime in animeJsonStuff.data {
print(anime.id)
print(anime.type)
print(anime.links?.selfStr)
print(anime.attributes?.createdAt)
for (key, value) in anime.relationships! {
print(key)
print(value.links?.selfStr)
print(value.links?.related)
}
}
} catch let jsonErr {
print("Error serializing json", jsonErr)
}
}.resume()
}

Resources