The app receives JSON object from the server. And some field in the object can be missing, or be nil. So, I need to find them before computing a value. I have a code fragment as below:
print(package["store"]!["cover"]) //here, console output: "nil"
if ((package["store"]!["cover"]) != nil) {
//the 'if' statment above has no effect, statment below is executed,
// and error occurs.
imageName = STATIC_IMAGE_URL + (package["cover"] as! String)
}
How can I detect if the response JSON has some missing or nil fields?
You can use optional chaining, optional binding, and conditional casting combined in the same if-let statement:
if let cover = package["store"]?["cover"] as? String {
imageName = STATIC_IMAGE_URL + cover
} else {
imageName = "someDefaultImage"
}
It works like this:
package["store"]?["cover"] will return nil if either package["store"], or package["store"]["cover"] is nil
the conditional cast as? String returns nil if the expression to the left is not a String
finally the if-let construct will either populate cover with the actual string, or will go on the else branch if there's no match
Related
I have a dictionary of data passed from another viewController, and when I tried to fetch the id from the dictionary it gives me conversion error!
var comments = [JSON]()
#IBAction func AddCommentBTN(_ sender: Any) {
let commentTXT = CommentTXTField.text
let name = self.accountDetails["name"]
let email = self.accountDetails["email"]
let articleId = self.comments["id"].string! ----> error is here
API.AddComment(articleId: "", name: name!, email: email!, message: commentTXT!) { (error: Error?, success: Bool) in
if success {
self.CommentTableView.reloadData()
print("Registerd Successfuly")
} else {
print("Faile To Comment")
}
}
}
Firstly, self.comments is an array of JSON object. So before getting value from the JSON you need to get the object first from the array. Before getting the 0 index value make sure the array contains value.
Seems like you are using SwiftyJSON to parse the value. So by using .intValue you can get the Int value. .intValue will return a Int type value. If the key not present there then this will return you the 0 value. .intValue not an Optional type.
Try this
let articleId = self.comments[0]["id"].intValue
instead of
let articleId = self.comments["id"].string!
You are converting a String to Int which is not as straightforward as you may think. The String can contain characters that is not a number like "Forest". To convert a String to Int you can use:
if let myInt = Int(self.comments["id"]) {
let articleId = myInt
}
It will check if the string is a correct Int and return an Optional value. You can also use a fallback to define a default value if the String is not correct Int
let myInt = Int(self.comments["id"]) ?? 0
That means “attempt to convert self.comments["id"] to an integer, but if the conversion failed because it contained something invalid then use 0 instead.”
The values of a json are optional and might be on any allowed type. A way parsing this save would be getting the value for "id", which will be an optional, and try to convert this optional to an Int. The Int itself is optional as well. (Short, it might be there or not)
guard let value = self.comments["id"], let articleId = Int64(value) else {
// Not valid
return
}
print(articleId)
A better way would be parsing your json data to an object in the first place. Your controller would not need to deal with the parsing of optional data, but instead just get the right data from the object.
I am new to Swift and am trying to compare my Error description name with different String constants to show the user different results, based on the error.
I am using:
let errorName = errors.first?["name"].debugDescription
The value of errorName comes as "Optional(AlreadyActiveUser)" and when i compare this to my constant string "AlreadyActiveUser", i get false.
I have tried many things, but i am not able to get the value of the string inside the optional.
Someone, please help.
You can use optional binding in this case...
guard let errorName = errors.first?["name"].debugDescription as? String {
print("value is not present...")
return
}
print(errorName)
//here you can compare errorName == "AlreadyActiveUser"
you can use this
if let errorName = errors.first?["name"] as? String {
print(errorName)
//here you can compare errorName == "AlreadyActiveUser"
}
else
{
print("value is not present...")
}
try let errorName = errors.first!["name"].debugDescription
Notes that I forced wrapping first with ! instead of ?.
I am using Vapor for Swift backend. Following is the code i am working with.
drop.post("postTodo") { request in
var jsonContent: JSON?
if let contentType = request.headers["Content-Type"], contentType.contains("application/json"), let jsonData = request.json {
jsonContent = jsonData
print("Got JSON: \(jsonContent)")
}
guard let id = jsonContent?.node.object?["id"]?.string
else {
return JSON(["message": "Please include mandatory parameters"])
}
let tempId = Int(id)!
I am getting "id" as optional string for eg: Optional("123") for jsonContent?.node.object?["id"]?.string
When I try to convert it to int using Int(id)! i get back nil
If i try to do let tempId = Int(id!) it gives error.
But when i do the same thing in playground i get proper int value.
let id: String?
id = "1234"
let myInt = Int(id!)
Why Optional string to Int is not working properly in my Vapor app ?
Any idea.
If "id" is an optional string, then you probably don't want to be force unwrapping it with the "!".
The safest approach would be something like:
if let id = id
{
let myIdAsInt = Int(id)
}
The reason it "works" in the playground, is you are definitely assigning a non-nil value to the string (therefore you get away with the force unwrap).
String!might contain a string, or it might contain nil. It’s like a regular optional, but Swift lets you access the value directly without the unwrapping safety. If you try to do it, it means you know there’s a value there – but if you’re wrong your app will crash.
var optionalString: String? = "123"
// first check if it doesn't contain nil
if let str = optionalString {
// string to -> Int
if let id = Int(str) {
print(id) // work with id
}
} else {
// optionalString contains nil
}
what i found is in my iOS code i had a struct with optional properties coz of which when mapped to Dict gave object with optional values to keys.
If I make properties non optional and send it to vapor backend after it works fine.
So basically it was the case of using Optionals properly.
In Class A I have this variable:
var collectionElement : PFObject?
I then need to call a method which users it. But before I want to check if it is nil or not. So I do something like:
if collectionElement != nil{
if let elementName = collectionElement["elementName"] as? NSString{
elementTitle.text = elementName as String
}
...
query.whereKey("fileCollectionElement", equalTo:collectionElement!)
}
In class B I assign the value like this:
cell.collectionElement = collectionElements[indexPath.row]
where collectionElements[indexPath.row] is a PFObject.
This code gives me errors, and by playing around with the ! and ?, I can make the app run but it crashes, specifically because of the line coll... != nil
I am really confused with the ? and ! things. What is the right thing to use and when? Why sometimes I cannot check if something is nil (in Objective-C I could do it all the time)?
? and ! for variables:
? is used when the the variable could be an object or can be nil.
! is used when the variable could be an object or can be nil BUT it should never be nil. This prevents the coder from having to unwrap it everytime.
? and ! for casting:
"x as? y" checks if the "x" CAN be casted as "y" AND it if so it WILL be casted as "y".
"x as! y" forces the cast from x to y
So in your code you should check as? String, because it seems like you are trying to cast it later on anyway to String. So try this:
if collectionElement != nil{
if let elementName = collectionElement["elementName"] as? String{
elementTitle.text = elementName
}
...
query.whereKey("fileCollectionElement", equalTo:collectionElement!)
}
As for the error when you index countElements, this could return a nil value so you should make sure the two sides agree on the type they working with. if countElements contains an optional (PFObject?) then make sure cell.collection element is an optional (PFObject?) also.
Having the same exact types is crucial in Swift.
I am doing what I believe to be a very simple task. I'm trying to get a value out of a dictionary if the key exists. I am doing this for a couple keys in the dictionary and then creating an object if they all exist (basically decoding a JSON object). I am new to the language but this seems to me like it should work, yet doesn't:
class func fromDict(d: [String : AnyObject]!) -> Todo? {
let title = d["title"]? as? String
// etc...
}
It gives me the error: Operand of postfix ? should have optional type; type is (String, AnyObject)
HOWEVER, if I do this, it works:
class func fromDict(d: [String : AnyObject]!) -> Todo? {
let maybeTitle = d["title"]?
let title = maybeTitle as? String
// etc...
}
It appears to be basic substitution but I may be missing some nuance of the language. Could anyone shed some light on this?
The recommended pattern is
if let maybeTitle = d["title"] as? String {
// do something with maybeTitle
}
else {
// abort object creation
}
It is possibly really a question of nuance. The form array[subscript]? is ambiguous because it could mean that the whole dictionary (<String:AnyObject>) is optional while you probably mean the result (String). In the above pattern, you leverage the fact that Dictionary is designed to assume that accessing some key results in an optional type.
After experimenting, and noticing that the ? after as is just as ambiguous, more, here is my solution:
var dictionary = ["one":"1", "two":"2"]
// or var dictionary = ["one":1, "two":2]
var message = ""
if let three = dictionary["three"] as Any? {
message = "\(three)"
}
else {
message = "No three available."
}
message // "No three available."
This would work with all non-object Swift objects, including Swift Strings, numbers etc. Thanks to Viktor for reminding me that String is not an object in Swift. +
If you know the type of the values you can substitute Any? with the appropriate optional type, like String?
There are a few of things going on here.
1) The ? in d["title"]? is not correct usage. If you're trying to unwrap d["title"] then use a ! but be careful because this will crash if title is not a valid key in your dictionary. (The ? is used for optional chaining like if you were trying to call a method on an optional variable or access a property. In that case, the access would just do nothing if the optional were nil). It doesn't appear that you're trying to unwrap d["title"] so leave off the ?. A dictionary access always returns an optional value because the key might not exist.
2) If you were to fix that:
let maybeTitle = d["title"] as? String
The error message changes to: error: '(String, AnyObject)' is not convertible to 'String'
The problem here is that a String is not an object. You need to cast to NSString.
let maybeTitle = d["title"] as? NSString
This will result in maybeTitle being an NSString?. If d["title"] doesn't exist or if the type is really NSNumber instead of NSString, then the optional will have a value of nil but the app won't crash.
3) Your statement:
let title = maybeTitle as? String
does not unwrap the optional variable as you would like. The correct form is:
if let title = maybeTitle as? String {
// title is unwrapped and now has type String
}
So putting that all together:
if let title = d["title"] as? NSString {
// If we get here we know "title" is a valid key in the dictionary, and
// we got the type right. title has now been unwrapped and is ready to use
}
title will have the type NSString which is what is stored in the dictionary since it holds objects. You can do most everything with NSString that you can do with String, but if you need title to be a String you can do this:
if var title:String = d["title"] as? NSString {
title += " by Poe"
}
and if your dictionary has NSNumbers as well:
if var age:Int = d["age"] as? NSNumber {
age += 1
}