Parsing Swift dictionary's secondary level - swift

I have a JSON object that comes from my server as so:
LOGIN_SUCCESS with JSON: {
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJteS5kb21haW4uY29tIiwic3ViIjoiNTkwOTRkNjRjMmRhN2E3MWI4NTljYTFhIiwiaWF0IjoxNDk0MjY1NTE1LCJleHAiOjE0OTQ4NzAzMTV9.SqsLeToG8-_3CV1Yr4Z4SUIv4-vqGbntGwFLB4i7n-w";
user = {
"__v" = 0;
"_id" = 59094d64c2da7a71b859ca1a;
createdAt = "2017-05-03T03:24:20.309Z";
email = "dylan#msn.com";
name = Dylan;
updatedAt = "2017-05-03T03:24:20.309Z";
};
}
This get converted to a dictionary as userInfo. However, since my token is not returned in userinfo how can I parse this into a class. Currently, my
initialization looks like this with hard coded strings:
self.loggedInUser.setUser(firstName: "Dylan" as String!,
email: "dylan#msn.com"as String!,
token: "test" as String!,
id: "1"as String!,
longitude: "40.0"as String!,
latitude: "-70"as String!)
self.loggedInUser.printUser()
Full Request Attempt:
func loginPost(email: String, password: String, completion: #escaping (_ userInfo: User?, _ error: [[String : Any]]?) -> Void) {
let headers: HTTPHeaders = ["Content-Type" : "application/json"]
let parameters: Parameters = ["email": "\(email)","password": "\(password)"]
Alamofire.request(loginURL, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: headers)
.validate(contentType: ["application/json"])
.responseJSON { response in
if response.response?.statusCode == 200 {
print("LOGIN_SUCCESS with JSON: \(response.result.value!)")
if let userInfo = response.value as? [String : Any] {
self.loggedInUser.setUser(firstName: userInfo.["user"]["name"] as! String!,
email: userInfo["email"] as! String!,
token: userInfo["token"] as! String!,
id: "1"as String!,
longitude: "40.0"as String!,
latitude: "-70"as String!)
self.loggedInUser.printUser()
return completion(self.loggedInUser, nil)
}
} else {
print("LOGIN_FAILURE with JSON: \(response.result.value!)")
if let error = response.result.value as? [[String: Any]] {
//If you want array of task id you can try like
return completion(nil, error)
}
}
}
}

Find the corrected code below:
if response.response?.statusCode == 200 {
print("LOGIN_SUCCESS with JSON: \(response.result.value!)")
if let responseData = response.value as? [String : Any] {
let token = responseData["token"] as? String
if let userInfo = responseData as? [String : Any] {
self.loggedInUser.setUser(firstName: userInfo.["user"]["name"] as! String!,
email: userInfo["email"] as! String!,
token: userInfo["token"] as! String!,
id: "1"as String!,
longitude: "40.0"as String!,
latitude: "-70"as String!)
self.loggedInUser.printUser()
return completion(self.loggedInUser, nil)
}
}
}

Related

how to link a nickname with a keychain

Before, I was storing sensitive data(email, nickame, and password) in a json file. Now I am storing the email and the password into keychains ( kSecAttrAccount is the email)
But I have no ideea how can I store the nickname in the keychain. Or where can I store the nickname so it gets linked with a specific keychain.
import Foundation
import SwiftUI
func save(account: String, password: String) {
do {
try KeychainManager.save(
service: "loseamp",
account: account,
password: password.data(using: .utf8) ?? Data())
} catch {
print(error)
}
}
func getPassword(account: String, password: String) {
guard let data = KeychainManager.get(
service: "loseamp",
account: account
) else {
print("Failed to read password")
return
}
let password = String(decoding: data, as: UTF8.self)
print("read password here: \(password)")
}
class KeychainManager {
enum KeychainError: Error {
case duplicateEntry
case unknown(OSStatus)
}
static func save(service: String, account: String, password: Data) throws {
// service, account, password, class, data
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecValueData as String: password as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemAdd(query as CFDictionary, nil)
guard status != errSecDuplicateItem else {
throw KeychainError.duplicateEntry
}
guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
static func get(service: String, account: String) -> Data? {
// service, account, password, class, data
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecReturnData as String: kCFBooleanTrue,
kSecClass as String: kSecClassGenericPassword,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
print("Read status: \(status)")
return result as? Data
}
func update(service: String, account: String, password: Data) {
let query = [
kSecAttrService: service as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount: account as AnyObject,
] as CFDictionary
let updatedData = [kSecValueData: password] as CFDictionary
SecItemUpdate(query, updatedData)
}
func delete(service: String, account: String) {
let query = [
kSecAttrService: service as AnyObject,
kSecClass: kSecClassGenericPassword,
kSecAttrAccount: account as AnyObject
] as CFDictionary
SecItemDelete(query)
}
func isEmailDuplicate(service: String, account: String) -> Bool {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword
]
let status = SecItemAdd(query as CFDictionary, nil)
return status == errSecDuplicateItem
}
}

