Cannot downcast object of type Any to Int when accessing from dictionary - swift

I have a Gfycat struct that represents the data I want to store after making a network call to the Gfycat API.
typealias JSONDictionary = [String: Any]
struct Gfycat {
let id: String
let number: Int
}
In an extension to the Gfycat struct, I wrote a failable initializer that takes a dictionary of type [String: Any] as its argument. This dictionary is then used to assign values to the struct's properties. This is the original init method I wrote:
extension Gfycat {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["gfyId"] as? String,
let number = dictionary["gfyNumber"] as? Int { return nil }
self.id = id
self.number = number
}
}
The problem is that when accessing a value from the dictionary, I cannot downcast the value from Any to Int. I must first downcast Any to String, then convert that string to Int. Is this a bug or rather a feature of Swift that I don't understand?
This was my solution:
extension Gfycat {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["gfyId"] as? String,
let uncastedNumber = dictionary["gfyNumber"] as? String,
let number = Int(uncastedNumber) else { return nil }
self.id = id
self.number = number
}
}

I must first downcast Any to String, then convert that string to Int. Is this a bug or rather a feature of Swift that I don't understand?
It's neither a bug nor a feature of Swift. It's a fact about the dictionary you're working with. This thing is a String, not an Int. So you cannot cast it to an Int.

Related

Is it possible to write an extension for a specific swift Dictionary ... [String : AnyObject]? [duplicate]

