Swift Dictionary access value through subscript throws Error - swift

Accessing a Swift Dictionary value through subscript syntax is gives error Ambiguous reference to member 'subscript'
Here is the code
class Model {
struct Keys {
static let type :String = "type" //RowType
static let details :String = "details"
}
var type :RowType = .None
var details :[Detail] = []
init(with dictionary:Dictionary<String, Any>) {
if let type = dictionary[Keys.type] as? String {
self.type = self.rowTypeFromString(type: type)
}
if let detailsObj = dictionary[Keys.details] as? Array { //Error : Ambiguous reference to member 'subscript'
}
}
}
if i remove the type casting as? Array at the end of optional-binding it compiles fine
I am expecting the value of details key to be an Array, I know that i can use [String,Any] instead of Dictionary<Key, Value>, What is causing the issue ?

Array doesn't work like NSArray, you explicitly need to tell what type your array is storing.
If you want to store Detail objects in it, the proper syntax is Array<Detail> or simply [Detail]
if let detailsObj = dictionary[Keys.details] as? Array<Detail> {
}

Issue is solved by explicitly specifying the Type of objects the array contains, In my case it was Array<[String:Any]>
if let detailsObj = dictionary[Keys.details] as? Array<[String:Any]> { //we should also specify what type is present inside Array
}
Credits: #Hamish, #Dávid Pásztor
Thanks!

Related

Append Values in NSSet To An Array of Type [String]

I am attempting to get the values from an NSSet in core data and append those values to an array of type String.
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
recipeStep.append(String(i))
}
}
}
entity.steps is the list of steps tied to a core data entity. This is an NSSet. I am trying to copy those values to an array of type String.
#State var recipeStep: [String]
When trying to do this in my for in loop, I receive the following error: No exact matches in call to initializer
If I remove the conversion of "I" to String, I receive the following error:
Cannot convert value of type NSSet.Element (aka Any) to expected argument type String
Any idea on how to get this to work?
NSSet is defined in Objective C, which didn't have generics. It's an untyped collection, so you don't statically know anything about its elements.
As you've noticed, your i variable isn't a String, it's an Any.
You're confusing type coercion ("casting") with type conversion. If i were a Double, you could call String(i) to invoke an initializer which takes a double, and processes into a String.
You tried something similar by calling String(i), where you're making the Swift compiler find an initializer on String with the signitiure init(_: Any).
There is no such initializer. And besides, that's not what you want. You don't want to create a new String from a different kind of value. You already have a string, it's just "hidden" behind an Any reference.
What you're looking for is to do a down-cast, from Any to String:
func addStepsToArray() {
if let steps = entity.steps {
for i in steps {
guard let step = i as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
recipeStep.append(i as String)
}
}
}
I'll warn you though, there are several issues/blemishes with this code:
You're using a for loop to do what is ultimately just a mapping operation
Your computation doesn't return its ouput, and instead indirectly achieves its goal through a side-effect on recipeStep
Your computation doesn't take a its input as a parameter, and instead indirectly achieves its goal through a side-effect on entity
i is conventionally expected to be an integer index of a for loop iterating over a sequence of numbers. Here it's an Any (a String at runtime)
Here's what I would suggest instead:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
guard let steps = entity.steps else { return [] }
return steps.map { step in
guard let stringStep = step as? String else {
fatalError("Decide what to do if the value isn't a String.")
}
return step
}
}
Then in the rest of your code (and your tests), you can write self.recipeSteps = getRecipeSteps(from: myEntity). Elegant!
If you're certain that these entity.steps values can only ever be strings, then you can boil this down to a single map with a force-cast:
func getRecipeSteps(from entity: MyEntityType) -> [String] {
entity.steps?.map { $0 as! String } ?? []
}
Just convert directly:
let set = Set(["1", "2", "3"])
let array = Array(set)
DDLog(set)//Set<String>)
DDLog(array)//[String]

Use of undeclared type 'valueMirror' when using Mirror

I am trying to map a struct to other class that have same properties. but it keep showing this error
Use of undeclared type 'valueMirror'
My code
extension Mapper {
func map<T:Object>(to type: T.Type){
let object = T()
let m = Mirror(reflecting: self)
for property in m.children {
guard let key = property.label else { continue }
let value = property.value
let valueMirror = Mirror(reflecting: value)
if valueMirror.displayStyle == .collection {
let array = value as! valueMirror.subjectType // <-- error
object.setValue(array.asRealMList, forKey: key)
} else {
object.setValue(value, forKey: key)
}
}
}
}
valueMirror.subjectType is not a type as far as the compiler is concerned. There must be a compile time type after as!.
Since the only place you are using array is in array.asRealMList, you probably just need to cast value to a type that has the property asRealMList. As you said in the comments, this is an extension on Array.
Luckily Array is covariant, so even without knowing which type of array it is, you'll be able to cast any array to [Any]:
let array = value as! [Any]
valueMirror.subjectType is of type Any.Type.
You probably want to cast value to Any.

Value of type _ cannot convert to specified type Set<> Swift

I'm trying to create a set of random exercises. I have made my struct Hashable and Equatable following the tutorial here https://medium.com/#JoyceMatos/hashable-protocols-in-swift-baf0cabeaebd and that is working fine so it's ready to be put in a Set<>.
When I use an Array to collect the workout exercises, as per below, it works fine. But when I switch to a Set<> I get an error "cannot convert value of type [_] to specified type 'Set'. What is it about 'Sets' that mean you can't map in the same way as an Array?
func generateWorkout() {
let allPossibleExercises = masterExerciseArray
let numberOfExercisesKey = Int(arc4random_uniform(4)+3)
//error triggers on the line below if I switch [WorkoutExercise]
//for Set<WorkoutExercise> (which conforms to Hashable/Equatable
let workoutSet : [WorkoutExercise] = (1...numberOfExercisesKey).map { _ in
let randomKey = Int(arc4random_uniform(UInt32(allPossibleExercises.count)))
return WorkoutExerciseGenerator( name: allPossibleExercises[randomKey].name,
maxReps: allPossibleExercises[randomKey].maxReps).generate()
}
print (workoutSet)
}
There is an answer here with a similar error message Cannot convert value of type '[_]' to specified type 'Array' but my array wouldn't be empty as in this example so I don't think this is the same root cause?
UPDATE : for anyone having the same problem, you can use Array but then simply convert the Array to a Set afterwards if the correct elements are Hashable/Equatable
If creating the array works create the array and then make the Set from the array. If all involved objects conform to Hashable this is supposed to work.
func generateWorkout() {
let allPossibleExercises = masterExerciseArray
let numberOfExercisesKey = Int(arc4random_uniform(4)+3)
let workoutArray : [WorkoutExercise] = (1...numberOfExercisesKey).map { _ in
let randomKey = Int(arc4random_uniform(UInt32(allPossibleExercises.count)))
return WorkoutExerciseGenerator( name: allPossibleExercises[randomKey].name,
maxReps: allPossibleExercises[randomKey].maxReps).generate()
}
let workoutSet = Set(workoutArray)
print (workoutSet)
}

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).