Working with datatypes in swift - swift

I'm really new to swift and spent whole day trying to parse simple json, received thought websockets. Finally I got parsed (kinda), but many things still lay way beyond my understanding and I ask for some explanation.
Ok, here is what I have got so far
First of all I declare variable QUOTES, which will be Dictionary, same as in json
var QUOTES: Dictionary<String, Dictionary<String, Any>>
I want my QUOTES var look like this at the end:
{
"AAPL": {name: "Apple Comp", "price": 100},
"F": {name: "Ford Comp", "price": 200}
}
When my websocket receives data, I want to populate QUOTES with received data.
socket.on("q"){data, ack in
//data looks like this: [{q:[{c: "AAPL", price: 100}, {c: "F", price: 100}]}]
//I convert first element of received [Any] to dictionary
let object = data[0] as! Dictionary<String, NSArray>
//I get array of quotes out of received data
let quotes = object["q"] as! [Dictionary<String, Any>]
//Now I want to iterate through received quotes to fill me QUOTES variable declared in the very beginning
for quote in quotes {
//I know ticker of current quote
let ticker = quote["c"] as! String
//Not sure if I have to do it - if QUOTES does not have current ticker in dictionary, I create it as empty dictionary
if (QUOTES[ticker] == nil) {
QUOTES[ticker] = [String: Any]()
}
//Now I iterate properties of received quote
for(k,v) in quote {
//And I want to fill my QUOTES dictionary,
// but I get compile error
//Value of optional type '[String : Any]?' not unwrapped; did you mean to use '!' or '?'?
//I don't understand what compiler wants me to do?
QUOTES[ticker][k] = v
}
}
}
but I get compile error
Value of optional type '[String : Any]?' not unwrapped; did you mean to use '!' or '?'?
I don't understand what compiler wants me to do?

Related

How to add/Update key/values in dict in swift?

func exercise() {
var stockTickers: [String: String] = [
"APPL" : "Apple Inc",
"HOG": "Harley-Davidson Inc",
"BOOM": "Dynamic Materials",
"HEINY": "Heineken",
"BEN": "Franklin Resources Inc"
]
stockTickers["WORK"] = ["Slack Technologies Inc"]
stockTickers["BOOM"] = ["DMC Global Inc"]
print(stockTickers["WORK"]!)
print(stockTickers["BOOM"]!)
}
Error: Cannot assign value of type '[String]' to subscript of type 'String'
I do not understand the error, I'm new to swift, can someone guide me through this and tell mw why I see this error.
Alexander explained what you need to do in abstract terms. (Voted)
Specifically, change
stockTickers["WORK"] = ["Slack Technologies Inc"]
stockTickers["BOOM"] = ["DMC Global Inc"]
To
stockTickers["WORK"] = "Slack Technologies Inc"
stockTickers["BOOM"] = "DMC Global Inc"
The expression ["Slack Technologies Inc"] defines a String array containing a single string. That's not what you want. you defined a dictionary of type [String:String]
If you wanted your dictionary to have String keys and values that contained arrays of strings, you'd have to change the way you declared your dictionary:
var stockTickers: [String: [String]] = [
"APPL" : ["Apple Inc"],
"HOG": ["Harley-Davidson Inc"],
"BOOM": ["Dynamic Materials"],
"HEINY": ["Heineken"],
"BEN": ["Franklin Resources Inc"]
]
["DMC Global Inc"] is an array literal containing a string. It evaluates to a value of type [String] (a.k.a. Array<String>).
Thus the error makes sense: you’re trying to a assign an array of strings in a place where only a String is expected.
Just remove the square brackets.

What is the return type of JSONSerialization.jsonObject(with:options:) in Swift?

