I really can't find a way to get the user value from the below output
If I print the result directly I get
(TotersAPIFrameWork.Result<TotersAPIFrameWork.DataContainer<TotersAPIFrameWork.User>>) $R5 = success {
success = {
user = {
id = 200
first_name = "yara"
last_name = "Padberg"
email = "client#client.com"
phone_number = "+9999"
type = "client"
account_type = "email"
sm_user_id = nil
sm_access_token = nil
picture = ""
deleted_at = nil
created_at = "2018-04-04 14:33:29"
updated_at = "2018-04-24 11:15:45"
rating = "5"
rating_count = 1
regid = ""
platform = "ios"
is_agora = nil
currency_id = 1
is_verified = true
promo_code_id = nil
referral_promo_code_id = nil
op_city_id = 1
is_daas = false
token = ""
retailer_info = nil
}
}
}
I tried to convert directly to user like below
p result as? User
it is returning nil, so how should i get the result from the DataContainer?
Thanks for your help
After spending more time and understanding what i'm dealing with :) i figured out how should i get the user object.
First of all the Result that i'm using is an enum and my callback is returning Result so below was the solution:
let login = PostLogin.init(email: "client#client.com", password: "pass")
let client = APIClient(publicKey: "", privateKey:"")
client.send(login) { (result) in
switch result {
case .success(let value):
print(value)
case .failure(let error):
print(error)
}
}
The result is an enum:
import Foundation
public enum Result<Value> {
case success(Value)
case failure(Error)
}
public typealias ResultCallback<Value> = (Result<Value>) -> Void
The Value struct is below:
import Foundation
/// All successful responses return this, and contains all
/// the metainformation about the returned chunk.
public struct DataContainer<T: Decodable>: Decodable {
public var user:T?
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: UserResponseKeys.self)
if let u = try container.decodeIfPresent(T.self, forKey: .user)
{
self.user = u
}
}
}
now i can get user like this:
let user = value.user
Hope this will help others.
Related
I have the following enum:
enum MembershipLevel: Int {
case basic = 25
case bronze = 50
case silver = 100
case gold = 500
case platinum = 1000
}
which then I have the following firebase lookup:
userRef.child(userId).child("memeberlevel").observeSingleEvent(of: .value, with: { (snapshot) in
self.userRef.child(userId).child("count").observeSingleEvent(of: .value, with: { (snap) in
if((snap.value!) < MembershipLevel.(snapshot.value!).rawValue) {
completion(false)
} else {
completion(true)
}
})
})
The code above throws a complier error due to the following code:
MembershipLevel.(snapshot.value).rawValue
How can I reference the enum values dynamically, since the code snippet MembershipLevel.basic.rawValue is perfectly OK?
You should not use an enum here. Enum cases cannot be referred to dynamically.
While you could do something like this:
enum MembershipLevel: Int, CaseIterable {
case basic = 25
case bronze = 50
case silver = 100
case gold = 500
case platinum = 1000
init?(string: String) {
if let value = MembershipLevel.allCases.first(where: { "\($0)" == string }) {
self = value
} else {
return nil
}
}
}
// usage:
let userValue = snapValue.value as! Int
let membershipString = snapshot.value as! String
if userValue < MembershipLevel(string: membershipString)!.rawValue {
}
It might break in the future as I don't think "\($0)" producing the enum case name is guaranteed.
I would use a dictionary:
let membershipLevelNameDict = [
"basic": 25,
"bronze": 50,
"silver": 100,
"gold": 500,
"platinum": 1000
]
Usage:
let userValue = snapValue.value as! Int
let membershipString = snapshot.value as! String
if userValue < membershipLevelNameDict[membershipString] ?? 0 {
}
Using this dictionary, you can also create an instance of your enum:
membershipLevelNameDict[membershipString].flatMap(MembershipLevel.init(rawValue:))
But if you want to compare the raw values, just access the dictionary directly like in the first snippet.
You can make your enumeration conform to Comparable protocol and create an initializer that takes an Int:
extension MembershipLevel: Comparable {
init?(_ value: Any?) {
switch value {
case let int as Int:
self.init(rawValue: int)
case let string as String:
switch string {
case "basic": self = .basic
case "bronze": self = .bronze
case "silver": self = .silver
case "gold": self = .gold
case "platinum": self = .platinum
default: return nil
}
default: return nil
}
}
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
Now you can initialize your enumeration from the snapshot values and compare them directly
if let lhs = MembershipLevel(snap.value),
let rhs = MembershipLevel(snapshot.value),
lhs < rhs {
completion(false)
}
} else {
completion(true)
}
I want to use the same model struct for sending parameters and receiving JSON response from an API.
I want to send email and password and receive JSON response message.
But when I declare a property referencing another model in my user model, I am forced to provide a value.
My API does not return the parameters email and password, it simply returns an array if login is unsuccessful and returns the user info on successful login.
this is the json response on error :
Optional({
errors = {
msg = "USER_DOES_NOT_EXIST";
};
this is my model I used to post:
struct LoginUser: Codable {
let email: String?
let password: String?
}
this is the response on successful login:
Optional({
token = 267e6a9d5a128fb1f44e670fcd89793af50fa9a831e6ae7dc2f0592b508bd224a71290fbdf1619cf52ed0f2c034b26383b915343f3822a52e1386c042484744b71811f80d3cb663fc76a6cc74d4866737421e3b9d62e35b415c0f7c385e5c70d472a5facf7f0101165d321c35eb20ae5f8bb32f06120e66a42de47c79a7587a2aa7f81f35c3821b9418e0c9142a7ec2b67b9755d3e17753dd8f1cdf3f71c0816627e2be26485f9b50ee1ad96a867856f0de736963c5ff59e9e37e92d5f3386f7;
user = {
"_id" = 5e52c0c5cf65d33726a98590;
email = "test7#gmail.com";
"first_name" = gagz;
"last_name" = bhullar;
verified = 0;
};
})
I want to modify my loginUser model such that it can both receive and send data.
is it possible?
if not, why?
For an API which returns two different models on success and failure I highly recommend to use an enum with associated values. The benefit is you get rid of the tons of optionals (and if let expressions) and you can use a descriptive switch for the success and failure cases.
enum Response : Decodable {
case success(UserResponse)
case failure(ErrorResponse)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let userData = try container.decode(UserResponse.self)
self = .success(userData)
} catch DecodingError.typeMismatch {
let errorData = try container.decode(ErrorResponse.self)
self = .failure(errorData)
}
}
}
And these are the corresponding structs
struct ErrorResponse: Decodable {
let errors: [String:String]
}
struct UserResponse: Decodable {
let token : String
let user: User
}
struct User: Decodable {
private enum CodingKeys : String, CodingKey {
case id = "_id"
case email
case firstName = "first_name"
case lastName = "last_name"
case verified
}
let id, email, firstName, lastName : String
let verified : Int // maybe Bool
}
To send only two key-value pairs an extra struct and Codable is overkill. Create a temporary [String:String] dictionary and encode it with JSONSerialization or create the JSON Data directly for example
func jsonSendData(email: String, password: String) -> Data {
let jsonString =
"""
{"email":"\(email)","password":"\(password)"}
"""
return Data(jsonString.utf8)
}
Although it is recommended to create separate model for handling the failure and success cases, still if you want to create a single model, you can do it like so,
struct LoginUser: Codable {
let email, password, token, id, firstName, lastName: String?
let verified: Int?
let errorMessage: String?
enum CodingKeys: String, CodingKey {
case email, token, _id, firstName, lastName, verified, user
case errors, msg
case password
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
token = try container.decodeIfPresent(String.self, forKey: .token)
if container.contains(.user) {
let user = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
id = try user.decodeIfPresent(String.self, forKey: ._id)
email = try user.decodeIfPresent(String.self, forKey: .email)
firstName = try user.decodeIfPresent(String.self, forKey: .firstName)
lastName = try user.decodeIfPresent(String.self, forKey: .lastName)
verified = try user.decodeIfPresent(Int.self, forKey: .verified)
errorMessage = nil
} else if container.contains(.errors) {
let errors = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .errors)
errorMessage = try errors.decodeIfPresent(String.self, forKey: .msg)
id = nil
email = nil
firstName = nil
lastName = nil
verified = nil
} else {
id = nil
email = nil
firstName = nil
lastName = nil
verified = nil
errorMessage = nil
}
password = nil
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(email, forKey: .email)
try container.encode(password, forKey: .password)
//add the keys that you want to send in the request...
}
}
Since, you haven't added the the JSON that you want to send in the request, I've assumed that you're only sending email and password in the request. You can add more in the above encode(to:) method.
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) }
}
I'm using CloudKit and I'm checking if a specific zone was already created.
For this example, let's say that a zone isn't set, so CloudKit retrieves me a CKError.
This CKError has a property called partialErrorsByItemID which is of type [AnyHashable : Error]?
Here's the code:
fileprivate func checkIfZonesWereCreated() {
let privateDB = CKContainer.default().privateCloudDatabase
let op = CKFetchRecordZonesOperation(recordZoneIDs: [zoneID1, zoneID2])
op.fetchRecordZonesCompletionBlock = { (dict, err) in
if let err = err as? CKError, let _err = err.partialErrorsByItemID {
print(_err)
/* prints
[AnyHashable(<CKRecordZoneID: 0x60800003cba0; ownerName=__defaultOwner__, zoneName=TestZone>): <CKError 0x60400005a760: "Zone Not Found" (26/2036); server message = "Zone 'TestZone' does not exist"; uuid = ...-2DF4E13F81E2; container ID = "iCloud.com.someContainer">]
*/
// If I iterate through the dictionary
_err.forEach({ (k, v) in
print("key:", k) // prints: key: <CKRecordZoneID: 0x60800002d9e0; ownerName=__defaultOwner__, zoneName=TestZone>
print("value:", v) // prints: value: <CKError 0x60400005a760: "Zone Not Found" (26/2036); server message = "Zone 'TestZone' does not exist"; uuid = ...-2DF4E13F81E2; container ID = "iCloud.com.someContainer
})
return
}
print("dict:", dict)
}
privateDB.add(op)
}
How do I parse this error? I need to access the zoneName ?
The key in _err is a CKRecordZoneID. Once you have that, use the zoneName property to get the zone name.
I would write your code as follows:
fileprivate func checkIfZonesWereCreated() {
let privateDB = CKContainer.default().privateCloudDatabase
let op = CKFetchRecordZonesOperation(recordZoneIDs: [zoneID1, zoneID2])
op.fetchRecordZonesCompletionBlock = { (dict, err) in
if let err = err as? CKError {
switch err {
case CKError.partialFailure:
if let _err = err.partialErrorsByItemID {
for key in _err.keys {
if let zone = key as? CKRecordZoneID {
let name = zone.zoneName
print("Missing zone: \(name)")
}
}
return
}
default:
break
}
}
print("dict:", dict)
}
privateDB.add(op)
}
I found an error when I test some codes from Github.
class Profile {
let text: String
let date: String
let id: String?
init?(data: NSDictionary?) {
if let text = data?.valueForKeyPath(Test.Text) as? String {
self.text = text
if let date = data?.valueForKeyPath(Test.Created) as? String {
self.date = date
id = data?.valueForKeyPath(Test.ID) as? String
}
}else{
return nil
}
}
struct Test {
static let Text = "text"
static let Created = "created"
static let ID = "id"
}
}
The line of init? shows the error "constants self.data used before being initialized."
And I create a similar class of it, like
class Context {
let words: String
init?(text:String?) {
if let words = text {
self.words = words
}else{
return nil
}
}
}
This time it shows " all stored properties of class instance must be initialized before returing nil from an initializer."
For the first one , there is a workaround that I can delete the else block and give each properties an empty value would fix the error. However it would have me change the properties mutable.
( I don't want it to be mutable)
And for the second example, I just insert self.word = ""before the line of return nil could also fix the error.
But I really wonder why these similar cases show the different errors and realize the logic of Swift, and how can I fix it correctly?
Thank you for helping me.
Try this version of the code.
Code 1:
class Profile {
var text: String = ""
var date: String = ""
var id: String? = ""
init?(data: NSDictionary?) {
if let text = data?.valueForKeyPath(Test.Text) as? String {
self.text = text
if let date = data?.valueForKeyPath(Test.Created) as? String {
self.date = date
id = data?.valueForKeyPath(Test.ID) as? String
}
}else{
return nil
}
}
struct Test {
static let Text = "text"
static let Created = "created"
static let ID = "id"
}
}
Code 2:
class Context {
var words: String = ""
init?(text:String?) {
if let words = text {
self.words = words
} else {
return nil
}
}
}
York initializers are incomplete and that's why you get the message. Swift is type save, which means that all non optional properties must be initialized before the class is returned. Even when the class returns a nil. secondly, you can't call self (which is the class itself) if you haven't initialized the class. However, that should not be a problem in your case, since you've defined a root class. For your first class, please, implement the code like this and it should work.
class Profile {
struct Test {
static let Text = "text"
static let Created = "created"
static let ID = "id"
}
let text: String
let date: String
let id: String?
init?(data: NSDictionary?) {
guard let tempText = data?.valueForKeyPath(Test.Text) as? String else {
text = ""
date = ""
id = nil
return nil
}
text = tempText
if let tempDate = data?.valueForKeyPath(Test.Created) as? String {
date = tempDate
id = data?.valueForKeyPath(Test.ID) as? String
} else {
date = ""
id = nil
}
}
}
For the second class you need to do a similar thing, which means in the else statement give words a value and it should be okay.