How can I write a generic wrapper for alamofire request in swift?

How can I write a generic wrapper for alamofire request ?
How can I convert the POST and GET function to Generic function in the following code?
I need to have generic request functions that show different behaviors depending on the type of data received.
Also, Can the response be generic?
My non-generic code is fully outlined below
import Foundation
import Alamofire
import SwiftyJSON
// for passing string body
extension String: ParameterEncoding {
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = data(using: .utf8, allowLossyConversion: false)
return request
}
}
public class ConnectionManager {
func Post (FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
guard let data = response.result.value else { return }
print(data)
completion(true)
} else {
print("error reg auth service")
guard let er = response.result.value else { return }
print(er)
completion(false)
debugPrint(response.result.error as Any)
}
}
}
func get(FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) -> [Person] {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .get, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
print("no error login in authservice")
guard let data = response.result.value else { return }
print(data)
completion(true)
}
else if response.response?.statusCode == 400 {
print("error 400 login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
} else {
print("error ## login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
}
}
return [Person]()
}
}
The best idea is to use the URLRequestConvertible Alamofires protocol and create your own protocol and simple structs for every API request:
import Foundation
import Alamofire
protocol APIRouteable: URLRequestConvertible {
var baseURL: String { get }
var path: String { get }
var method: HTTPMethod { get }
var parameters: Parameters? { get }
var encoding: ParameterEncoding { get }
}
extension APIRouteable {
var baseURL: String { return URLs.baseURL }
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL().appendingPathComponent(path)
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
return try encoding.encode(urlRequest, with: parameters)
}
}
and request can look like this:
struct GetBooks: APIRouteable {
var path: String
var method: HTTPMethod
var parameters: Parameters?
var encoding: ParameterEncoding
}
and inside the APIClient prepare the generic method:
func perform<T: Decodable>(_ apiRoute: APIRouteable,
completion: #escaping (Result<T>) -> Void) {
let dataRequest = session
.request(apiRoute)
dataRequest
.validate(statusCode: 200..<300)
.responseDecodable(completionHandler: { [weak dataRequest] (response: DataResponse<T>) in
dataRequest.map { debugPrint($0) }
let responseData = response.data ?? Data()
let string = String(data: responseData, encoding: .utf8)
print("Repsonse string: \(string ?? "")")
switch response.result {
case .success(let response):
completion(.success(response))
case .failure(let error):
completion(.failure(error))
}
})
}
and use it:
func getBooks(completion: #escaping (Result<[Book]>) -> Void) {
let getBooksRoute = APIRoute.GetBooks()
perform(getBooksRoute, completion: completion)
}

What is causing this "Cannot convert type" error in Swift?

