I just want to ask how to handle JSON return with null values in Swift.
I used guard for unwrapping the values, but if the JSON has a null value like in the example below, guard will not be executed
I also want to know proper way of using a guard. Is it okay to use multiple conditions in one guard? Like in my sample code below?
{
"error_code": "0",
"error_description": [],
"status": true,
"results": {
"id": 25,
"email": "jaundelacruz#yahoo.com",
"created_at": "2016-12-10T15:54:03.779Z",
"updated_at": "2016-12-15T06:41:49.432Z",
"first_name": "Juan",
"last_name": "Cruz",
"middle_name": "Dela",
"nickname": "joe",
"option_nickname": null,
"age": 24,
"gender": "Male",
"mobile_number": "639959283721",
"photo_url": {
"url": null
},
"school": "University of the Philippines",
"user_address": null,
"city": "Quezon City",
"country": null,
"longtitude": 121.0621301,
"latitude": 14.5832954,
"facebook_user_id": null,
"device_token": null,
"token": "YrxVEWUMYxieFy7PEsAn",
"is_sent": false,
"show_nickname": false
},
"error": []
}
Here's my code:
func UserLogin(facebookID: String) -> Void {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
let urlString = Constants.baseURL + Constants.apiURL + Constants.loginUrl
let params:Dictionary<String, AnyObject> = [
"facebook_id": facebookID as AnyObject
]
Alamofire.request(urlString, method: .post, parameters: params, encoding: JSONEncoding.default)
.validate()
.responseJSON { response in
switch response.result {
case .success:
let userjson = JSON(data: response.data!)
let results = userjson["results"].dictionaryValue
guard let auth_token = results["token"]?.string,
let userid = results["id"]?.int,
let firstname = results["first_name"]?.string,
let lastname = results["last_name"]?.string,
let nickname = results["nickname"]?.string,
let city = results["city"]?.string,
let photo = results["photo_url"]?["url"]else{
//nil
return
}
//The Process will not continue from here if the JSON response has a null value)
self.userdefault.saveUserToken(authToken: auth_token)
self.userdefault.setUserID(userID: userid)
self.userdefault.setuserFirstName(userFName: firstname)
self.userdefault.setuserLastName(userLName: lastname)
self.userdefault.setuserNickName(userNName: nickname)
self.userdefault.setuserCity(userCity: city)
self.GotoMainNavigation()
case .failure(let error):
print("susb \(error)")
self.getFacebookInfo()
}
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}
Thank you
Your guard expression is perfectly fine and filters all null values except in the last line. Assuming the url is also a string you have to check
let photo = results["photo_url"]?["url"]?.string ...
but consider that the guard expression will only be evaluated to true if all optional bindings succeed.
For example if the photo value is supposed to be optional put it after the guard expression and use the nil coalescing operator:
let photo = results["photo_url"]?["url"]?.string ?? ""
data = {
results = {
resultsData = (
{
a = "A";
b = "B";
c = "C":
}
);
};
};
guard is a new conditional statement that requires execution to exit the current block if the condition isn’t met. Any new optional bindings created in a guard statement’s condition are available for the rest of the function or block, and the mandatory else must exit the current scope, by using return to leave a function, continue or break within a loop, or a #noreturn function
guard let data = response["data"] as? [String:AnyObject],let resultsObj = response["results"] as? [String:AnyObject],let resultsData = results["resultsData"] as? [String:AnyObject] else{
return
}
Related
I try to decode api response but the values did not have key. How can I decode values to my entity ? I can get values but how can I map values to array of object?
Here is my code:
if let encoded = urlString.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed), let url = URL(string: encoded) {
AF.request(url, method: .get, encoding: JSONEncoding.default).validate().responseData { (response) in
switch response.result {
case .success:
print("Validation Successful)")
if let json = response.data {
do{
let asJSON = try JSONSerialization.jsonObject(with: json, options: []) as? [String: Any]
let dataArray = asJSON?["states"] as? [NSArray]
guard let data = dataArray else { return }
for (i, flight) in data.enumerated() {
for first in flight.enumerated() {
print(first)
}
}
}
catch{
print("JSON Error")
}
}
case .failure(let error):
print(error.errorDescription)
}
}
}
And this is my print
(
4baa88,
"THY3TB ",
Turkey,
1669746477,
1669746477,
"28.782",
"40.9959",
"1127.76",
0,
"118.94",
"301.56",
"-6.5",
"<null>",
"1196.34",
"<null>",
0,
0
)
And this is example of my response
{
"time": 1669744917,
"states": [
[
"4bc855",
"PGT4AE ",
"Turkey",
1669744917,
1669744917,
28.1582,
41.0447,
5791.2,
false,
181.16,
119.99,
-5.85,
null,
5913.12,
"7554",
false,
0
],
[
"4bc846",
"PGT4BC ",
"Turkey",
1669744916,
1669744916,
28.2241,
40.8712,
5608.32,
false,
216.41,
168.34,
-13.33,
null,
5722.62,
"7703",
false,
0
]
]
Alamofire.request("https://example.com/stories.php", method: .post, parameters: parameters).validate().responseJSON { response in
switch response.result {
case .success:
if let json = response.result.value {
let json2 = JSON(json)
for (_, subJSON): (String, JSON) in json2[0]["stories"] {
if let arr = subJSON["user"].dictionary {
let title = arr["name"]?.string
let id = arr["id"]?.int
let photo = arr["picture"]?.string
let rel1 = InboxStories(title: title!, storyID: id!, photo:photo!)
cell.arrayOfRels.append(rel1)
}
}
cell.getStoryDel()
}
case .failure(_):
print("hata")
}
}
JSON output
[{
"stories": [{
"id": "s1",
"last_updated": "1582543824",
"user": {
"id": "2",
"name": "testuser",
"picture": "https:\/\/example.com\/ios\/images\/profile_pic\/116534.jpg"
},
"snaps_count": "1",
"snaps": [{
"id": "c1",
"mime_type": "image",
"url": "chttps:\/\/example.com\/ios\/stories\/image\/3434.jpg",
"last_updated": "1582543824"
}]
}],
"count": 1
}]
I am trying to reach user key of stories.
It returns nil in my code. How can I parse it?
stories is also an array, please note the []
And it's pretty confusing to name a dictionary arr 😉
if let stories = json2.array?.first?["stories"].array {
for story in stories {
if let dict = story["user"].dictionary {
let title = dict["name"]!.stringValue
let id = dict["id"]!.stringValue
let photo = dict["picture"]!.stringValue
let rel1 = InboxStories(title: title, storyID: id, photo:photo)
cell.arrayOfRels.append(rel1)
}
}
}
And consider to use Decodable. It's built-in and more comfortable than SwiftyJSON
I am creating an Event app where in the user should tap Check In Button to register for the event. registered_flag : false will be true and registered_type: 0 will be 1 once the user tapped the button. But as I build and run the app, error appeared that says
responseSerializationFailed(Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(Error
Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character
0." UserInfo={NSDebugDescription=Invalid value around character 0.})).
I am not really confident regarding the content of my codes in my APIService. Hope someone will help me to correct my codes so I could build and run my app successfully. Thank you so much.
application/json that needs to be updated which inside event_participant
{
"registered_flag": false,
"registration_type": 0
}
event_participant
"event_participants": [
{
"participant_id": "70984656-92bc-4c36-9314-2c741f068523",
"employee_number": null,
"last_name": "Surname",
"first_name": "FirstName",
"middle_name": null,
"display_name": "Surname, FirstName ",
"department_name": "Department",
"position_name": "Developer",
"registered_flag": false,
"registered_datetime": "2018-09-13T08:54:40.150",
"registration_type": 0,
"delete_flag": false,
"manual_reg_flag": false,
"out_flag": false,
"out_datetime": null,
"classification": 6,
"others": "Guest"
}
}
API Service using PUT request in Alamofire
func updateParticipant(updateType: UpdateParticipantType,
participantID: String,
completionHandler: #escaping(([Attendee]?, Error?) -> Void)) {
let updateParticipantURL = URL(string: "\(REGISTER_PARTICIPANT_URL)/\(updateType)/\(participantID)")
let headers: HTTPHeaders = [
"Content-Type": "application/json"
]
let parameters: Parameters = [
"registered_flag": false,
"registration_type": 0
]
Alamofire.request(updateParticipantURL!, method: .put, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
switch response.result {
case .success:
print("Done")
if let jsonArray = response.result.value as? [[String : Any]] {
for anItem in jsonArray {
if let eventparticipant = anItem["event_participants"] as? [[String : Any]] {
var extractedAttendee = [Attendee]()
for participant in eventparticipant{
print(participant)
let success = Attendee.init(JSON: participant)
extractedAttendee.append(success!)
}
completionHandler(extractedAttendee, nil)
}
}
}
case .failure(let error):
completionHandler(nil, error)
}
}
}
Struct for Attendee
struct Attendee: Decodable {
let id: String
let employeeNumber: String?
let lastName: String
let firstName: String
let middleName: String
let displayName: String
let department: String
let position: String
let registeredFlag: Bool
let registeredDateTime: Date?
let registrationType: Int
let deleteFlag: Bool
let manualFlag: Bool
let outFlag: Bool
let outDateTime: Date?
let classification: Int?
let others: String?
postman response
First issue I addressed
I am working on an APIService using Alamofire, I tried to print the response and the I got the data successfully, but unfortunately the data from JSON turns to nil when I parse it to the attendees Object. How can I reflect the data from json to the attendees object?
Second Issue
I solved the 1st issue, after all the debugging I had. The codes I used was written in my answer below. I parsed data from JSON going to attendees but as I checked only the first array was fetch. How can I get all the data inside the JSON? Hope you can help me. Thank you.
func getParticipants(passcode: String,
participantType: ParticipantType,
successBlock: #escaping (Attendees?) -> Void,
failureBlock: #escaping (Error) -> Void)
{
let attendeesURL = URL(string: "\(GET_PARTICIPANTS_URL)/\(passcode)/\(participantType)")
Alamofire.request(attendeesURL!, method: .get).responseJSON { (response) in
print(response)
if let error = response.error
{
failureBlock(error)
return
}
if let attendeeJSON = response.result.value as? [Dictionary<String, Any>],
let attendeeObj = attendeeJSON.first {
print(attendeeObj)
let attendees = Attendees.init(JSON: attendeeObj)
successBlock(attendees)
}
}
}
}
JSON
[
{
"event_name": "Laugh Trip",
"event_participants": [
{
"participant_id": "6f1e7fd5-6da9-4d5b-bc91-4771aeaa5235",
"employee_number": "",
"last_name": "name",
"first_name": "name",
"middle_name": "",
"display_name": "name, name ",
"department_name": "IT",
"position_name": "Application Developer",
"registered_flag": true,
"registered_datetime": "2018-07-16T14:51:57.813",
"registration_type": 1,
"delete_flag": false,
"manual_reg_flag": false,
"out_flag": true,
"out_datetime": "2018-07-16T14:54:00.000",
"classification": 1,
"others": ""
},
{
"participant_id": "6f1e7fd5-6da9-4d5b-bc91-4771aeaa5235",
"employee_number": "",
"last_name": "name",
"first_name": "name",
"middle_name": "",
"display_name": "name, name ",
"department_name": "IT",
"position_name": "Application Developer",
"registered_flag": true,
"registered_datetime": "2018-07-16T14:51:57.813",
"registration_type": 1,
"delete_flag": false,
"manual_reg_flag": false,
"out_flag": true,
"out_datetime": "2018-07-16T14:54:00.000",
"classification": 1,
"others": ""
},
]
]
Instead of using first which gets only the first item of the sequence use a loop respectively.
if let events = response.result.value as? [[String : Any]] {
for event in events {
if let eventparticipants = event["event_participants"] as? [[String : Any]] {
print(eventparticipants)
for participant in eventparticipants {
let attendees = Attendees.init(JSON: participant)
successBlock(attendees)
}
}
}
}
I recommend to decode the JSON directly into structs with Decodable
I solved my own issue. :D
Alamofire.request(attendeesURL!, method: .get).responseJSON { (response) in
print(response)
if let error = response.error
{
failureBlock(error)
return
}
if let jsonDictionary = response.result.value as? [Dictionary<String, Any>]{
if let eventparticipants = jsonDictionary.first {
print(eventparticipants)
if let partObj = eventparticipants["event_participants"] as? [[String : Any]]{
let attendeeObj = partObj.first
let attendees = Attendees.init(JSON: attendeeObj!)
successBlock(attendees)
}
}
}
}
I am parsing some data from Json and saved into the coredata,after fetching core data showing into the tableview working fine, tableview is show all the value in repeatedly how can avoid the repeated values I tried many ways but not find way
Json format
{
"contacts": [
{
"id": "c200",
"name": "Ravi Tamada",
"email": "ravi#gmail.com",
"address": "xx-xx-xxxx,x - street, x - country",
"gender" : "male",
"phone": {
"mobile": "+91 0000000000",
"home": "00 000000",
"office": "00 000000"
}
},
{
"id": "c201",
"name": "Johnny Depp",
"email": "johnny_depp#gmail.com",
"address": "xx-xx-xxxx,x - street, x - country",
"gender" : "male",
"phone": {
"mobile": "+91 0000000000",
"home": "00 000000",
"office": "00 000000"
}
},
{
"id": "c202",
"name": "Leonardo Dicaprio",
"email": "leonardo_dicaprio#gmail.com",
"address": "xx-xx-xxxx,x - street, x - country",
"gender" : "male",
"phone": {
"mobile": "+91 0000000000",
"home": "00 000000",
"office": "00 000000"
}
}
]
}
when I fetching "name" showing repeated values
these are save and fetch code
func getfetchdata()
{
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Contacts")
do{
let fetchResults = try self.context.fetch(fetchRequest) as? [Contacts]
if fetchResults!.count > 0 {
for bEntity in fetchResults! {
let employeeEntity = bEntity
print(employeeEntity.name as Any)
TableviewData.append(ContactsDataVal.init(name: employeeEntity.name!,
id: employeeEntity.id!, email: employeeEntity.email!, gender: employeeEntity.gender!, address: employeeEntity.address!))
}
print("data values already")
}}
catch let error as NSError
{
print(error)
}
}
func getdata()
{
let url = URL(string: "https://api.androidhive.info/contacts/")
URLSession.shared.dataTask(with: url!) { (Data, response, error) in
do
{
let data = try JSONSerialization.jsonObject(with: Data!, options: JSONSerialization.ReadingOptions.allowFragments)as! [String:AnyObject]
let arraydata = data["contacts"]as! [[String:Any]]
for arravalues in arraydata
{
let entityDescription = NSEntityDescription.entity(forEntityName: "Contacts", in:self.context)
let favouriteObj = Contacts(entity: entityDescription!, insertInto: self.context)
favouriteObj.name = arravalues["name"] as? String
favouriteObj.id = arravalues["id"] as? String
favouriteObj.email = arravalues["email"] as? String
favouriteObj.gender = arravalues["gender"] as? String
favouriteObj.address = arravalues["address"] as? String
do {
try self.context.save()
}
}
}catch let error as NSError{
print("error",error)
}
}
.resume()
}
how to avoid repeated values in core data and show proper data into the tableview
First of all in getdata do not save the context in each iteration of the loop, save it once after the loop.
To avoid duplicates fetch all contacts from Core Data, map them to the names and check if the array contains the received name
func getdata()
{
let url = URL(string: "https://api.androidhive.info/contacts/")
let names : [String]
do {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Contacts")
let fetchResults = try self.context.fetch(fetchRequest)
names = fetchResults.map{ $0.name }
} catch {
names = []
}
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil { print(error!); return }
do {
let data = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
let arraydata = data["contacts"] as! [[String:Any]]
for arravalues in arraydata
{
guard let name = arravalues["name"] as? String, !names.contains(name) else { continue }
let entityDescription = NSEntityDescription.entity(forEntityName: "Contacts", in:self.context)
let favouriteObj = Contacts(entity: entityDescription!, insertInto: self.context)
favouriteObj.name = name
favouriteObj.id = arravalues["id"] as? String
favouriteObj.email = arravalues["email"] as? String
favouriteObj.gender = arravalues["gender"] as? String
favouriteObj.address = arravalues["address"] as? String
}
try self.context.save()
} catch {
print("error",error)
}
}
.resume()
}
Notes:
A Core Data fetch with generic fetch request returns always a non-optional array of the NSManagedObject subclass which is specified as generic type on success.
Never check for empty array with foo.count > 0, use !foo.isEmpty
A JSON dictionary in Swift 3+ is always [String:Any] rather than [String:AnyObject]
Handle a potential error in the dataTask completion block.
Name the first parameter in the completion block lowercased (data) to avoid a confusion with the type Data.
Omit the options parameter in jsonObject(with as the result is clearly a collection type.
Maybe there is something that can be done to the fetch request similar to How to get distinct results from a single field in Core Data (Swift 4) but another option would be to remove the duplicates by simply creating a set from the fetch result:
let fetchSet = Set(fetchResults)
and iterate over the set instead