struct for nested dictionary API in swift - ios

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

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

Parsing JSON File From iTunes API

The code below attempts parse through a json file that contains details about an artist and their music genre and was collected from the iTunes API. I am trying to extract the collectionName and artistName keys from the file with the use of the guard statement but it isn't working. It prints out parsing error.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
musicData()
}
let urlString = "https://itunes.apple.com/search?term=Alex&media=music&entity=album"
func musicData(){
let url = URL(string: urlString)
var request = URLRequest(url: url!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let myData = data else {
return
}
guard let rawJSON = try? JSONSerialization.jsonObject(with: myData, options: []),
let json = rawJSON as? [String:Any] else {
print("error serializing JSON")
return
}
guard let musicDictionary = json["collectionName"] as? [String:String] else {
print("Parsing error")
return
}
}
task.resume()
}
let collectionName = musicDictionary["collectionName"]
let artistName = musicDictionary["artistName"]
}
The property you're trying to access is a level deeper. Better approach is to use JSONDecoder.
Model:
struct Response: Codable {
let resultCount: Int
let results: [Result]
}
struct Result: Codable {
let wrapperType: WrapperType
let collectionType: CollectionType
let artistId, collectionId: Int
let amgArtistId: Int?
let artistName, collectionName, collectionCensoredName: String
let artistViewUrl: String?
let collectionViewUrl, artworkUrl60, artworkUrl100: String
let collectionPrice: Double?
let collectionExplicitness: CollectionExplicitness
let trackCount: Int
let copyright: String
let country: Country
let currency: Currency
let releaseDate, primaryGenreName: String
let contentAdvisoryRating: String?
}
enum CollectionExplicitness: String, Codable {
case explicit = "explicit"
case notExplicit = "notExplicit"
}
enum CollectionType: String, Codable {
case album = "Album"
}
enum Country: String, Codable {
case usa = "USA"
}
enum Currency: String, Codable {
case usd = "USD"
}
enum WrapperType: String, Codable {
case collection = "collection"
}
Decoding:
guard let myData = data else {
return
}
do {
let response = try JSONDecoder().decode(Response.self, from: myData)
print(response.results[0].collectionName)
} catch { print(error) }

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

how yo access item key in swift 4

if let url = URL(string: "https://mysit.com") {
URLSession.shared.dataTask(with: url) {
data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let data = data, error == nil,
let valueEncoding = response?.textEncodingName,
let getContent = String(data: data, encoding: valueEncoding.textEncodingToStringEncoding)
else { return }
print(getContent)
}.resume()
}
my Data
{"Regions":null,"Cities":[{"Id":"9605","Name":"YANBAA AS SENAYAH"},{"Id":"15","Name":"ABHA"},{"Id":"13","Name":"AD DAMMAM"},{"Id":"1542","Name":"AL BAHA"},{"Id":"14","Name":"AL MADINAH AL MUNAWWARAH"},{"Id":"2213","Name":"AR'AR"},{"Id":"11","Name":"BURAYDAH"},{"Id":"10","Name":"HAIL"},{"Id":"17","Name":"JAZAN"},{"Id":"6","Name":"MAKKAH AL MUKARRAMAH"},{"Id":"3417","Name":"NAJRAN"},{"Id":"3","Name":"RIYADH"},{"Id":"2237","Name":"SAKAKA"},{"Id":"1","Name":"TABUK"},
how to get an array list of values "Name" ,can you help me?
You can try
struct Root :Decodable{
let Cities:[InnerItem]
}
struct InnerItem :Decodable{
let Id:String
let Name:String
}
do {
let arr = try JSONDecoder().decode(Root.self, from: data)
print(arr.Cities)
}
catch {
print(error)
}
//
Note : This is the correct json structure
{"Regions":null,"Cities":[{"Id":"9605","Name":"YANBAA AS SENAYAH"},{"Id":"15","Name":"ABHA"},{"Id":"13","Name":"AD DAMMAM"},{"Id":"1542","Name":"AL BAHA"},{"Id":"14","Name":"AL MADINAH AL MUNAWWARAH"},{"Id":"2213","Name":"AR'AR"},{"Id":"11","Name":"BURAYDAH"},{"Id":"10","Name":"HAIL"},{"Id":"17","Name":"JAZAN"},{"Id":"6","Name":"MAKKAH AL MUKARRAMAH"},{"Id":"3417","Name":"NAJRAN"},{"Id":"3","Name":"RIYADH"},{"Id":"2237","Name":"SAKAKA"},{"Id":"1","Name":"TABUK"}]}
let responseData = try JSONSerialization.jsonObject(with: (response["Cities"] as! String).data(using: String.Encoding.utf8)!, options: []) as! [[String: Any]]
for item in responseData{
let name = item["Name"] as! String
}
Together with the decoding step. I added several guards to print an error if one comes up. It is generally good practice to throw the error and handle it on the appropriate level.
func work() {
guard let url = URL(string: "https://mysit.com") else {
fatalError("url is nil.")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard error == nil else {
fatalError("\(error!)")
}
guard let response = response as? HTTPURLResponse,
response.statusCode == 200 else {
fatalError("Response is nil.")
}
guard let data = data else {
fatalError("data is nil.")
}
decode(data: data)
}.resume()
}
func decode(data: Data) {
let decoder = JSONDecoder.init()
let welcome = try! decoder.decode(Welcome.self, from: data)
print(welcome.cities.first!)
}
The decoding helpers. enum CodingKeys are used to convert the lowercase attributes to the uppercase JSON attributes and back.
struct Welcome: Codable {
var regions: [Region]?
let cities: [City]
enum CodingKeys: String, CodingKey {
case regions = "Regions"
case cities = "Cities"
}
}
struct City: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
struct Region: Codable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
Some use services like Quicktype to convert JSON strings to the specific programming language. It makes things faster and simpler.

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