I want to call a simple api and return the struct of JSON returned from server. I am using alamofire to achieve this.
This is my function
func loginUser(user_name: String, password: String, callBack:(Login) -> Void){
let parameters: Parameters = ["user_name": user_name,"password": password];
let urlString=serverURL+"login.php";
Alamofire.request(urlString, method: .post, parameters: parameters, encoding: URLEncoding.httpBody)
.responseJSON { response in
if let status = response.response?.statusCode {
switch(status) {
case 200:
guard response.result.isSuccess else {
//error handling
return
}
if let login: Login = response.result.value {
callBack(login)
} else {
callBack(Login(user_id: 0, status:0))
}
default:
print("HTTP Error : \(status)")
callBack(Login(user_id: 0, status:0))
}
}
}
}
struct Login {
let user_id: Int
let status: Int
}
This line is generating a "cannot convert type" error.
if let login: Login = response.result.value {
What am I doing wrong?
As mag_zbc commented response.result.value is either [String : Any] or Array.
So first you need to cast them to respective type like suppose it's [String : Any] you can cast it as shown below:
if let response = response.result.value as? [String : Any] {
//here you will get your dictionary
}
Now next thing you need to do is you need to take out the values from your response object which you can do by accessing the values from the keys from your response object which will look like:
let user_id = response["userid_key"] as? Int ?? 0
let status = response["status_key"] as? Int ?? 0
This way you will get user_id and status from your response object and then last step will be to set that data into your struct like shown below:
let login = Login(user_id: user_id, status: status)
Now you can set your callback with callBack(login)
and your final code will look like:
if let response = response.result.value as? [String : Any] {
let user_id = response["userid_key"] as? Int ?? 0
let status = response["status_key"] as? Int ?? 0
let login = Login(user_id: user_id, status: status)
callBack(login)
}

Json Object mapping Swift

I would like to do a Post request to a url however i just learnt of new concept in Swift called Object mapping. All the tutorial i have learnt how to map the Json object to swift structs or classes but non shows me how to use these objects once they are mapped.
How do i access these objects such that i use them when doing a post request.
Here is example Json:
{
"country": "string",
"dateOfBirth": "string",
"email": "string",
"gender": "string",
"id": "string",
"interaction": {
"deviceOS": "string",
"deviceType": "string",
"interactionLocation": "string",
"interactionType": "string",
"timeStamp": "string"
},
"name": "string",
"occupation": "string",
"passportOrIDimage": "string",
"phoneNumber": "string",
"physicalAddress": "string",
"salutation": "string",
"surname": "string",
"userlogin": {
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true,
"password": "string",
"roles": [
{
"roleName": "string"
}
],
"username": "string"
}
}
Example Object mapping in swift 4 :
struct Register: Codable {
let country: String?
let dateOfBirth: String?
let email: String?
let gender: String?
let id: String?
let interaction: Interaction?
let name: String?
let occupation: String?
let passportOrIDimage: String?
let phoneNumber: String?
let physicalAddress: String?
let salutation: String?
let surname: String?
let userlogin: Userlogin?
enum CodingKeys: String, CodingKey {
case country = "country"
case dateOfBirth = "dateOfBirth"
case email = "email"
case gender = "gender"
case id = "id"
case interaction = "interaction"
case name = "name"
case occupation = "occupation"
case passportOrIDimage = "passportOrIDimage"
case phoneNumber = "phoneNumber"
case physicalAddress = "physicalAddress"
case salutation = "salutation"
case surname = "surname"
case userlogin = "userlogin"
}
}
struct Interaction: Codable {
let deviceOS: String?
let deviceType: String?
let interactionLocation: String?
let interactionType: String?
let timeStamp: String?
enum CodingKeys: String, CodingKey {
case deviceOS = "deviceOS"
case deviceType = "deviceType"
case interactionLocation = "interactionLocation"
case interactionType = "interactionType"
case timeStamp = "timeStamp"
}
}
struct Userlogin: Codable {
let accountNonExpired: Bool?
let accountNonLocked: Bool?
let credentialsNonExpired: Bool?
let enabled: Bool?
let password: String?
let roles: [Role]?
let username: String?
enum CodingKeys: String, CodingKey {
case accountNonExpired = "accountNonExpired"
case accountNonLocked = "accountNonLocked"
case credentialsNonExpired = "credentialsNonExpired"
case enabled = "enabled"
case password = "password"
case roles = "roles"
case username = "username"
}
}
struct Role: Codable {
let roleName: String?
enum CodingKeys: String, CodingKey {
case roleName = "roleName"
}
}
// MARK: Convenience initializers
extension Register {
init(data: Data) throws {
self = try JSONDecoder().decode(Register.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Interaction {
init(data: Data) throws {
self = try JSONDecoder().decode(Interaction.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Userlogin {
init(data: Data) throws {
self = try JSONDecoder().decode(Userlogin.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
extension Role {
init(data: Data) throws {
self = try JSONDecoder().decode(Role.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
init(fromURL url: URL) throws {
try self.init(data: try Data(contentsOf: url))
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
I would to put the data i want to post in the params dictionary this where i am not sure how to do so using object mapping.
My post request :
var request = URLRequest(url: URL(string: "http://testURL")!)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: params as Any, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
print(response!)
do {
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, AnyObject>
let alert = UIAlertController(title: "Response", message: "message", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
print(json)
} catch {
print("error")
}
})
task.resume()
}
if you observe in your struct
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
In your post request you can use like this
request.httpBody = try? objectOfCodableStruct.jsonData()
Hope it is helpful

Facebook Graph Request using Swift3 -

I am rewriting my graph requests with the latest Swift3. I am following the guide found here - https://developers.facebook.com/docs/swift/graph.
fileprivate struct UserProfileRequest: GraphRequestProtocol {
struct Response: GraphResponseProtocol {
init(rawResponse: Any?) {
// Decode JSON into other properties
}
}
let graphPath: String = "me"
let parameters: [String: Any]? = ["fields": "email"]
let accessToken: AccessToken? = AccessToken.current
let httpMethod: GraphRequestHTTPMethod = .GET
let apiVersion: GraphAPIVersion = .defaultVersion
}
fileprivate func returnUserData() {
let connection = GraphRequestConnection()
connection.add(UserProfileRequest()) {
(response: HTTPURLResponse?, result: GraphRequestResult<UserProfileRequest.Response>) in
// Process
}
connection.start()
However, I am getting this error in the connection.add method:
Type ViewController.UserProfileRequest.Response does not conform to protocol GraphRequestProtocol.
I can't seem to figure this out what to change here. It seems like the developer guide is not up to date on Swift3, but I am not sure that is the issue.
Is anyone able to see what is wrong here?
Thanks.
Browsing on the github issues, i found a solution.
https://github.com/facebook/facebook-sdk-swift/issues/63
Facebook documentation for Swift 3.0 and SDK 0.2.0 is not yet updated.
This works for me:
let params = ["fields" : "email, name"]
let graphRequest = GraphRequest(graphPath: "me", parameters: params)
graphRequest.start {
(urlResponse, requestResult) in
switch requestResult {
case .failed(let error):
print("error in graph request:", error)
break
case .success(let graphResponse):
if let responseDictionary = graphResponse.dictionaryValue {
print(responseDictionary)
print(responseDictionary["name"])
print(responseDictionary["email"])
}
}
}
enjoy.
This code works for me, first I make a login with the correct permissions, then I build the GraphRequest for get the user information.
let login: FBSDKLoginManager = FBSDKLoginManager()
// Make login and request permissions
login.logIn(withReadPermissions: ["email", "public_profile"], from: self, handler: {(result, error) -> Void in
if error != nil {
// Handle Error
NSLog("Process error")
} else if (result?.isCancelled)! {
// If process is cancel
NSLog("Cancelled")
}
else {
// Parameters for Graph Request without image
let parameters = ["fields": "email, name"]
// Parameters for Graph Request with image
let parameters = ["fields": "email, name, picture.type(large)"]
FBSDKGraphRequest(graphPath: "me", parameters: parameters).start {(connection, result, error) -> Void in
if error != nil {
NSLog(error.debugDescription)
return
}
// Result
print("Result: \(result)")
// Handle vars
if let result = result as? [String:String],
let email: String = result["email"],
let fbId: String = result["id"],
let name: String = result["name"] as? String,
// Add this lines for get image
let picture: NSDictionary = result["picture"] as? NSDictionary,
let data: NSDictionary = picture["data"] as? NSDictionary,
let url: String = data["url"] as? String {
print("Email: \(email)")
print("fbID: \(fbId)")
print("Name: \(name)")
print("URL Picture: \(url)")
}
}
}
})
Here is my code like. I use Xcode 8, Swift 3 and it works fine for me.
let parameters = ["fields": "email, id, name"]
let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: parameters)
_ = graphRequest?.start { [weak self] connection, result, error in
// If something went wrong, we're logged out
if (error != nil) {
// Clear email, but ignore error for now
return
}
// Transform to dictionary first
if let result = result as? [String: Any] {
// Got the email; send it to Lucid's server
guard let email = result["email"] as? String else {
// No email? Fail the login
return
}
guard let username = result["name"] as? String else {
// No username? Fail the login
return
}
guard let userId = result["id"] as? String else {
// No userId? Fail the login
return
}
}
} // End of graph request
Your UserProfileRequest should look like this:
fileprivate struct UserProfileRequest: GraphResponseProtocol {
fileprivate let rawResponse: Any?
public init(rawResponse: Any?) {
self.rawResponse = rawResponse
}
public var dictionaryValue: [String : Any]? {
return rawResponse as? [String : Any]
}
public var arrayValue: [Any]? {
return rawResponse as? [Any]
}
public var stringValue: String? {
return rawResponse as? String
}
}