I can't seem to access sub arrays in swiftyJSON - ios

So here is the JSON
{
"city": {
"id": 4930956,
"name": "Boston",
"coord": {
"lon": -71.059769,
"lat": 42.358429
},
"country": "US",
"population": 0,
"sys": {
"population": 0
}
},
"cod": "200",
"message": 0.0424,
"cnt": 39,
"list": [
{
"dt": 1473476400,
"main": {
"temp": 76.33,
"temp_min": 73.11,
"temp_max": 76.33,
"pressure": 1026.47,
"sea_level": 1027.96,
"grnd_level": 1026.47,
"humidity": 73,
"temp_kf": 1.79
},
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10n"
}
],
"clouds": {
"all": 8
},
"wind": {
"speed": 7.29,
"deg": 300.501
},
Here is my Controller where I go grab the data......
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var amConnected: UILabel!
#IBOutlet weak var weatherTable: UITableView!
var arrRes = [[String:AnyObject]]()
var swiftyJsonVar: JSON?
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.doSomethingNotification(_:)), name: "ReachabilityChangedNotification", object: nil)
let openWMAPI = "http://api.openweathermap.org/data/2.5/forecast/city?q=Boston,Ma&APPID=XXXXXXXXXXXXXXX&units=imperial"
Alamofire.request(.GET,openWMAPI).responseJSON{
(responseData) -> Void in
print(responseData)
let swiftyJsonVar = JSON(responseData.result.value!)
self.weatherTable.reloadData()
}
.responseString{ response in
//print(response.data.value)
// print(response.result.value)
//print(response.result.error)
//eprint("inhere");
}
weatherTable.rowHeight = UITableViewAutomaticDimension
weatherTable.estimatedRowHeight = 140
// Do any additional setup after loading the view, typically from a nib.
}
In my table loop it is now saying that the jsonArray is nil and failing.
I'm unsure as to what I'm doing wrong at this point.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = weatherTable.dequeueReusableCellWithIdentifier("theCell", forIndexPath: indexPath)
let label1 = cell.viewWithTag(101) as! UILabel
print("inhere")
if((swiftyJsonVar) != nil){
if let jsonArray = self.swiftyJsonVar["list"].array {
var temp = jsonArray[indexPath.row]["main"]["temp"].float
var rdate = jsonArray[indexPath.row]["dt_txt"].string
print(temp)
}else{
print("test")
}
}
label1.text = "TEST"
return cell
}
OVerall I'm just not sure how to dig down into the next level of the JSON.

If you are not averse to it, try using AlamofireObjectMapper instead of SwiftyJson
1) If you are going to change the name of the json keys very often, and are going to do a lot of enum transformations try :
AlamofireObjectMapper
2) If the names are going to be the same, with minimal transformations, directly use :
AlamofireJsonToObjects
Both of these cases, create model classes for your json object. If you have an array - you can define a var as an array
if it is an object or an array of objects - you can then create another model class which is again Mappable and then define such an object var in the original model.
The above libraries will make your code extremely clean while extracting objects to json.

You can access to the elements inside an JSON array in SwiftyJSON using consecutive subscripts, if we have the following JSON for example:
var json: JSON = ["name": "Jack", "age": 25,
"list": ["a", "b", "c", ["what": "this"]]]
Then you can access to the four element of the subarray listcontained in the main array in the following way for example:
json["list"][3]["what"] // this
Or you can define a path like this let path = ["list",3,"what"] and then call it in this way:
json[path] // this
With the above explained let's introduce it with your JSON file to list the elements inside the array weather:
if let jsonArray = json["list"].array {
// get the weather array
if let weatherArray = jsonArray[0]["weather"].array {
// iterate over the elements of the weather array
for index in 0..<weatherArray.count {
// and then access to the elements inside the weather array using optional getters.
if let id = weatherArray[index]["id"].int, let main = weatherArray[index]["main"].string {
print("Id: \(id)")
print("Main: \(main)")
}
}
}
}
And you should see in the console:
Id: 800
Main: Clear
I hope this help you.

Related

Swift: JSON parsing not showing up in UILabel but can see in output window using Alamofire

