Swift: Parsing Array of object - ios

I have a Contact object which can have and array of Address objects.
class Contact {
var firstName: String = ""
var lastName: String = ""
var middleName: String = ""
var id: Int = -1
var addresses: Array<Address> = []
How would I initialize each Address object while fetching it from json dictionary?
init(json: Dictionary<String, AnyObject>) {
if let line1 = json["streetLine"] as? String {
self.streetLine1 = line1
}
if let city = json["city"] as? String {
self.city = city
}
if let state = json["state"] as? String {
self.state = state
}
if let zip = json["zip"] as? Int {
self.zip = zip
}
Tried doing like this:
if let addressMap = json["addresses"] as? Array<Dictionary<String, AnyObject?>> {
for address as? Address in addressMap {
addresses.append(address)
}
}

Try this:
if let dictionaryArray = json["addresses"] as? Array<Dictionary<String, Anyobject?>>
for address in dictionaryArray {
var address = Address(json: address)
addresses.append(address)
}
}
So you get your list of Dictionarys from your json, loop through them and then use your init function to inject the data into your class.

Try this
addresses = (json["addresses"] as? Array<Dictionary<String, AnyObject?>>)?.map{Address(json: $0)}

Related

Convert array of dictionaries into a dictionary and init the model

I'm getting an array of dictionaries from the server. Then I'm trying to convert it to jsonDictionary it seems like I'm doing something wrong. How can I also init my Users model?
Here is the code:
func getSearchedUsers(key: String, completion: #escaping(SearchedUsers?) -> Void) {
if let url = URL(string: baseURL + "search?qerty=\(key)") {
Alamofire.request(url).responseJSON { (response) in
if let array = response.result.value as? [[String:Any]] {
var dictionary = [String:Any]()
for item in array {
for (key, value) in item {
dictionary.updateValue(value, forKey: key)
}
}
} else {
completion(nil)
}
}
}
}
And here is the model:
class SearchedUsers {
let id: Int
let username: String?
let fullName: String?
let profilePicture: URL?
let isPrivate: Bool
init(data: [String: Any]) {
id = data["id"] as! Int
username = data["username"] as? String
fullName = data["fullName"] as? String
isPrivate = data["isPrivate"] as! Bool
profilePicture = data["profilePicUrl"] as? URL
}
}
How can I get this to work?
Here is the response I get:
[Result]: SUCCESS: (
{
byline = "21.9k followers";
followerCount = 21911;
friendshipStatus = {
following = 0;
"incoming_request" = 0;
"is_bestie" = 0;
"is_private" = 0;
"outgoing_request" = 0;
};
fullName = "Undefined Variable";
hasAnonymousProfilePicture = 0;
id = 8513861541;
isPrivate = 0;
isVerified = 0;
mutualFollowersCount = 0;
picture = "https://scontent-ams3-1.cdninstagram.com/vp/885ac17fe17809de22790f0559f61877/5CD13A1C/t51.2885-19/s150x150/39312159_480582069091253_3011569611268161536_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
pk = 8513861541;
profilePicId = "1857507164564653723_8513861541";
profilePicUrl = "https://scontent-ams3-1.cdninstagram.com/vp/885ac17fe17809de22790f0559f61877/5CD13A1C/t51.2885-19/s150x150/39312159_480582069091253_3011569611268161536_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
reelAutoArchive = on;
username = "i_am_variable";
},
{
byline = "467 followers";
followerCount = 467;
friendshipStatus = {
following = 0;
"incoming_request" = 0;
"is_bestie" = 0;
"is_private" = 0;
"outgoing_request" = 0;
};
fullName = undefined;
hasAnonymousProfilePicture = 0;
id = 8657882817;
isPrivate = 0;
isVerified = 0;
latestReelMedia = 1547794887;
mutualFollowersCount = 0;
picture = "https://scontent-ams3-1.cdninstagram.com/vp/fb3c992c899aa269bdce2c4c1db8575b/5CD068BA/t51.2885-19/s150x150/46378106_2062632390480778_1266491662662631424_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
pk = 8657882817;
profilePicId = "1931972067016763185_8657882817";
profilePicUrl = "https://scontent-ams3-1.cdninstagram.com/vp/fb3c992c899aa269bdce2c4c1db8575b/5CD068BA/t51.2885-19/s150x150/46378106_2062632390480778_1266491662662631424_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
reelAutoArchive = on;
username = "undefi.ned";
})
It's an array of dictionaries, I need to parse it in a proper way. That's my main issue.
If you know how to parse dictionary, then you should know how to make one ;) There are tools out there to make your own model class, like: http://www.jsoncafe.com/
EDIT: As suggested by Robert in the comment section below, you can learn Decodable.
You can use that to give yourself an idea how a model class could or should look like. Use it however you like. In a decent project, there could be tons of data, and you don't want to make a class model out of it especially if you're the only one handling the iOS project.
So we suppose, we have this json data, based on your post:
{
"id": 1,
"username": "dd",
"fullName": "dd",
"profilePicture": "ddd",
"isPrivate": true
}
We could make a model out of it like so:
//
// UserRootClass.swift
// Model Generated using http://www.jsoncafe.com/
// Created on January 18, 2019
import Foundation
class UserRootClass : NSObject {
var fullName : String!
var id : Int!
var isPrivate : Bool!
var profilePicture : String!
var username : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
fullName = dictionary["fullName"] as? String
id = dictionary["id"] as? Int
isPrivate = dictionary["isPrivate"] as? Bool
profilePicture = dictionary["profilePicture"] as? String
username = dictionary["username"] as? String
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]
{
var dictionary = [String:Any]()
if fullName != nil{
dictionary["fullName"] = fullName
}
if id != nil{
dictionary["id"] = id
}
if isPrivate != nil{
dictionary["isPrivate"] = isPrivate
}
if profilePicture != nil{
dictionary["profilePicture"] = profilePicture
}
if username != nil{
dictionary["username"] = username
}
return dictionary
}
}
The model class above was made using the tool I gave above, but I removed the NSCoding protocol methods.
I hope this helps! Good luck and welcome to Stackoverflow.
You can use Decodable if you have Struct instead of Class for easy parsing. Here is the example in Alamofire 5.0
struct SearchedUsers: Decodable {
let id: Int
let username: String?
let fullName: String?
let profilePicture: URL?
let isPrivate: Bool
}
AF.request("http://url_endpoint/").responseData { response in
do {
// data we are getting from network request
let decoder = JSONDecoder()
let response = try decoder.decode([SearchedUsers].self, from: response.data!)
} catch { print(error) }
}

