I'm making an api call and getting the response like so..
if let data = NSData(contentsOf: NSURL(string: "http://test.chatongo.in/testdata.json")! as URL) {
do {
if let response = try JSONSerialization.jsonObject(with: data as Data, options: []) as? NSDictionary {
print("THE RESPONSE IS: \(response)")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
And the response I get like so...
THE RESPONSE IS: {
Message = Success;
Status = 200;
data = {
Records = (
{
Id = 1;
collectedValue = 500;
endDate = "10/06/2018";
mainImageURL = "http://iphonedeveloperguide.com/oneinr/project1.jpg";
shortDescription = "This foundation will bring smile on there faces";
startDate = "05/05/2018";
title = "Smile Crowdfunding";
totalValue = 5000;
},
{
Id = 2;
collectedValue = 750;
endDate = "08/06/2018";
mainImageURL = "http://iphonedeveloperguide.com/oneinr/project10.jpg";
shortDescription = "This foundation will help animals";
startDate = "05/05/2018";
title = "Animal Funding";
totalValue = 20000;
}
);
TotalRecords = 10;
};
}
But how do I parse this json and get the individual elements out of it including the image, that I'm not able to figure out.
You need
import UIKit
class ViewController: UIViewController {
var records = [Record]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
URLSession.shared.dataTask(with: URL(string: "http://test.chatongo.in/testdata.json")!) { (data, response, error) in
guard let data = data else { return }
do {
let res = try JSONDecoder().decode(Root.self, from: data)
self.records = res.data.records
print(res.data.records)
// if it's a collection/table wrap the reload here inside DispatchQueue.main.async
}
catch {
print(error)
}
}.resume()
}
}
// MARK: - Empty
struct Root: Codable {
let status: Int
let message: String
let data: DataClass
enum CodingKeys: String, CodingKey {
case status = "Status"
case message = "Message"
case data
}
}
// MARK: - DataClass
struct DataClass: Codable {
let totalRecords: Int
let records: [Record]
enum CodingKeys: String, CodingKey {
case totalRecords = "TotalRecords"
case records = "Records"
}
}
// MARK: - Record
struct Record: Codable {
let id: Int
let title, shortDescription: String
let collectedValue, totalValue: Int
let startDate, endDate: String
let mainImageURL: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case title, shortDescription, collectedValue, totalValue, startDate, endDate, mainImageURL
}
}
Tip : Don't use NS stuff in swift and avoid using Data(contentsOf: as it blocks the main thread
There are multiple ways, one way to create a modelobject of
struct RecordItem : Codable {
var id : Int?
var collectedValue : Int?
var endDate : String?
var mainImageURL: String?
var shortDescription : String?
var title :String?
var startDate : String?
var totalValue : Int?
}
and
struct Records : Codable {
var items : [RecordItem]?
}
and use this. = let item = data [index]
print (item. collectedValue) and so on.
seconds methods, you already created dict, then extract all keys and array objects using ["Key": "Value"] and set to any variable.
Related
I am trying to get the value of "price" key which is "25"
I am getting this response Json From Backend
{
"errorCode": 0,
"message": "Request successfully served.",
"data": {
"games": {
"TWELVEBYTWENTYFOUR": {
"jackpot_amount": "KES 40,000.00",
"draw_date": "2021-05-21 10:59:45",
"extra": {
"jackpotAmount": 40000,
"unitCostJson": [
{
"currency": "KES",
"price": 25
}
]
},
}
},
"currentTime": {
"date": "2021-05-20 22:28:18.738038"
}
}
}
This is my code so far :
fetchData { (dict, error) in
let playerLoginInfo = dataDict["data"] as? NSDictionary
let playerGameInfo = playerLoginInfo?.value(forKey: "games") as? NSDictionary
if let TWELVEBYTWENTYFOUR = playerGameInfo?.value(forKey: "TWELVEBYTWENTYFOUR") as? NSDictionary {
let extra = TWELVEBYTWENTYFOUR.value(forKey: "extra") as? NSDictionary
let unitCostJson = extra?.value(forKey: "unitCostJson") as? NSArray
print("price")
print(unitCostJson?.value(forKey: "price") as? Any)
}
}
I get this is console :
Optional(Optional(<__NSSingleObjectArrayI 0x600001f091d0>(
25
)
))
I have seen this question How can I access values within Optional NSSingleObjectArrayI? but I couldn't figure out a solution
Edit:
I have now used Codeable to get data:
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: Games
let currentTime: CurrentTime
}
struct Games: Codable {
let game_code: String
let datetime: String
let estimated_jackpot: String
let guaranteed_jackpot: String
let jackpot_title: String
let jackpot_amount: String
let draw_date: String
let extra: Extra
let next_draw_date: String
let active: String
}
struct Extra: Codable {
let currentDrawNumber: Int
let currentDrawFreezeDate: String
let currentDrawStopTime: String
let jackpotAmount: Int
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
let timezone_type: Int
let timezone: String
}
I'm trying to get value from price now with this code
do{
let resp:Resp = try JSONDecoder().decode(Resp.self , from:data);
let data = resp.data
let games = data.games
let extra = games.extra
let unitCostJson = extra.unitCostJson
print(unitCostJson[0].price)
}
catch{
GlobalFunctions.shared.callOnMainThread {
self.showAlert(Message: "Something went wrong. Please retry.")
}
}
It is going into catch
How should I get the data inside on the unitCostJson now??
I butchered your struct and removed any irrelevant properties (compared to the json), if you want to add them back then you need to use an CodingKey enum
struct Resp: Codable {
let errorCode: Int
let message: String
let data: Dat
}
struct Dat: Codable {
let games: [String:Games]
let currentTime: CurrentTime
}
struct Games: Codable {
let extra: Extra
}
struct Extra: Codable {
let unitCostJson: [UnitCostJson]
}
struct UnitCostJson: Codable {
let currency: String
let price: Int
}
struct CurrentTime: Codable {
let date: String
}
Now you can access the unitCost like this
let unitCost = resp.data.games["TWELVEBYTWENTYFOUR"]?.extra.unitCostJson
I am attempting to get specific information from the JSON below within the "list" section.
Sample of JSON
{
city = {
coord = {
lat = "37.323";
lon = "-122.0322";
};
country = US;
id = 5341145;
name = Cupertino;
population = 58302;
sunrise = 1579965395;
sunset = 1580001833;
timezone = "-28800";
};
cnt = 40;
cod = 200;
list = (
{
clouds = {
all = 99;
};
dt = 1580018400;
"dt_txt" = "2020-01-26 06:00:00";
main = {
"feels_like" = "286.89";
"grnd_level" = 1011;
humidity = 78;
pressure = 1020;
"sea_level" = 1020;
temp = "287.29";
"temp_kf" = "-0.68";
"temp_max" = "287.97";
"temp_min" = "287.29";
};
sys = {
pod = n;
};
weather = (
{
description = "overcast clouds";
icon = 04n;
id = 804;
main = Clouds;
}
);
wind = {
deg = 183;
speed = "0.77";
};
},
{
clouds = {
all = 88;
};
dt = 1580029200;
"dt_txt" = "2020-01-26 09:00:00";
main = {
"feels_like" = "286.55";
"grnd_level" = 1011;
humidity = 91;
pressure = 1020;
"sea_level" = 1020;
temp = "286.64";
"temp_kf" = "-0.51";
"temp_max" = "287.15";
"temp_min" = "286.64";
};
rain = {
3h = "1.88";
};
sys = {
pod = n;
};
weather = (
{
description = "light rain";
icon = 10n;
id = 500;
main = Rain;
}
);
wind = {
deg = 140;
speed = "1.04";
};
},
However I am confused on how to structure my objects and decode the JSON to grab the values I require. Here is my Codable class
class FiveDayWeatherInformation: Codable {
struct Weather: Codable {
var description: String
var icon: String
enum CodingKeys: String, CodingKey {
case description
case icon
}
}
struct Main: Codable {
var temp: Double
enum CodingKeys: String, CodingKey {
case temp
}
}
struct ThreeHourItem: Codable {
var dateText: String
var weather: Weather
var main: Main
enum CodingKeys: String, CodingKey {
case main
case dateText = "dt_txt"
case weather
}
}
struct RootList: Codable {
let list: [[String:ThreeHourItem]]
enum CodingKeys: String, CodingKey {
case list
}
}
var rootList: RootList
enum CodingKeys: String, CodingKey {
case rootList = "list"
}
required public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.rootList = try values.decode(RootList.self, forKey: .rootList)
}
}
And this is where I attempt to get the values
func fetchFiveDayForecastForCoordinates(latitude: CLLocationDegrees, longitude: CLLocationDegrees, completion: #escaping (_ weatherForecasts:[FiveDayWeatherInformation]?, Error?) -> Void) {
// to do
let url = URL(string: "http://api.openweathermap.org/data/2.5/forecast?lat=\(latitude)&lon=\(longitude)&APPID=APPID")
let urlRequest = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
guard let data = data, error == nil else {
completion(nil, APIError.apiServiceError)
return
}
do {
let forecast = try JSONDecoder().decode(FiveDayWeatherInformation.RootList.self, from:data)
print(forecast)
} catch {
print(error)
}
}
task.resume()
}
However I keep getting this warning.
keyNotFound(CodingKeys(stringValue: "main", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "list", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), _JSONKey(stringValue: "clouds", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"main\", intValue: nil) (\"main\").", underlyingError: nil))
I believe I have structured my Codable class wrong but can't seem to figure out why.
The value for key main in list is an array. Change the structs to
struct RootList : Decodable {
let list : [List]
}
struct List : Decodable {
let main : Main
let weather : [Weather]
let dateText : String
enum CodingKeys: String, CodingKey { case main, dateText = "dtTxt", weather }
}
struct Weather : Decodable {
let icon : String
let description: String
}
struct Main : Decodable {
let temp : Double
}
Is the key really dt_txt? I get dtTxt
i have a string of this form:
[{"LocationsItems":[{"ItemId":4,"LocationId":3,"Qty":34},{"ItemId":19,"LocationId":3,"Qty":55}]}];
i need to convert this to an array of object, i tried this:
let data = convertedData.data(using: .utf8)!
do {
if let jsonArray = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? [Dictionary<String,Any>]
{
print(jsonArray)
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
}
and returned this , which is an array of dictionaries:
[["LocationsItems": <__NSArrayI 0x600003a1c100>(
{
ItemId = 4;
LocationId = 3;
Qty = 34;
},
{
ItemId = 19;
LocationId = 3;
Qty = 55;
}
)
]]
how can i extract the objects from it?
thanks
You can create a struct of LocationItem.
struct LocationItem {
var itemId: Int?
var locationId: Int?
var qty: Int?
}
var innerDictionary = jsonArray[0] as? [String: Any]
var arrayOfDict = innerDictionary["LocationsItems"] as? [[String: Any]]
var locationsItems = arrayOfDict.map {
LocationItem(itemId: $0["ItemId"], locationId: $0["LocationId"], qty: $0["Qty"])
}
Alternatively, you can use Codable:
struct LocationItem: Codable {
var itemId: Int?
var locationId: Int?
var qty: Int?
enum CodingKeys: String, CodingKey {
case itemId = "ItemId"
case locationId = "LocationId"
case qty = "Qty"
}
}
let decoder = JSONDecoder()
do {
let responseStructure = try decoder.decode([[String:[[String:Any]]]], from: data)
} catch {
// failed
}
let locationItems = responseStructure[0]["LocationsItems"]
I am trying to load an icon image form the API OpenWeatherMap, and display it in an ImageView. I am trying to load it into the imageView 'iconImage'. I have successfully loaded the JSON data for the location and humidity, as they are Strings, but the Icon data is also a String and I cannot get it to display as a UIImage.
Code below:
My JSON Structs below:
struct Coordinate : Decodable {
let lat, lon : Double?
}
struct Weather : Decodable {
var id : Int?
var main, myDescription, icon : String?
enum CodingKeys : String, CodingKey {
case id = "id"
case main = "main"
case icon = "icon"
case myDescription = "description"
}
}
struct Sys : Decodable {
let type, id : Int?
let sunrise, sunset : Date?
let message : Double?
let country : String?
}
struct Main : Decodable {
let temp, tempMin, tempMax : Double?
let pressure, humidity : Int?
}
struct Wind : Decodable {
let speed : Double?
let deg : Int?
}
struct MyWeather : Decodable {
let coord : Coordinate?
let cod, visibility, id : Int?
let name : String?
let base : String?
let weather : [Weather]?
let sys : Sys?
let main : Main?
let wind : Wind?
let dt : Date?
}`enter code here`
View controller below:
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=e7b2054dc37b1f464d912c00dd309595&units=Metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
//Decoder
do {
if (self.iconImage != nil)
{
if let gicon = weatherData.weather?.first?.icon {
DispatchQueue.main.async {
self.iconImage.image! = gicon
}
}
}
if (self.LocationLabel != nil)
{
if let gmain = weatherData.name {
print(gmain)
DispatchQueue.main.async {
self.LocationLabel.text! = "Current Weather in: " + String (gmain)
}
}
}
if (self.HumidityLabel != nil)
{
if let ghumidity = weatherData.main?.humidity
{
print(ghumidity, "THIS IS HUMIDITY")
DispatchQueue.main.async {
self.HumidityLabel.text! = String (ghumidity)
}
}
}
Use Kingfisher
It creates an extension in ImageView. You will use self.imageview.kf.setImage(with: "url")
Icon is the id of the image , you need to append it to this url and load it
http://openweathermap.org/img/w/10d.png // here ------ id = 10d
suppose you'll use SDWebImage , then do this
let urlStr = "http://openweathermap.org/img/w/\(gicon).png"
self.iconImage.sd_setImage(with: URL(string:urlStr), placeholderImage: UIImage(named: "placeholder.png"))
See here in Docs
How can I append into an array using JSON Model Class.
Here is my JSON API Request: https://developer.github.com/v3/search/
import Foundation
typealias GitDecode = [GitDecodeElement]
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Item]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Item: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String
let size, stargazersCount, watchersCount: Int
let language: String
let forksCount, openIssuesCount: Int
let masterBranch, defaultBranch: String
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case masterBranch = "master_branch"
case defaultBranch = "default_branch"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
And here I have my class Model where I'm saying what I want to extract from that response:
import Foundation
struct Git: Codable{
let totalCount: Int
let items: GitItem
init ( totalCount: Int,
itemID: Int, itemDescription: String,
ownerID: Int, ownerAvatarURL: String) {
self.totalCount = totalCount
self.items = GitItem(id: itemID, description: itemDescription, owner: GitOwner(id: ownerID, avatarURL: ownerAvatarURL))
}
}
struct GitItem: Codable{
let id: Int
let description: String
let owner: GitOwner
}
struct GitOwner: Codable {
let id: Int
let avatarURL: String
}
Now I'm stuck when I try to append into my array all my custom properties because itemID, itemDescription, ownerID and ownerAvatarURL are in different classes.
Here is how I'm trying to get all the that properties from JSON using JSONDecoder:
import UIKit
class MainViewController: UIViewController {
var gitRepositoriesArray = [Git]()
override func viewDidLoad() {
super.viewDidLoad()
}
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://developer.github.com/v3/search/")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount,
itemID: <#T##Int#> , itemDescription: <#T##String#>, ownerID: <#T##Int#>, ownerAvatarURL: <#T##String#>))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
}
Complete working playground code:
Makes a search against the API for Swift related repos.
Parses them, adds them to the array and prints out some basic information on each one. (fullName, name, avatarUrl
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
struct GitDecodeElement: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [Repo]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct Repo: Codable {
let id: Int
let nodeID, name, fullName: String
let owner: Owner
let itemPrivate: Bool
let htmlURL, description: String
let fork: Bool
let url, createdAt, updatedAt, pushedAt: String
let homepage: String?
let size, stargazersCount, watchersCount: Int
let language: String?
let forksCount, openIssuesCount: Int
let score: Double
enum CodingKeys: String, CodingKey {
case id
case nodeID = "node_id"
case name
case fullName = "full_name"
case owner
case itemPrivate = "private"
case htmlURL = "html_url"
case description, fork, url
case createdAt = "created_at"
case updatedAt = "updated_at"
case pushedAt = "pushed_at"
case homepage, size
case stargazersCount = "stargazers_count"
case watchersCount = "watchers_count"
case language
case forksCount = "forks_count"
case openIssuesCount = "open_issues_count"
case score
}
}
struct Owner: Codable {
let login: String
let id: Int
let nodeID, avatarURL, gravatarID, url: String
let receivedEventsURL, type: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case receivedEventsURL = "received_events_url"
case type
}
}
var gitRepositoriesArray = [Repo]()
// Download Git Repositories from API
func parseGitRepositories() {
let url = URL(string: "https://api.github.com/search/repositories?q=topic:swift+topic:ios")
var request = URLRequest(url: url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {
print(error?.localizedDescription)
return
}
do {
let gitRepositoriesList = try JSONDecoder().decode(GitDecodeElement.self, from: data!)
gitRepositoriesArray = gitRepositoriesArray + gitRepositoriesList.items
print(gitRepositoriesArray.count)
for repo in gitRepositoriesList.items {
print("\(repo.fullName) - \(repo.name) - \(repo.owner.avatarURL)")
}
} catch {
let str = String(data: data!, encoding: .utf8)
print(str)
print(error)
}
}.resume()
}
parseGitRepositories()
PlaygroundPage.current.needsIndefiniteExecution = true
Output:
30
justjavac/free-programming-books-zh_CN - free-programming-books-zh_CN - https://avatars1.githubusercontent.com/u/359395?v=4
dkhamsing/open-source-ios-apps - open-source-ios-apps - https://avatars0.githubusercontent.com/u/4723115?v=4
matteocrippa/awesome-swift - awesome-swift - https://avatars2.githubusercontent.com/u/475463?v=4
xitu/gold-miner - gold-miner - https://avatars2.githubusercontent.com/u/10482599?v=4
lkzhao/Hero - Hero - https://avatars1.githubusercontent.com/u/3359850?v=4
ReactiveX/RxSwift - RxSwift - https://avatars1.githubusercontent.com/u/6407041?v=4
realm/realm-cocoa - realm-cocoa - https://avatars0.githubusercontent.com/u/7575099?v=4
CocoaPods/CocoaPods - CocoaPods - https://avatars1.githubusercontent.com/u/1189714?v=4
CosmicMind/Material - Material - https://avatars1.githubusercontent.com/u/10069574?v=4
// rest truncated
Notice how I use less models than you do in your code. There is no need to duplicate code, just use the parts you want, when you want them.
There is some problem in you Git structure. I have corrected the initializer like this:
struct Git: Codable{
let totalCount: Int
var items = [GitItem]()
init(totalCount: Int, items: [Item]) {
self.totalCount = totalCount
for item in items {
self.items.append(GitItem(id: item.id, description: item.description, owner: GitOwner(id: item.owner.id, avatarURL: item.owner.avatarURL)))
}
}
So your parsing method will be changed accordingly:
// Download Git Repositories from API
func parseGitRepositories(){
let url = URL(string: "https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil{
do{
let gitRepositoriesList = try JSONDecoder().decode(GitDecode.self, from: data!)
for eachRepo in gitRepositoriesList{
self.gitRepositoriesArray.append(Git(totalCount: eachRepo.totalCount, items: eachRepo.items))
}
}catch{
print(error.localizedDescription)
}
}
}.resume()
}
Also note the change of url to https://api.github.com/search/repositories?q=tetris+language:assembly&sort=stars&order=desc. The current url that your are using in your code is just the html page with examples. It doesn't return proper json results.