How to easily read AnyObject stored in dictionaries? - swift

I have a dictionary that stores strings and integers. This dictionary's type is [String:AnyObject].
var person: [String:AnyObject] = ["occupation": "teacher", "age": 1]
I read this dictionary by this way:
occupationLabel.text = person["occupation"] as! String
let newAge = person["age"] as! Int + 1
It's inconvenient. How can I use this dictionary by the following way?
occupationLabel.text = person["occupation"]
let newAge = person["age"] + 1
Thank you.

You can't. You threw away static typing of the dictionary values when you made this a [String:AnyObject]. What you are doing, casting each value to what you know it to be, is correct.
The real solution, of course, is to have a Person type with occupation and age properties!
struct Person {
var occupation:String
var age:Int
}
Now each property has an inherent type and you don't need to cast.

Related

How to initialize a struct with dictionaries in Swift

I want to initialize every time a struct with dictionaries. Later, I'm going to use its properties instead a dictionary's keys and values - it seems rather easier. However, when I try the code below, it tells me that "Return from initializer without initializing all stored properties" and "1. 'self.one' not initialized" and "2. 'self.two' not initialized". My question is how to initialize a struct from a dictionary, so that I have basically a struct with the contents of the dictionary? Or how to transform it into struct?
struct Blabla {
var one: String
var two: [Int]
init(three: [String: [Int]]) {
for i in three {
self.one = i.key
self.two = i.value
}
} ERROR! - Return from initializer without initializing all stored properties
}
struct Blabla {
var one: String
var two: [Int]
init(three: [String: [Int]]) {
one = ""
two = []
for i in three {
self.one = i.key
self.two = i.value
}
} ERROR! - Return from initializer without initializing all stored properties
}
for in clause may have zero runs, in which case struct properties will not be initialized. You have to provide default values (or emit fatalError if you really need to).
While I think your example is pure synthetical, there is no need to loop through array, you can set properties to its last entry.
The issues is that if three is an empty Dictionary, the instance properties one and two don't get initialised. Also, you are overwriting the properties in each iteration of the for loop and the compiler cannot guarantee that there will be any iterations of the loop in compile-time, hence the compiler error.
You could make the initialiser failable to account for this by checking that the dictionary actually contains at least one key-value pair and assigning that first key-value pair to your properties.
struct Blabla {
var one: String
var two: [Int]
init?(three: [String: [Int]]) {
guard let key = three.keys.first, let value = three[key] else { return nil }
one = key
two = value
}
}
However, you should rethink what it is that you are actually trying to achieve, since with your current setup you have a mismatch between your init input values and the properties of your struct.
This code should compile, but it feels unsafe to me to initialize a Struct in this way because:
It assume your dictionary has values in it.
Your stored properties will always have the last value you looped through.
In order to pull values out to satisfy the compiler you need to force unwrap them. (With Dávid Pásztor's guard-letting approach, this can be avoided)
struct Blabla {
var one: String
var two: [Int]
init(three: [String: [Int]]) {
self.one = three.keys.first!
self.two = three[three.keys.first!]!
}
}
let input = ["pizza": [1,2]]
let l = Blabla(three: input)
If I were you I would let the memberwise initializer that you get for free do its thing and provide either a specialized initializer to handle your case of taking a Dictionary as input or move that parsing to another function/class/etc....
The compiler error is clear: If the dictionary is empty the struct members are never initialized. But the code makes no sense anyway as each iteration of the dictionary overwrites the values.
Maybe you mean to map the dictionary to an array of the struct
struct Blabla {
let one: String
let two: [Int]
}
let three = ["A":[1,2], "B":[3,4]]
let blabla = three.map{Blabla(one: $0.key, two: $0.value)}
print(blabla) // [Blabla(one: "A", two: [1, 2]), Blabla(one: "B", two: [3, 4])]
struct blabla{
var a : string
var b : [int] = []
init(_ data: [string:[int]]){
// whatever you want to do
}
}

Failing cast from Any to Int

I have the following dictionary of type [String: Any] (this is what the log looks like):
["name": Cesare de Cal, "last_name": de Cal, "email": hi#cesare.io, "id": 1012058902268810, "first_name": Cesare]
I want to get the profile ID "id":
if let fbID = fbValues["id"] as? Int {
print("here should be the fb Id", fbID)
} else {
print("cast failed") // cast failed
}
but this cast fails. Why? I'm assuming "id" (1012058902268810) is a number, right?
rob already provided you with a possible solution. Just to answer your question about why the cast fails, it fails because of how type casting works in Swift:
From the documentation:
Type casting is a way to check the type of an instance, or to treat
that instance as a different superclass or subclass from somewhere
else in its own class hierarchy. (...)
A constant or variable of a certain class type may actually
refer to an instance of a subclass behind the scenes. Where you
believe this is the case, you can try to downcast to the subclass type
with a type cast operator (as? or as!). (...)
Casting does not actually modify the instance or change its values. The underlying instance remains the same; it is simply treated and accessed as an instance of the type to which it has been cast.
This will work:
let something: Any = 1
let number = something as! Int
This won't work:
let something: Any = "1"
let number = something as! Int
This won't work either because Int has no initializer for type Any:
let something: Any = "1"
let number = Int(something)
But this will work - first you cast to String and then you coerce to Int (and Int has an initializer that accepts String)
let something: Any = "1"
let string = something as! String
let number = Int(string)!
Edit to answer Cesare: You're right. I edited my answer to just provide more info about type casting since you already had your problem solved ;)
And these were just some examples for getting the cast/coercion point across. In a real scenario you shouldn't be forcing unwrapping any of this as John Montgomery pointed out.
If you don't know whether the id value is coming in as a String or an Int, you could try handling both:
switch fbValues["id"] {
case nil:
print("no id given")
case let string as String:
if let id = Int(string) { print("id = \(id)") }
else { print("non-numeric id \(string)") }
case let id as Int:
print("id = \(id)")
default:
print("what is this I don't even")
}

Accessing values in a dictionary containing AnyObject

I have some data I stored into a dictionary which is defined as:
let data = Dictionary<String, AnyObject>()
In this dictionary the value is always a string, but the value can be an array or integer or string. But when I try to access an item in a array in this dictionary, like:
let item = data["key"][0]
It gives me this error:
Cannot subscript value of type "AnyObject"
How should I access that item?
You need to tell the compiler that you're expecting an array:
if let array = data["key"] as? [Int] {
let item = array[0]
}
Without that, the compiler only knows that there MAY be an AnyObject in data["key"] (it might also be nil).

Swift: Casting collections, and creating custom convertible protocols

Consider this Person class, which simply implements StringLiteralConvertible and assigns the string literal to name:
class Person : StringLiteralConvertible {
var name : String?
typealias StringLiteralType = String
required init(stringLiteral value: StringLiteralType) {
println("stringLiteral \(value)")
name = value
}
typealias ExtendedGraphemeClusterLiteralType = String
required init(extendedGraphemeClusterLiteral value: ExtendedGraphemeClusterLiteralType) {
println("extendedGraphemeClusterLiteral \(value)")
name = value
}
typealias UnicodeScalarLiteralType = Character
required init(unicodeScalarLiteral value: UnicodeScalarLiteralType) {
println("unicodeScalarLiteral \(value)")
name = "\(value)"
}
}
This allows me to create a Person instance using a string:
let aaron : Person = "Aaron"
I can even cast an array of Persons from an array of strings:
let names = ["John", "Jane"] as [Person]
However this only works with string literals. If I use a string variable, it fails:
let aaronString = "Aaron"
let aaron : Person = aaronString
// Error: 'NSString' is not a subtype of 'Person'
Similarly, trying to cast an array of non-literal strings fails:
let nameStrings = ["John", "Jane"]
let people : [Person] = nameStrings
// Error: 'String' is not identical to 'Person'
I have three questions:
Is there another protocol I can implement to cast a non-literal string to a Person? I'd like to do this so I can cast entire collections to convert the objects.
If no to #1, is map + an initializer the best way to perform the conversion myself?
let nameStrings = ["John", "Jane"]
let people = nameStrings.map{Person(name: $0)}
If yes to #1, is there a similar approach I can use to specify an approach to convert two objects which are unrelated in hierarchy? That is, can I work around this error without an initializer?
let rikerPerson : Person = "Riker"
let rikerEmployee = rikerPerson as Employee
// Error: 'Person' is not convertible to 'Employee'
What you are describing as “casting” isn’t really casting (in the way that, say, s = “fred”; ns = s as NSString is, or that casts in C++ are).
let names = ["John", "Jane"] as [Person]
is just another a way of writing:
let names: [Person] = ["John", "Jane"]
that is, a way of telling Swift which of the many possible versions of StringLiteralConvertible to use (and not the one for String, which is the default).
Put it another way – your as is fulfilling a similar function to the as in this snippet that disambiguates two overloaded functions that differ only by return type:
func f() -> String { return "foo" }
func f() -> Int { return 42 }
let i = f() as Int // i will be 42
let s = f() as String // s will be “foo"
No “conversion” is going on here – the as is just being used to disambiguate which f Swift calls. It’s the same with which init(stringLiteral:) is chosen.
Definitely (but only if you put a space between map and the { } ;-).
If you’re concerned about the waste of converting it all to an array just to do some other thing with it, check out lazy(a).map
Nope. In the betas, there used to be a __conversion() -> T method you could implement to do “casts” like this on your own classes – or more importantly, allowed you to pass your Person class into a function that took an Employee argument and have it be converted implicitly. But that got disappeared. Generally that kind of implicit conversion is antithetical to Swift’s style, except in rare cases (Obj-C and C interop, and implicit wrapping in optionals, being the main ones). You have to write an init for Employee that takes a Person (or some class or protocol that Person conforms to), and then call it.

Convert JSON AnyObject to Int64

this is somewhat related to this question: How to properly store timestamp (ms since 1970)
Is there a way to typeCast a AnyObject to Int64? I am receiving a huge number via JSON this number arrives at my class as "AnyObject" - how can I cast it to Int64 - xcode just says its not possible.
JSON numbers are NSNumber, so you'll want to go through there.
import Foundation
var json:AnyObject = NSNumber(longLong: 1234567890123456789)
var num = json as? NSNumber
var result = num?.longLongValue
Note that result is Int64?, since you don't know that this conversion will be successful.
You can cast from a AnyObject to an Int with the as type cast operator, but to downcast into different numeric types you need to use the target type's initializer.
var o:AnyObject = 1
var n:Int = o as Int
var u:Int64 = Int64(n)
Try SwiftJSON which is a better way to deal with JSON data in Swift
let json = SwiftJSON.JSON(data: dataFromServer)
if let number = json["number"].longLong {
//do what you want
} else {
//print the error message if you like
println(json["number"])
}
As #Rob Napier's answer says, you are dealing with NSNumber. If you're sure you have a valid one, you can do this to get an Int64
(json["key"] as! NSNumber).longLongValue