I'm new to coding and not seem to show Daily quotes to UILabel.
Using http://quotes.rest/qod.json
output console is showing the result but nothing is there in UILabel.
Any help is greatly appreciated.
Below is JSON format from website:
{
"success": {
"total": 1
},
"contents": {
"quotes": [
{
"quote": "You make a living by what you earn",
"length": "69",
"author": "Winston Churchill",
"tags": [
"inspire"
],
"category": "inspire",
"id": "XZiOy4u9_g4Zmt7EdyxSIgeF"
}
],
"copyright": "2017-19 theysaidso.com"
}
}
Swift code I'm using to parse above JSON is:
import Alamofire
import SwiftyJSON
class DailyQuotes {
private var _quotes: String!
private var _author: String!
var quotes: String
{
if _quotes == nil
{
_quotes = ""
}
return _quotes
}
/// Downloading Current Weather Data
func downloadQuotes(completed: #escaping DownloadComplete){
Alamofire.request(QuotesDaily).responseJSON
{ (response) in
let result = response.result
print(result.value)
let json = JSON(result.value!)
self._quotes = json["contents"]["quotes"]["quote"].stringValue
completed()
}
}
}
In ViewConrtroller.swift :
func updateQuotesUI()
{
quoteLabel.text = dailyQuotes.quotes
}
quotes is an array
let arr = json["contents"]["quotes"].arrayValue
self._quotes = arr[0]["quote"].stringValue

Nested repeatative loop in swift

I am trying to parse a nested iterative loop in swift
I am getting the response from web service in the following format
{
"categories": [{
"name": "Default Category",
"id": "default_category",
"children": [{
"uuid": "783f491fef5041438fb7a2c3bf6a3650",
"name": "Accessories",
"children": [{
"uuid": "d21b4491ff784a9bae88de279b99fac3",
"name": "All Accessories",
"children": [{
"uuid": "2b1a23c4107844ad8a7afc1b324d0ffd",
"name": "Belts",
"children": [{
"uuid": "2b1a23c4107844ad8a7afc1b324d0ffd",
"name": "Belts",
"children": []
},
{
"uuid": "2b1a23c4107844ad8a7afc1b324d0ffd",
"name": "Belts",
"children": []
}
]
},
{
"uuid": "a1c2a64c36c2461cad3d5f850e4fd0f5",
"name": "Hats",
"children": []
},
{
"uuid": "8f26bc764b8342feaa0cb7f3b96adcae",
"name": "Scarves",
"children": []
},
{
"uuid": "aa1116d1a0254ecea836cc6b32eeb9e0",
"name": "Sunglasses",
"children": []
},
{
"uuid": "9d7033233e8f47eaa69eb1aaf2e98cdd",
"name": "Watches",
"children": []
}
]
}]
}],
"uuid": "6a23415771064e7aaad59f84f8113561"
}]
}
Inside, the categories, there is 'children' key which in turn can contain another children and so on.
I want to continuously loop inside the children key until the children key is empty and insert the last child into database.
Following is the code which i have done
for currentCategory in mainCategories {
// guard against if there are child categories
guard var children = currentCategory.children, children.count > 0 else {
// Save the context
self.coreData.saveStore()
continue
}
for thisChildCategory in children {
if thisChildCategory.children?.count > 0 {
for innerChildCategory in thisChildCategory.children! {
print("innerChildCategory name \(String(describing: innerChildCategory.name))")
}
}
if let child = thisChildCategory.children {
children = child
}
// Create new object
if let currentChildCategory = self.coreData.insertNewObject(CoreDataEntities.BijouCategories.rawValue,
keyValues: ["id" : thisChildCategory.id! as Optional<AnyObject>,
"uuid" : thisChildCategory.uuid as Optional<AnyObject>,
"name" : thisChildCategory.name! as Optional<AnyObject>,
"gender" : thisChildCategory.gender as Optional<AnyObject>!,
"active" : NSNumber(value: false)]) as? BijouCategories {
// Set as parent category
currentChildCategory.parentCategory = parentCategory
// Save the context
self.coreData.saveStore()
}
}
}
But this is not saving all the last child category in database.
Swift 4
You should let Swift 4's codable do the work for you.
You can use the following class as a struct but I find using a class is better if you plan on editing the data.
class Categories: Codable {
var categories: [CategoryItems]
}
class CategoryItems: Codable {
var name: String?
var id: String?
var uuid: String?
var children: [CategoryItems]?
required init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeIfPresent(String.self, forKey: CodingKeys.name)
id = try container.decodeIfPresent(String.self, forKey: CodingKeys.id)
uuid = try container.decodeIfPresent(String.self, forKey: CodingKeys.uuid)
children = try container.decodeIfPresent([CategoryItems].self, forKey: CodingKeys.children)
if children != nil, children!.count == 0 {
children = nil
}
}
You can see here we add create the root level class "Categories" that has an array of CategoryItems. CategoryItems has all the possible values within it, but each item in the array may or may not have all of the possible values, hence they are optional. The important one is the children which is optional. Then in the required init we only se the optional values if the key value pair is available when decoding. I also set the children to nil if there are zero items, this is optional but helps when doing if statements later.
Then to decode your json using these codable classes you use the following code.
func decode(jsonData data: Data) {
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(Categories.self, from: data)
}
catch let error as NSError {
print("JSON Decode error = ", error)
}
}
If you want to do a quick test to see if you got the deeping children level which I did you can simply run the following on the decoded variable.
for i in decoded.categories.first!.children!.first!.children!.first!.children!.first!.children! {
print(i.name)
print(i.uuid)
}
With more than 2 nested levels a recursive function is recommended. recursive means the function calls itself.
Here is an simple example assuming jsonString is the given JSON in the question.
The function parseCategory passes the children array and the UUID string as parent identifier. The print line is the place to save the object in Core Data and of course you can pass the created Core Data object as parent as well to set the relationship.
func parseCategory(children: [[String:Any]], parent: String) {
for child in children {
print("Save in Core Data", child["name"] as! String, parent)
let descendants = child["children"] as! [[String:Any]]
parseCategory(children:descendants, parent: child["uuid"] as! String)
}
}
let data = Data(jsonString.utf8)
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String:Any]
parseCategory(children: json["categories"] as! [[String:Any]], parent: "")
} catch { print(error)}
The output is
"Save in Core Data Default Category
Save in Core Data Accessories 6a23415771064e7aaad59f84f8113561
Save in Core Data All Accessories 783f491fef5041438fb7a2c3bf6a3650
Save in Core Data Belts d21b4491ff784a9bae88de279b99fac3
Save in Core Data Belts 2b1a23c4107844ad8a7afc1b324d0ffd
Save in Core Data Belts 2b1a23c4107844ad8a7afc1b324d0ffd
Save in Core Data Hats d21b4491ff784a9bae88de279b99fac3
Save in Core Data Scarves d21b4491ff784a9bae88de279b99fac3
Save in Core Data Sunglasses d21b4491ff784a9bae88de279b99fac3
Save in Core Data Watches d21b4491ff784a9bae88de279b99fac3"
Created an model class to hold your nested children in the form of a tree.
class Children {
var uuid: String?
var name: String?
var children: [Children] = [Children(array: [])]
init(array: NSArray) {
let childrenDic = array[0] as! NSDictionary
uuid = childrenDic["uuid"] as? String
name = childrenDic["name"] as? String
children[0] = Children.init(array: childrenDic["children"] as! NSArray)
}
}
Use like
var childrenModel = Children.init(array: yourArray)
I would suggest you to use ObjectMapper instead of unwrapping the json manually.
https://github.com/Hearst-DD/ObjectMapper
then everything should be much cleaner
class Child: Mappable {
var uuid: String?
var name: String?
var childern: [Child]?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
uuid <- map["uuid"]
name <- map["name"]
childern <- map["childern"]
}
}
class Category: Mappable {
var _id: String? //id is the reserved word
var name: String?
var childern: [Child]?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
_id <- map["id"]
name <- map["name"]
childern <- map["childern"]
}
}