I am trying to create a dictionary extension where Dictionary is of the type <String, AnyObject>.
Was looking in many places and trying different approaches, but none of them seemed to work. This was one of them:
extension Dictionary where <String, AnyObject>{
var jsonString:String {
return ""
}
}
Another method that didn't actually work for some reason:
extension Dictionary where Key:Hashable, Value:AnyObject {
var jsonString:String {
do {
let stringData = try NSJSONSerialization.dataWithJSONObject(self, options: NSJSONWritingOptions.PrettyPrinted)
if let string = String(data: stringData, encoding: NSUTF8StringEncoding){
return string
}
}catch _ {
}
return ""
}
}
Got: Argument type 'Dictionary' does not conform to expected type of 'AnyObject'
>=3.1
From 3.1, we can do concrete extensions, ie:
extension Dictionary where Key == String {}
<3.1
We can not conform concrete types w/ concrete generics, ie:
extension Dictionary where Key == String
However, because Dictionary conforms to sequence and we can conform protocol types w/ concrete generics, we could do:
extension Sequence where Iterator.Element == (key: String, value: AnyObject) {
func doStuff() {...
Otherwise, we can constrain our key to a protocol that string conforms to like this:
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
var jsonString: String {
return ""
}
}
As per your updated answer. Json serialization needs an object, Swift Dictionaries are structs. You need to convert to an NSDictionary You must specify Key to conform to NSObject to properly convert to an NSDictionary.
Small note: Dictionaries already type constrain Key to be Hashable, so your original constraint wasn't adding anything.
extension Dictionary where Key: NSObject, Value:AnyObject {
var jsonString:String {
do {
let stringData = try NSJSONSerialization.dataWithJSONObject(self as NSDictionary, options: NSJSONWritingOptions.PrettyPrinted)
if let string = String(data: stringData, encoding: NSUTF8StringEncoding){
return string
}
}catch _ {
}
return ""
}
}
Note, that the dictionaries must conform to this type to access the extension.
let dict = ["k" : "v"]
Will become type [String : String], so you must be explicit in declaring:
let dict: [NSObject : AnyObject] = ["k" : "v"]
Swift 3 Approach
extension Dictionary where Key: ExpressibleByStringLiteral, Value: AnyObject
As StringLiteralConvertible is now deprecated and replaced by ExpressibleByStringLiteral
Update for Swift 3
Here is my example using ExpressibleByStringLiteral for Key and Any for Value.
extension Dictionary where Key: StringLiteralConvertible, Value: Any {
var jsonString: String? {
if let dict = (self as AnyObject) as? Dictionary<String, AnyObject> {
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: JSONSerialization.WritingOptions(rawValue: UInt.allZeros))
if let string = String(data: data, encoding: String.Encoding.utf8) {
return string
}
} catch {
print(error)
}
}
return nil
}
}
and then I use it like this:
let dict: Dictionary<String, AnyObject> = [...]
let jsonString = dict.jsonString
You can convert self to AnyObject or NSObject, both works, then you do unwrap as Dictionary or any other specific type.
Adding to the answer provided by #Logan, for those looking to add custom properties to the string-subscripted Dictionary, that is possible as well (was looking to do this when I came across this SO question):
extension Dictionary where Key: StringLiteralConvertible {
var somePropertyThatIsAColor:UIColor? {
get {
return self["someKey"] as? UIColor
}
set {
// Have to cast as optional Value
self["someKey"] = (newValue as? Value)
}
}
Anyone using [String:Any] instead of Dictionary can use below extension
extension Dictionary where Key == String, Value == Any {
mutating func append(anotherDict:[String:Any]) {
for (key, value) in anotherDict {
self.updateValue(value, forKey: key)
}
}
}
So, Dictionary is:
public struct Dictionary<Key : Hashable, Value> : CollectionType, DictionaryLiteralConvertible {..}
How about a protocol extension? :D
extension CollectionType where Self: DictionaryLiteralConvertible, Self.Key == String, Self.Value == AnyObject, Generator.Element == (Self.Key, Self.Value) {
...
}
I was not able to make any of the offered solutions work in Swift 3, but by taking advantage of bridging between Dictionary and NSDictionary I could make this work:
extension NSDictionary {
var jsonString:String {
do {
let stringData = try JSONSerialization.data(withJSONObject: self, options: .prettyPrinted)
if let string = String(data: stringData, encoding: .utf8) {
return string
}
}catch _ {
}
return ""
}
}

Swift: Cannot convert value of type '[String : DayData]' to expected argument type 'AnyObject?'

I'm having trouble saving to CoreData when trying to save a new struct I created.
var allInformationByDate = [
"2016-08-13": DayData(sales: 0, doorsKnocked: 0, milesWalked: 0.00, hoursWorked: 0.00)
]
struct DayData {
let sales: Int
let doorsKnocked: Int
let milesWalked: Double
let hoursWorked: Double
}
I'm getting the error:
Cannot convert value of type '[String : DayData]' to expected argument type 'AnyObject?'
on this chunk of code, specifically on "allInformationByDate"...
var allInfoByDateDefault = NSUserDefaults.standardUserDefaults()
allInfoByDateDefault.setValue(allInformationByDate, forKey:"allInfoByDateRecord")
allInfoByDateDefault.synchronize()
Does anybody know how to fix this syntax? I've tried changing to
allInformationByDate as! AnyObject
but that just makes the app crash.
The issue is that you cannot save custom objects in NSUserDefaults
But since all properties of DayData are property list compliant you could write an extension of NSUserDefaults to convert DayData to a dictionary and vice versa.
This extension can write a single DayData object as well as a dictionary [String:DayData].
extension NSUserDefaults {
// read a write a single 'DayData' object
func dayDataForKey(key: String) -> DayData? {
guard let data = self.objectForKey(key) as? [String:AnyObject] else { return nil }
return DayData(sales: data["sales"] as! Int, doorsKnocked: data["doorsKnocked"] as! Int, milesWalked: data["milesWalked"] as! Double, hoursWorked: data["hoursWorked"] as! Double)
}
func setDayData(dayData : DayData, forKey key: String) {
let propertyListRepresentation = ["sales": dayData.sales, "doorsKnocked" : dayData.doorsKnocked, "milesWalked": dayData.milesWalked, "hoursWorked": dayData.hoursWorked]
self.setObject(propertyListRepresentation, forKey: key)
}
// read a write a dictionary ('[String:DayData]') object
func dayDataDictionaryForKey(key: String) -> [String: DayData]? {
guard let dayData = self.objectForKey(key) as? [String : [String: AnyObject]] else { return nil }
var result = [String: DayData]()
for (key, value) in dayData {
result[key] = DayData(sales: value["sales"] as! Int, doorsKnocked: value["doorsKnocked"] as! Int, milesWalked: value["milesWalked"] as! Double, hoursWorked: value["hoursWorked"] as! Double)
}
return result
}
func setDayDataDictionary(dayData : [String: DayData], forKey key: String) {
var result = [String : [String: AnyObject]]()
for (key, value) in dayData {
result[key] = ["sales": value.sales, "doorsKnocked" : value.doorsKnocked, "milesWalked": value.milesWalked, "hoursWorked": value.hoursWorked]
}
self.setObject(result, forKey: key)
}
}
Now you can easily write a dictionary to user defaults:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setDayDataDictionary(allInformationByDate, forKey: "allInfoByDateRecord")
or read it
let defaults = NSUserDefaults.standardUserDefaults()
if let infoByDateDefault = defaults.dayDataDictionaryForKey("allInfoByDateRecord") {
allInformationByDate = infoByDateDefault
}
You shouldn't use the setValue method. To set a dictionary, call the setObject method.
Documentation:
Sets the value of the specified default key in the standard application domain.
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects...
Also, it's very inconvenient to use Swift structs with Objective-C APIs. You need to make your struct conform to NSCoding and convert it to NSData before storing it to NSUserDefaults. It's basically a whole lot of mess.
You can search for tutorials that show you how to conform to NSCoding, but I don't really recommend using it.
NSUserDefaults is usually used to save simple data structures, like integers, floats, bools. This is because, after all, it is designed for you to save user preferences and related stuff.
Seeing you have a struct like this, I recommend you to use Core Data instead of NSUserDefaults. You can create a data model and generate an NSManagedObject subclass based on the struct:
class DayData: NSManagedObject {
#NSManaged var sales: NSNumber?
#NSManaged var doorsKnocked: NSNumber?
#NSManaged var milesWalked: NSNumber?
#NSManaged var hoursWorked: NSNumber?
}
Trust me, Core Data is much more convenient in these kinds of circumstances.
For details, take a look at this tutorial.

Assign Value of NSNumber to AnyObject

I have a segment of code that gets info from an API, and I need to add it to a Dictionary. The code is below:
typealias JSONdic = [String: AnyObject]
var weatherData: AnyObject = StorageManager.getValue(StorageManager.StorageKeys.WeatherData)!
let json: AnyObject = ["Any": "Object"]
if let json = json as? JSONdic, history = json["history"] as? JSONdic, tempi = history["tempi"] as? Int, hum = history["hum"] as? String, precip = history["precipi"] as? String{
println("Temperature:\(tempi) Humidity:\(hum) Precipitation:\(precip)")
weatherData = [NSDate: AnyObject]()
let temp = tempi as NSNumber
weatherData[(The Current Date)] = temp
}
I want to first add "temp" to the weatherData Dictionary, but even after casting it to NSNumber, I am told that an NSNumber value cannot be assigned to the AnyObject?! type. Can anyone help me fix this?
Your weatherData variable is of type AnyObject. Despite the fact that you later assign it a value of type [NSDate: AnyObject], the variable itself is still considered by the compiler to be AnyObject. You then hit problems because you try to subscript it, assigning an NSNumber, which is obviously not possible on AnyObject.
Your declaration of weatherData should ensure it is the type you intend. If you are sure that your StorageManager will return you the appropriate dictionary type for the weather data key, you can force downcast it to the correct type:
var weatherData = StorageManager.getValue(StorageManager.StorageKeys.WeatherData) as! [NSDate: NSObject]

Convenience initializer with non-optional property

An object of mine has an integer ID. Since this is a required property I am not defining it as an optional and I am requiring it in the designated initializer:
class Thing {
var uniqueID: Int
var name: String?
init (uniqueID: Int) {
self.uniqueID = uniqueID
}
}
Since I am creating one of these from some JSON, the usage is along the lines of:
if let uniqueID = dictionary["id"] as? Int {
let thing = Thing(uniqueID: unique)
}
Next, I would like to be able to add a convenience initializer to the Thing class that accepts the dictionary object and sets the properties accordingly. This includes the required uniqueID and some other optional properties. My best effort so far is:
convenience init (dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
//set other values here?
}
//or here?
}
But of course this isn't sufficient since the designated initializer isn't called on all paths of the conditional.
How should I be handling this scenario? Is it even possible? Or should I accept that uniqueID must be an optional?
You have a couple of options with this one. One is a failable initialisers:
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: -1)
return nil
}
}
Technically this can be tweaked a bit (mainly depending on your preference/version of swift), but my person preference is something as follows:
class func fromDictionary(dictionary: [String: AnyObject]) -> Thing? {
if let uniqueID = dictionary["id"] as? Int {
return self.init(uniqueID: uniqueID)
}
return nil
}
All together, as a playground:
class Thing {
var uniqueID: Int
var name: String?
init(uniqueID: Int) {
self.uniqueID = uniqueID
}
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: -1)
return nil
}
}
class func fromDictionary(dictionary: [String: AnyObject]) -> Thing? {
if let uniqueID = dictionary["id"] as? Int {
return self.init(uniqueID: uniqueID)
}
return nil
}
}
let firstThing = Thing(uniqueID: 1)
let secondThing = Thing(dictionary: ["id": 2])
let thirdThing = Thing(dictionary: ["not_id": 3])
let forthThing = Thing.fromDictionary(["id": 4])
let fithThing = Thing.fromDictionary(["not_id": 4])
The best solution is probably to use a failable initializer, which will either return an instantiated object or nil.
Because Swift objects cannot be partially constructed and convenience initializers must call a non-convenience initializer, we must still do something in the failure case.
The result will look something like this:
convenience init?(dictionary: [String: AnyObject]) {
if let uniqueID = dictionary["id"] as? Int {
self.init(uniqueID: uniqueID)
} else {
self.init(uniqueID: 0)
return nil
}
}
Generally speaking, our non-convenience initializer(s) should be one that accepts all arguments, and convenience initializers should be methods which don't require some of the arguments.
For example, I might make my default initializer look like this:
init(uniqueID: Int, name: String? = nil) {
self.uniqueID = uniqueID
self.name = name
}
This allows us to call the constructor in several different ways:
let thing1 = Thing(1)
let thing2 = Thing(2, nil)
let thing3 = Thing(3, "foo")
let thing4 = Thing(4, myUnwrappedStringVar)
let thing5 = Thing(5, myWrappedStringOptional)
And that already covers a lot of use cases for us.
So, let's add another convenience initializer that accepts an optional Int.
convenience init?(uniqueID: Int? = nil, name: String? = nil) {
if let id = uniqueID {
self.init(uniqueID: id, name: name)
} else {
self.init(uniqueID: 0)
return nil
}
}
Now we can take an Int? for our uniqueID argument and just fail when it's nil.
So, one more to accept the dictionary.
convenience init?(dictionary: [String: AnyObject]) {
let uniqueID = dictionary["id"] as? Int
let name = dictionary["name"] as? String
self.init(uniqueID: uniqueID, name: name)
}
We still have the slightly weird initialize then return nil pattern in our first convenience constructor, but everything else we build on top of this can simply call that convenience initializer and doesn't require the weird pattern.
In the initializer that takes the dictionary, if there's no id key, or if it's something that's not an Int, then the let uniqueID will be nil, so when we call the other constructor, it will call the one that accepts an Int?, be passed nil, return nil, and therefore the one we called will return nil.

