FYI: New to Swift, mainly coming from the world of Python.
I'm trying to understand dictionaries in Swift. As dictionaries are type safe in Swift I wonder why I can have nested dictionaries with different data types. I've been experimenting in XCode Playground. Example follows:
let dict = ["color": "green"]
works fine as it is [String: String]
data type.
I also kind of understand the following one (is it [String: [String: String]]
?):
let dict2 = [
"France": [
"capital": "Paris",
"anthem": "La Marseillaise"],
"Germany": [
"capital": "Berlin",
"anthem": "Das Lied der Deutschen"]
]
However I don't understand the following one. Why can the nested dictionary contain different data types (e.g. strings and integers)?
let dict3 = [
"France": [
"capital": "Paris",
"anthem": "La Marseillaise",
"population": 67_390_000],
"Germany": [
"capital": "Berlin",
"anthem": "Das Lied der Deutschen",
"population": 83_240_000]
]
Then I try to read the values and I can only read "the first level", i.e. dict2["France"]
, but not e.g. dict2["France"]["anthem"]
. How come I can't read from the nested dictionary? Am I missing something fundamental here?
CodePudding user response:
Am I missing something fundamental here?
You are.
A dictionary containing multiple heterogenous values is [String:Any]
. The compiler cannot infer the type, it suggests to annotate it
❗️Heterogeneous collection literal could only be inferred to '[String : Any]'; add explicit type annotation if this is intentional.
By the way there are some syntax mistakes in your code.
let dict : [String:[String:Any]] = [
"France": [
"capital": "Paris",
"anthem": "La Marseillaise",
"population": 67_390_000],
"Germany": [
"capital": "Berlin",
"anthem": "Das Lied der Deutschen",
"population": 83_240_000]
]
To read the value again the compiler doesn't know the static type represented by Any
, you have to downcast the type.
This is the safe way to get the anthem, as dictionary keys can be missing an optional is returned which must be (safely) unwrapped
if let france = dict["France"], // no cast is needed because the compiler knows it's a dictionary
let marseillaise = france["anthem"] as? String {
print(marseillaise)
}
The type safer swiftier way is a custom struct
struct Country {
let capital, anthem: String
let population: Int
}
let dict = [
"France": Country(capital: "Paris", anthem: "La Marseillaise", population: 67_390_000),
"Germany": Country(capital: "Berlin", anthem: "Das Lied der Deutschen", population: 83_240_000)
]
Now the compiler can infer the type [String:Country]
, however you still have to safely unwrap the optional, but no type cast/annotation is required at all.
if let france = dict["France"] {
print(france.anthem)
}
CodePudding user response:
When you make dict3
as
let dict3:[String:Any] = [
"France": [ //key is a String
"capital": "Paris",
"anthem": "La Marseillaise", // it's values are [String:Any] as 67_390_000 is integer and La Marseillaise & Paris are strings
"population": 67_390_000
],
"Germany": [ //key is a String
"capital": "Berlin",
"anthem": "Das Lied der Deutschen", // it's values are [String:Any] as 83_240_000 is integer and 83_240_000 & Berlin are strings
"population": 83_240_000
]
]
it's inferred type is [String:Any]/[String:[String:Any]]
so you need a cast
if let france = dict3["France"] as? [String:Any] , let anthem = france["anthem"] as? String {
print(anthem)
}