How to access enum within struct model? Swift

I wanted to display in my tableView the data and time which is within the struct model right now.
I tried accessing view the main, WeatherModel struct but there is a [List] which abandons me accessing the dtTxt. I just want to show the data in every row. Any help would be appreciated. I add my contents below.
Controller:
#IBOutlet var tableView: UITableView!
var weatherData = [WeatherModel]()
override func viewDidLoad() {
super.viewDidLoad()
getJSONData {
self.tableView.reloadData()
}
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return weatherData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = weatherData[indexPath.row].city.name.capitalized // Instead of city name, it should be dtTxt.
return cell
}
Here is my model:
struct WeatherModel: Codable {
let list: [List]
let city: City
}
struct City: Codable {
let name: String
}
struct List: Codable {
let main: Main
let weather: [Weather]
let dtTxt: String // I want to show this
enum CodingKeys: String, CodingKey {
case main, weather
case dtTxt = "dt_txt" //access here
}
}
struct Main: Codable {
let temp: Double
}
struct Weather: Codable {
let main, description: String
}
And here is my JSON object:
{
"list": [
{
"main": {
"temp": 277.12
},
"weather": [{
"main": "Clouds",
"description": "scattered clouds"
}],
"dt_txt": "2018-06-05 15:00:00"
},
{
"main": {
"temp": 277.12
},
"weather": [{
"main": "Sunny",
"description": "Clear"
}],
"dt_txt": "2018-06-05 18:00:00"
},
{
"main": {
"temp": 277.12
},
"weather": [{
"main": "Rain",
"description": "light rain"
}],
"dt_txt": "2018-06-05 21:00:00"
}
],
"city": {
"name": "Bishkek"
}
}
[List] contains an array of forecast objects at different times.
Use a for loop:
for forecast in weatherModel.list {
print(forecast.dtTxt)
}
or filter one object by a certain condition:
if let threePMForecast = weatherModel.list.first(where: { $0.contains("15:00") }) {
print(threePMForecast.dtTxt)
}