I made the json parser in swift language.
But, many of people are using like below.
let jsonParsed = JSONSerialization.jsonObject(with: data, options: [])
guard let jsonDict = jsonParsed as? Dictionary<String, AnyObject> else { return }
...
Then, I wonder the type of jsonParsed. The JSONSerialization.jsonObject(with:options:) function reference describes that the result type is just Any.
I know the type is Dictionary because JSON. The KEY is String type but, VALUE is AnyObject? How about Any?
I know the difference between AnyObject and Any. Any also includes value type, function type.
Number is also value type in swift: Int, Float, Double...
Is that impossible return type is value type?
Json can be array also. This is valid josn
[
"1",
"2",
"3"
]
You can use this site to verify [https://jsoneditoronline.org/][1].
When you give fragmentsallowed option in the function json result can be Int,Float or any other primitive data type.
So it is not possible to return type in compile time. At run time it is possible. But return type is decided at run time.

Chaining Optionals in Swift

Up until now, I've been unwrapping Optionals in Swift 2.1 like so:
#IBOutlet var commentTextView: UITextView!
if let comment = user["comment"] as? String {
commentTextView.text = comment
}
I never really thought about it, but I think the reason I was doing this was because I was worried that this statement would throw an error if user["comment"] returned something other than a String:
commentTextView.text = user["comment"] as? String
If user["comment"] isn't a String, will the variable on the left of the assignment operator be assigned and throw an error or will the assignment be skipped?
I guess user is in fact a dictionary [String: Any] and what you really do with if let comment = user["comment"] as? String { ... } is not just unwrapping the optional but a conditional type casting (and then unwrapping an optional result of it):
Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.
Now, to answer your question, if user["comment"] isn't a String then the result will be that commentTextView.text will be assigned nil value, which is bad because its type is String! (implicitly unwrapped optional) about which we hold a promise that it will never be nil. So, yes, there will be an error, an exception actually, but not at the place you would like it to be but at the moment your application will try to access its value assuming that it's not going to be nil.
What you should really do depends on a particular case.
E.g. if you can make user to be a dictionary like [String: String], then you would be able to truly get to unwrapping the optionals and use something like if let comment = user["comment"] { ... }. Or, if you are totally sure that the value for "comment" key will always be there, then you could just do let comment = user["comment"]!.
But if that's not possible then you have to stick with down-casting and the only other thing you can do is to use forced form of it, that is commentTextView.text = user["comment"] as! String. This one at least will produce an exception right at the spot in case if the value at "comment" happens to be not a String but something else.
nil will be assigned to the variable.
If the type of the variable is a non-optional, you'll get a runtime error.
However if user["comment"] is a String you'll get a compiler error about missing ! or ?.
First we need to know of what type the dictionary "user" is.
I assume it is of an unknown type like [String: AnyObject], otherwise why would you try to unwrap it as an String. Let us write a short test to see what happens:
let dict: [String: AnyObject] = ["SomeKey" : 1]
if let y = dict["SomeKey"] as? String {
print(y)
}
You can see clearly that the value of "SomeKey" is an Integer. Trying to unwrap it as an String triggers no error, the "if" statement is just skipped. If an assignment actually happened is hard to prove (maybe by looking at the assembler code) because the variable "y" simply does not exist after the if statement. I assume it will not be created at all.
If the type of the dictionary is known as [String: String] you can omit the try to unwrap it as a String because it's always clear that the type is String.
let dict2: [String: String] = ["SomeKey" : "SomeValue"]
if let y = dict2["WrongKey"] {
// In this case print(y) will not be called because the subscript operator of the dictionary returns nil
print(y)
}
// In this case print(y) will be called because the key is correct
if let y = dict2["SomeKey"] {
print(y)
}

Swift: Checking type of a dictionary value

I would like to check the type of a dictionary value, and I use the following test code:
let swiftDict = [1: "one", 2: "two"]
if swiftDict[1] is NSString {
println("it's an nsstring")
}
I got Compiler warning: "Cast from 'String?' to unrelated NSString always fails.
Modified the code to the following:
if let str = swiftDict[1] as? NSString? {
println("it's an nsstring")
}
Now I get Compiler warning: Conditional cast from String? to NSString? always succeeds.
How do I fix the above warning? What's the right way to check for a variable type?
The reason for the check is to find out whether String or NSString is stored.
If you have a dictionary with mixed types (i.e. value:AnyObject) you could iterate through them and use is to check what type a value in the dictionary is, e.g.
let swiftDict:[Int:AnyObject] = [1: "one", 2: "two", 3: 15, 4: true]
for (key, value) in swiftDict
{
if (value is String)
{
println("\(key): \(value) is String")
}
}
Why do you want to test your value type at all, as Swift is already type-safe?
Contrary to Objective-C, arrays and dictionaries in Swift are typed. So your swiftDict variable is of type Dictionary<Int, String> (or [Int:String] for short) and thus the values of this dictionary will always be Strings, by definition.
For example, if you had tried to write this:
let swiftDict = [1: "one", 2: "two", 3: 7]
// error: '_' is not convertible to 'StringLiteralConvertible'
Then the compiler would error, telling you that 7 is not of type String nor convertible to a String, so you can't add it to swiftDict, which is inferred to be of type [Int:String]
This means that, by definition, swiftDict[1] will always be a String (because swiftDict is a [Int:String])… or nil if there is no key 1 in that dict.
--> swiftDict[1] is guaranteed to be of type String?. So you don't even need to test the type cast. You just need to test if it's nil or not.
if let value = swiftDict[1] {
println("swiftDict has a string value of \(value) for key 1")
} else {
println("swiftDict does not have the key 1")
}
[EDIT] Also note that String and NSString are toll-free bridged, which means that they are totally interchangeable. So you don't even need to test or cast if your String is an NSString, as it will always be possible to cast from String to NSString and vice-versa (it will always be possible to interpret your string either as String or NSString).
guard could be used
guard let key = swiftDict[1] else {
println("swiftDict does not have the key 1")
return;
}
It wont matter if you cast it to string or NSString as its just a character sequence in the dictionary. Both will work. If it's a string in the dictionary, it's a String or an NSString, depending what you set it as.
if let str = swiftDict[1] as? String
{
println("it's a string")
}

(String: AnyObject) does not have a member named 'subscript'

I've been through similar questions but still do not understand why my code is throwing an error.
var dict = [String:AnyObject]()
dict["participants"] = ["foo", "bar"]
dict["participants"][0] = "baz"
The error is on line 3: (String: AnyObject) does not have a member named 'subscript'
I'm setting the participants key to an array and then trying to update the first element of it without any luck. The code above is shortened for example purposes, but I am using [String:AnyObject] because it is not only arrays that are stored in the dictionary.
It's probably something really trivial but I am still new to Swift. Thanks for any help in advance!
The error message tells you exactly what the problem is. Your dictionary values are typed as AnyObject. I know you know that this value is a string array, but Swift does not know that; it knows only what you told it, that this is an AnyObject. But AnyObject can't be subscripted (in fact, you can't do much with it at all). If you want to use subscripting, you need to tell Swift that this is not an AnyObject but rather an Array of some sort (here, an array of String).
There is then a second problem, which is that dict["participants"] is not in fact even an AnyObject - it is an Optional wrapping an AnyObject. So you will have to unwrap it and cast it in order to subscript it.
There is then a third problem, which is that you can't mutate an array value inside a dictionary in place. You will have to extract the value, mutate it, and then replace it.
So, your entire code will look like this:
var dict = [String:AnyObject]()
dict["participants"] = ["foo", "bar"]
var arr = dict["participants"] as [String] // unwrap the optional and cast
arr[0] = "baz" // now we can subscript!
dict["participants"] = arr // but now we have to write back into the dict
Extra for experts: If you want to be disgustingly cool and Swifty (and who doesn't??), you can perform the mutation and the assignment in one move by using a define-and-call anonymous function, like this:
var dict = [String:AnyObject]()
dict["participants"] = ["foo", "bar"]
dict["participants"] = {
var arr = dict["participants"] as [String]
arr[0] = "baz"
return arr
}()