Swift AnyObject is not convertible to String/Int

I want to parse a JSON to object, but I have no idea how to cast AnyObject to String or Int since I'm getting:
0x106bf1d07: leaq 0x33130(%rip), %rax ; "Swift dynamic cast failure"
When using for example:
self.id = reminderJSON["id"] as Int
I have ResponseParser class and inside of it (responseReminders is an Array of AnyObjects, from AFNetworking responseObject):
for reminder in responseReminders {
let newReminder = Reminder(reminderJSON: reminder)
...
}
Then in Reminder class I'm initialising it like this (reminder as AnyObject, but is Dictionary(String, AnyObject)):
var id: Int
var receiver: String
init(reminderJSON: AnyObject) {
self.id = reminderJSON["id"] as Int
self.receiver = reminderJSON["send_reminder_to"] as String
}
println(reminderJSON["id"]) result is: Optional(3065522)
How can I downcast AnyObject to String or Int in case like this?
//EDIT
After some tries I come with this solution:
if let id: AnyObject = reminderJSON["id"] {
self.id = Int(id as NSNumber)
}
for Int and
if let tempReceiver: AnyObject = reminderJSON["send_reminder_to"] {
self.id = "\(tempReceiver)"
}
for string
In Swift, String and Int are not objects. This is why you are getting the error message. You need to cast to NSString and NSNumber which are objects. Once you have these, they are assignable to variables of the type String and Int.
I recommend the following syntax:
if let id = reminderJSON["id"] as? NSNumber {
// If we get here, we know "id" exists in the dictionary, and we know that we
// got the type right.
self.id = id
}
if let receiver = reminderJSON["send_reminder_to"] as? NSString {
// If we get here, we know "send_reminder_to" exists in the dictionary, and we
// know we got the type right.
self.receiver = receiver
}
reminderJSON["id"] gives you an AnyObject?, so you cannot cast it to Int You have to unwrap it first.
Do
self.id = reminderJSON["id"]! as Int
if you're sure that id will be present in the JSON.
if id: AnyObject = reminderJSON["id"] {
self.id = id as Int
}
otherwise
Now you just need to import Foundation. Swift will convert value type(String,int) into object types(NSString,NSNumber).Since AnyObject works with all objects now compiler will not complaint.
This is actually pretty simple, the value can be extracted, casted, and unwrapped in one line: if let s = d["2"] as? String, as in:
var d:[String:AnyObject] = [String:AnyObject]()
d["s"] = NSString(string: "string")
if let s = d["s"] as? String {
println("Converted NSString to native Swift type")
}