Swift: parsing dynamic JSON

I am trying to parse this JSON that has a changing key for each object Im trying to find the best way to parse each object. I am very new to swift programming so any example code would be very helpful. I don't need to store all the values received just particular field such as address, streetName, image
JSON Input
{
"a000!%5362657": {
"address": "20 Ingram Street",
"streetName": "Ingram Street",
"streetNumber": "20",
"streetDirection": "N",
"unitNumber": "",
"cityName": "Forest Hills",
"countyName": "New York",
"state": "NY",
"zipcode": "11375",
"listingPrice": "$1,151,000",
"listingID": "5362657",
"remarksConcat": "From nytimes.com: In the comics, Peter Parker, the mild-mannered photojournalist who is Spider-Man's alter ego, grew up at 20 Ingram Street, a modest, two-story boarding house run by his Aunt May in the heart of Forest Hills Gardens. The address actually exists and is home to a family named Parker: Andrew and Suzanne Parker, who moved there in 1974, and their two daughters. In 1989, the family began receiving junk mail addressed to Peter Parker. We got tons of it, Mrs. Parker said yesterday. Star Trek magazines, a Discover Card in his name, and notices from them over the years calling him a good customer. There were also prank phone calls, all of which she attributed to a teenager who found it funny that we had the same last name as Spider-Man.",
"rntLse": "neither",
"propStatus": "Active",
"bedrooms": "3",
"totalBaths": "2.75",
"latitude": "40.712968",
"longitude": "-73.843206",
"acres": "0.24",
"sqFt": "2,760",
"displayAddress": "y",
"listingAgentID": "8675301",
"listingOfficeID": "lmnop",
"sample_mlsPtID": "1",
"sample_mlsPhotoCount": "39",
"parentPtID": "1",
"detailsURL": "a000/5362657",
"idxID": "a000",
"idxPropType": "Residential",
"idxStatus": "active",
"viewCount": "2",
"mediaData": [],
"ohCount": "0",
"vtCount": "0",
"featured": "n",
"image": {
"0": {
"url": "http://cdn.photos.ample_mls.com/az/20151113223546806109000000.jpg",
"caption": "17596-20"
},
"totalCount": "39"
},
"fullDetailsURL": "http://sample_return.idxbroker.com/idx/details/listing/a000/5362657/20-Ingram-Street-Forrest-Hills-NY-11375"
},
"a000!%5358959": {
"address": "177A Bleecker Street",
"streetName": "Bleecker Street",
"streetNumber": "177",
"streetDirection": "N",
"unitNumber": "A",
"cityName": "Greenwich Village",
"countyName": "New York",
"state": "NY",
"zipcode": "10012",
"listingPrice": "$616,000,000",
"listingID": "5358959",
"remarksConcat": "Home to Dr. Stephen Vincent Strange(Doctor Strange in Marvel comics) and his faithful bodyguard and manservant Wong. Spider-Man often visits the Doctor for help when battling those with magic powers. Features: Wong's Storage Cellar, Strange's bedchambers, guest quarters, Wong's bedchamber, Study, Meditain Chamber, Library, Storage Area for Occult Artifacts.",
"rntLse": "neither",
"propStatus": "Active",
"bedrooms": "2",
"totalBaths": "2.75",
"latitude": "40.729117",
"longitude": "-74.000773",
"acres": "0.31",
"sqFt": "206800000000",
"displayAddress": "y",
"listingAgentID": "8675301",
"listingOfficeID": "lmnop",
"sample_mlsPtID": "1",
"sample_mlsPhotoCount": "34",
"parentPtID": "1",
"detailsURL": "a000/5358959",
"idxID": "a000",
"idxPropType": "Residential",
"idxStatus": "active",
"viewCount": "6",
"mediaData": [],
"ohCount": "0",
"vtCount": "0",
"featured": "y",
"image": {
"0": {
"url": "http://cdn.photos.sample_mls.com/az/20151105214013253867000000.jpg",
"caption": "Front"
},
"totalCount": "34"
},
"fullDetailsURL": "http://sample_return.idxbroker.com/idx/details/listing/a000/5358959/177A-Bleecker-Street-Greenwich-Village-NY-10012"
}
}
swift viewController
import UIKit
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
//final let urlString = "http://microblogging.wingnity.com/JSONParsingTutorial/jsonActors"
final let urlString = "https://api.idxbroker.com/clients/featured"
#IBOutlet weak var tableView: UITableView!
var nameArray = [String]()
var dobArray = [String]()
var imgURLArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithTask()
//self.downloadJsonWithURL()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/***********************************************************************************************/
func downloadJsonWithTask() {
let url = NSURL(string: urlString)
var downloadTask = URLRequest(url: (url as URL?)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 20)
/************** Get Users Accesy Key for API CALL *************************/
var key = String()
let textFieldKeyConstant = "AccessKey"
let defaults = UserDefaults.standard
if let textFieldValue = defaults.string(forKey: textFieldKeyConstant) {
key = textFieldValue
}
print(key) //accesskey debug
/******************** End Get accessKey *************************************/
/******************** Add Headers required for API CALL *************************************/
downloadTask.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
downloadTask.setValue(key, forHTTPHeaderField: "accesskey")
downloadTask.setValue("json", forHTTPHeaderField: "outputtype")
downloadTask.httpMethod = "GET"
/******************** End Headers required for API CALL *************************************/
URLSession.shared.dataTask(with: downloadTask, completionHandler: {(data, response, error) -> Void in
let jsonData = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
print(jsonData ?? "Default")
/******** Parse JSON **********/
/******** End Parse JSON **********/
/******** Reload table View **********/
OperationQueue.main.addOperation({
self.tableView.reloadData()
}) }).resume()
}
/***********************************************************************************************/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
/***********************************************************************************************/
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.nameLabel.text = nameArray[indexPath.row]
cell.dobLabel.text = dobArray[indexPath.row]
let imgURL = NSURL(string: imgURLArray[indexPath.row])
if imgURL != nil {
let data = NSData(contentsOf: (imgURL as URL?)!)
cell.imgView.image = UIImage(data: data! as Data)
}
return cell
}
/****************************************************************/
///for showing next detailed screen with the downloaded info
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.imageString = imgURLArray[indexPath.row]
vc.nameString = nameArray[indexPath.row]
vc.dobString = dobArray[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
}
for swift3 [String:Any] it is a Dictionary with key has String and Any type in Swift, which describes a value of any type
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
print(parsedData)
} catch let error as NSError {
print(error)
}
For example :
if let info = parsedData["a000!%5362657"] as? [String : String]{
if let address = info["address"] {
print(address)
}
}
Working with JSON in Swift apple blog https://developer.apple.com/swift/blog/?id=37

