Making an api request using URLSession.shared.dataTask - ios

I'm making an api request:
var urlRaw = bookSummaryReadsDomainUrl + apiKey;
let url = URL(string: urlRaw.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)
let task = URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching book summary reads: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let flurryItems = try? JSONDecoder().decode(FlurrySummary.self, from: data) {
completionHandler(flurryItems.rows ?? [])
}
})
task.resume()
to an endpoint that returns the following data
{
"rows": [
{
"dateTime": "2020-07-04 00:00:00.000-07:00",
"event|name": "BookSummaryRead",
"paramName|name": "bookId",
"paramValue|name": "elon-musk",
"count": 12
},
...
]
import Foundation
struct FlurrySummary: Codable {
var rows: [FlurryItem]?
enum CodingKeys: String, CodingKey {
case rows = "rows"
}
}
struct FlurryItem: Codable {
var name: String?
var event: String?
var value: String?
var count: String?
var date: String?
enum CodingKeys: String, CodingKey {
case name = "paramName|name"
case event = "event|name"
case value = "paramValue|name"
case count = "count"
case date = "dateTime"
}
}
For some reason the JSONDecoder.decode part is not working. It's not filling up the flurryItems and flurryItems.rows = nil. What am I doing wrong?

The property count in FlurryItem has to be of type Int.
var count: Int?
You have to catch the Error that are thrown.
do {
if let data = data,
let flurryItems = try JSONDecoder().decode(FlurrySummary.self, from: data) {
completionHandler(flurryItems.rows ?? [])
}
} catch { print(error) }
Also, you don't need the CodingKeys in FlurrySummary since the property name is the same.
struct FlurrySummary: Codable {
var rows: [FlurryItem]?
}
Note: Also, avoid using optional declaration if the property never becomes null.

Related

swift decode json data

I have following json data returns form php.
{"Response":"OK","Data":[{"id":"1","organization_name":"Organization","description":"Description","address":"Address1, Ny, USA"}]}
In need to decode it using swift
Below is my code.
struct OrgData: Decodable {
let data: [Data]
enum CodingKeys : String, CodingKey {
case data = "Data"
}
}
struct Data: Decodable {
let id: String
let address: String
let description: String
let organization_name: String
}
and I am decoding it using
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
guard let dataObj = try? JSONDecoder().decode(OrgData.self, from: data) else {
print("Error: Couldn't decode data ")
return
}
..................
But no data I am getting in dataObj.
I am referring this article
https://roadfiresoftware.com/2018/02/how-to-parse-json-with-swift-4/
Create a function in a helper class
func decodedObject<T: Decodable>(_ type: T.Type, dictionaryData: JSONDictionary) throws -> T? {
guard let jsonData = try? JSONSerialization.data(withJSONObject: dictionaryData,
options: JSONSerialization.WritingOptions.prettyPrinted) else {
return nil
}
let decodedData = try self.decode(type, from: jsonData)
return decodedData
}
Now call it like follows:
guard let dataObj = try? JSONDecoder().decodedObject(OrgData.self, dictionaryData: data) else {
print("Error: Couldn't decode data ")
return
}
There were some issues with how your structs were configured:
There is no field for the "Response" value
Data is an array, not a String
The following appears to properly output the json you give:
import Foundation
let json =
"""
{"Response":"OK","Data":[{"id":"1","organization_name":"Organization","description":"Description","address":"Address1, Ny, USA"}]}
"""
// MARK: - OrgData
struct OrgData: Codable {
let response: String
let data: [Datum]
enum CodingKeys: String, CodingKey {
case response = "Response"
case data = "Data"
}
}
// MARK: - Datum
struct Datum: Codable {
let id, organizationName, datumDescription, address: String
enum CodingKeys: String, CodingKey {
case id
case organizationName = "organization_name"
case datumDescription = "description"
case address
}
}
guard let data = json.data(using: .utf8) else {
return
}
do {
let dataObj = try JSONDecoder().decode(OrgData.self,
from: data)
print(dataObj)
// Optional(__lldb_expr_1.OrgData(response: "OK", data: [__lldb_expr_1.Datum(id: "1", organizationName: "Organization", datumDescription: "Description", address: "Address1, Ny, USA")]))
} catch {
print(error)
}

Api request IOS using flurry api

I have the following code that makes an API request to a flurry.com API endpoint and decodes the request.
let url = URL(string: bookStarRateDomainUrl)!
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error {
print("Error with fetching book star rates: \(error)")
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(response)")
return
}
if let data = data,
let flurryItems = try? JSONDecoder().decode(FlurryItem.self, from: data) {
completionHandler(flurryItems.results ?? [])
}
})
task.resume()
The problem is that I cannot use .decode(FlurryItem.self, because the values I get back from the API endpoint is this:
[{
dateTime:2020-06-05 00:00:00.000-07:00
event|name:BookStarRate
paramName|name:bookId
paramValue|name:why-buddism-is-true
count:3
}]
notice how the variable name is "paramName|name". The | makes it impossible to name a variable for that item. What can I do instead?
1- You need to use enum
struct Root: Codable {
var date: String
var name: String
var id: Int
var value: String
var count: Int
enum CodingKeys: String, CodingKey {
case date = "dateTime"
case name = "event|name"
case id = "paramName|name"
case value = "paramValue|name"
case count
}
}
2- You should use [FlurryItem].self
let flurryItems = try? JSONDecoder().decode([FlurryItem].self, from: data)

Getting objects from JSON

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

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

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