object list always print nil in swift

In my object Dish xxx.Dish, I want to access the Choice class price and name to display but I failed. dish data load from web API and I tested data loaded success full and put the data to the object dish and it return the object list to viewcontroller to load tableview.
Output of printed console
Optional([xxx.Dish, xxx.Dish])
and in the dish class before append optionList?.append(_obj)
xxx.DishOption
Anyone helps me how can I do that .. I am new to swift and is it right way to implement? Please suggest me?
class Dish {
let dishId : String
var optionList : [DishOption]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let dishId = resposne["dishId"] as? String else {
return nil
}
self.dishId = dishId
if let objs = resposne["options"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = DishOption(fromAPIResponse: obj){
optionList?.append(_obj)
}
}
}
}
class DishOption {
let optionId : String
var choiceList : [Choice]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let optionId = resposne["optionId"] as? String else {
return nil
}
self.optionId = optionId
if let objs = resposne["choices"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = Choice(fromAPIResponse: obj){
choiceList?.append(_obj)
}
}
}
}
}
class Choice{
let choiceId : String
let name : String
let price : String
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let choiceId = resposne["choiceId"] as? String ,
let name = resposne["name"] as? String,
let price = resposne["price"] as? String else {
return nil
}
self.choiceId = choiceId
self.name = name
self.price = price
}
}
UPDATE:
var dishMenuList = [Dish]()
guard let objs = json["menu_list"] as? [[String : AnyObject]] else {
return
}
for obj in objs {
if let _obj = Dish(fromAPIResponse: obj){
print(_obj.optionList) //always print nil
if let options = _obj.optionList {
for data in options {
print(data.displayAsButton)
}
}
dishMenuList.append(_obj)
}
}
From what I can see, you are never initializing both the optionList and choiceList arrays. It would be better to initialize them as empty arrays:
class Dish {
let dishId : String
var optionList = [DishOption]()
...
optionList.append(_obj)
This is the reason that you cannot see any options. Since the optionList is still nil, the line optionList?.append(_obj) does not execute.

API doesn't contain any value for some objects and Xcode gives a fatal error when running the app

My app takes some data from this API: https://api.jqestate.ru/v1/properties/country
GitHub link to my project: https://github.com/armansharvel/JQ-Estate.git (download branch "Refreshing")
There are no compiler errors but when I run my app in the simulator Xcode prints in console "Fatal error: Index out of range".
In the ObjectModel.swift I created a class of the object with some data types. One of them is the variable mainPic (URL of picture for TableVeiw that I want to get from the API also). But the problem is not every object in the API contains value of URL of the picture.
So Xcode (when I try to run the app) marks the second line of code block that initialises mainPic variable and the error is: "Thread 7: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)"
Here is the whole class in code:
import Foundation
class Houses {
// Data Encapsulation
private var _mainPic: String
private var _localityName: String
private var _routeName: String
private var _mkadDistance: String
private var _rentOffer: String
private var _saleOffer: String
// Make a getted
var mainPic: String {
return _mainPic
}
var localityName: String {
return _localityName
}
var routeName: String {
return _routeName
}
var mkadDistance: String {
return _mkadDistance
}
var rentOffer: String {
return _rentOffer
}
var saleOffer: String {
return _saleOffer
}
// Initialization
init(data: JSONDictionary) {
// Main Picture
if let images = data["images"] as? JSONArray,
pic0 = images[0] as? JSONDictionary, // THIS LINE IS WITH ERROR
mainPic = pic0["url"] as? String {
self._mainPic = mainPic
} else {
_mainPic = ""
}
// Locality Name
if let location = data["location"] as? JSONDictionary,
localityName = location["localityName"] as? String {
self._localityName = localityName
} else {
_localityName = ""
}
// Route Name
if let location = data["location"] as? JSONDictionary,
routeName = location["routeName"] as? String {
self._routeName = routeName
} else {
_routeName = ""
}
// MKAD Distance
if let location = data["location"] as? JSONDictionary,
mkadDistance = location["mkadDistance"] as? String {
self._mkadDistance = mkadDistance
} else {
_mkadDistance = ""
}
// Rent Offer
if let rentDict = data["rentOffer"] as? JSONDictionary,
rentOffer = rentDict["price"] as? String {
self._rentOffer = rentOffer
} else {
_rentOffer = ""
}
// Sale Offer
if let saleDict = data["saleOffer"] as? JSONDictionary,
saleOffer = saleDict["price"] as? String {
self._saleOffer = saleOffer
} else {
_saleOffer = ""
}
}
}
Just in case, JSONDictionary and JSONArray are just typealiases:
typealias JSONDictionary = [String : AnyObject]
typealias JSONArray = Array<AnyObject>
Thanks in advance!
images[0] will crash with "Fatal error: Index out of range" if the images array is empty.
Since you're using optional binding, use first instead of [0]:
if let images = data["images"] as? JSONArray,
pic0 = images.first as? JSONDictionary,
mainPic = pic0["url"] as? String {
self._mainPic = mainPic
} else {
_mainPic = ""
}

Value of type '[Businessdata]' has no member 'objectAtIndex'

I am try to implement search bar to my table view. But I am getting this error in one function. Don't know how to solve??
Value of type '[Businessdata]' has no member 'objectAtIndex'
My code
var arrDict = [Businessdata]()
func searchMethod(notification:NSNotification)
{
isSearching = true;
let text:String = notification.userInfo!["text"] as! String;
arrSearch = [];
for(var i=0;i<arrDict.count;i++)
{
if((arrDict.objectAtIndex(i).objectForKey("name")?.lowercaseString?.containsString(text.lowercaseString)) == true)
{
arrSearch.addObject(arrDict.objectAtIndex(i));
}
}
TableViewList.reloadData();
}
Edited :
import UIKit
class Businessdata: NSObject {
var BusinessName: String?
var BusinessEmail: String?
var BusinessLatLng: NSArray?
var Address: String?
var ContactNumber: String?
var WebsiteUrl: String?
var Specialities:Array<String>?
var StoreImages: NSArray?
var Languages:Array<String>?
var PaymentMethod:Array<String>?
var OpenHours: [NSDictionary]?
var Rating: Float?
var Updated_date: String?
var FeaturedBusiness: NSDictionary?
init(json: NSDictionary)
{
self.BusinessName = json["business_name"] as? String
self.BusinessEmail = json["business_email"] as? String
self.BusinessLatLng = json["latlng"] as? NSArray
self.Address = json["location"] as? String
self.ContactNumber = json["phone_no"] as? String
self.WebsiteUrl = json["website_url"] as? String
self.Specialities = json["specialities"] as? Array<String>
self.StoreImages = json["images"] as? NSArray
self.Languages = json["languages"] as? Array<String>
self.PaymentMethod = json["method_payment"] as? Array<String>
self.OpenHours = json["opening_hours"] as? [NSDictionary]
self.Rating = json["__v"] as? Float
self.Updated_date = json["updated_at"] as? String
if((json["featured_business"]) != nil)
{
self.FeaturedBusiness = json["featured_business"] as? NSDictionary
}
}
}
Here i have posted the Bussinessdata class code.Now how to solve for my problem
Help me out!!
There is no objectAtIndex in an array. You need to do something like this:
arrDict[i]
Instead of
arrDict.objectAtIndex(i)
Edit
As we discussed in the comments this is what you need
if((arrDict[i].name.lowercaseString?.containsString(text.lower‌​caseString)) == true)
Try this one:
func searchMethod(notification:NSNotification)
{
isSearching = true;
let text:String = notification.userInfo!["text"] as! String;
arrSearch = [];
for(var i=0;i<arrDict.count;i++)
{
if((arrDict[i].BusinessName.lowercaseString?.containsString(text.lower‌​caseString)) == true)
{
arrSearch.addObject(arrDict[i]);// or arrSearch.append(arrDict[i])
}
}
TableViewList.reloadData();
}
objectAtIndex: belongs to NSArray and objectForKey: belongs to NSDictionary.
Both are not available for the Swift native types.
But there are two fatal issues:
Businessdata is a custom class which does not respond to objectForKey: at all, and there is no property name in the class.
Assuming you are talking about the property BusinessName and the logic is supposed to filter all Businessdata instances whose lowercase string of BusinessName contains the search string you might write
arrSearch = [Businessdata]()
for item in arrDict {
if let businessName = item.BusinessName as? String where businessName.lowercaseString.containsString(text.lowercaseString) {
arrSearch.append(item)
}
}
or swifiter
arrSearch = arrDict.filter({ (item) -> Bool in
if let businessName = item.BusinessName as? String {
return businessName.lowercaseString.containsString(text.lowercaseString)
}
return false
})
And please conform to the naming convention and use always variable names starting with a lowercase letter.

Error handling parsing JSON in Swift

I'm using Alamofire and am parsing the returned JSON into an object as shown below:
final class User: NSObject, ResponseObjectSerializable {
var id: Int
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
} else {
self.id = 0
}
if let facebookUID = representation.valueForKeyPath("facebook_UID") as? String {
self.facebookUID = facebookUID
}
if let email = representation.valueForKeyPath("email") as? String {
self.email = email
} else {
self.email = ""
}
if let firstName = representation.valueForKeyPath("first_name") as? String {
self.firstName = firstName
} else {
self.firstName = ""
}
if let lastName = representation.valueForKeyPath("last_name") as? String {
self.lastName = lastName
} else {
self.lastName = ""
}
if let phone = representation.valueForKeyPath("phone") as? String {
self.phone = phone
}
if let position = representation.valueForKeyPath("position_name") as? String {
self.position = position
}
if let timeCreated = representation.valueForKeyPath("time_created") as? String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
if let date = formatter.dateFromString(timeCreated) {
self.timeCreated = CVDate(date: date)
} else {
self.timeCreated = CVDate(date: NSDate())
}
} else {
self.timeCreated = CVDate(date: NSDate())
}
}
}
My question is, is this style the best way to decode JSON and set the non-optional instance variables? For example, in this statement:
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
}
I am required by the compiler to add an else clause and set the id to something otherwise xCode throws an error saying: self.id is not initialized at implicitly generated super.init call.
But at the same time, intializing self.id with a value of 0 is wrong and doesn't help me at all.
But at the same time, intializing self.id with a value of 0 is wrong and doesn't help me at all.
If having a default value for self.id feels wrong, then you should make this property an Optional. That way you wouldn't have to add an else clause:
final class User: NSObject, ResponseObjectSerializable {
var id: Int?
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
}
...
Update
You said in the comments:
I always need to have an id for the user object though.
If you have to have this id property then the question is moot, you just have to do
let id = representation.valueForKeyPath("id") as! Int
and guarantee earlier that this value will exist.
Because if your object needs an ID, then you can't initialize it anyway if this value doesn't exist and if you don't want a default value.
You could use ?? to provide default values like this:
self.id = (representation.valueForKeyPath("id") as? Int) ?? 0
While the ResponseObjectSerializable code is a great example from the Alamofire project, it's really a better idea to use a dedicated JSON parsing library that has actual error states. This is far better than using optionals to represent error states, or having to provide a default value for every field just in case the response isn't correctly formed.
Although it has a bit of learning curve, I prefer to use Argo for my JSON parsing. Once you get the hang of it it makes JSON parsing practically bulletproof. Better yet, it's easy to integrate with Alamofire, especially version 3 that was released today.
To address your concern about not having an ID being an error condition, you could use a failable initializer. I did that in a recent project. Looks something like this:
let id: Int!
init? (inputJson: NSDictionary) {
if let id = inputJson["id"] as? Int {
self.id = id
} else {
// if we are initing from JSON, there MUST be an id
id = nil
cry(inputJson) // this logs the error
return nil
}
}
Of course, this means your code will need to accept that the initialization of your entire object may fail ..

Resources