XCTest for test model objects

Bellow i have mentioned my model object.
class HVConnection: NSObject {
//private var _data: NSMutableDictionary
private var _data: NSMutableDictionary
// MARK:- Init
init(data: NSDictionary)
{
_data = NSMutableDictionary(dictionary: data)
}
// MARK:- Properties
var last_name: String? {
if let lastNameObject = _data.objectForKey("last_name") {
return (idObject as! String)
} else {
return nil
}
}
}
then i implemented a test case to check variables.
Bellow i have mentioned the test case.
func testNetworkModelObject() {
let connectionObject = ["network": ["first_name": "Dimuth", "last_name": "Lasantha", "business_email": "example#gmail.com", "currency": "USD", "language": "en-us", "category": "individual"]]
let modelObject = HVConnection(data: connectionObject)
XCTAssertEqual(modelObject.last_name, "Lasantha")
}
bellow i have mentioned the error
XCTAssertEqual failed: ("nil") is not equal to ("Optional("Lasantha")")
please help me to fix the issue
Your problem is that you cannot use
_data.objectForKey("last_name")
in your HVConnection because it is nested within another dictionary key called network.
So instead use:
// MARK:- Properties
var last_name: String? {
if let lastNameObject = _data.objectForKey("network")?.objectForKey("last_name") {
return (lastNameObject as! String)
} else {
return nil
}
}
This is to demonstrate the dictionaries in use:
["network": // your dictionary is a dictionary within a dictionary
["first_name": "Dimuth",
"last_name": "Lasantha",
"business_email": "example#gmail.com",
"currency": "USD",
"language": "en-us",
"category": "individual"]
]
In order to get your last_name, you have to get the dictionary for key network to get the actual dictionary, and once you have that, you can check that dictionary you get back from the network key for the key last_name.

Resources