Different data types in nested dictionaries

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?

>Solution :

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

Leave a Reply