Midoxx
Midoxx

Reputation: 67

Iterate through Dictionary over all levels

I have a data structure in a Dictionary which looks like this:

- device
   - type
   - isActive
   - Data
      - Manufacturer 
      - Build Date
      - Power
      ...
   - Example

Now if I create a For Loop it only shows me the Values of the First level which means

type, isActive, Data, Example


for (key, value) in value {

}

All below Data like Build Date or Power is Missing - how can I irritate through this levels also?

Upvotes: 1

Views: 384

Answers (3)

Duncan C
Duncan C

Reputation: 131491

As Tomas says, this can be solved with recursion. His "walk" function simply prints the values on separate lines, but with no keys, and not formatting. Here is code that logs nested dictionaries using the structure you outlined:

//Define the keys we use in our example
enum keys: String {
    case device
    case type
    case isActive
    case Data
    case Manufacturer
    case BuildDate
    case Power
    case Example
    }

//Create a sample dictionary using those keys and some random values
let dict = [keys.device.rawValue:
                [keys.type.rawValue: "appliance",
                 keys.isActive.rawValue: true,
                 keys.Data.rawValue:
                    [keys.Manufacturer.rawValue: "GE",
                     keys.BuildDate.rawValue: "12/23/96",
                     keys.Power.rawValue: 23,
                    ],
                 keys.Example.rawValue: "Foo"
                ]
]

//Create an extension of Dictionary for dictionaries with String Keys.
extension Dictionary where Key == String {
    
    //Define a function to recursively build a description of our dictionary
    private func descriptionOfDict(_ aDict: [String: Any], level: Int = 0) -> String {
        var output = ""
        var indent: String = ""
        if level > 0 {
            output += "\n"
            for _ in 1...level {
                indent += "   "
            }
        }
        for (key,value) in aDict {
            output += indent + key + ": "
            if let subDict = value as? [String: Any] {
                //If the value for this key is another dictionary, log that recursively
                output +=  descriptionOfDict(subDict, level: level+1)
            } else {
                if let aString = value as? String {
                    output += "\"" +  aString + "\"\n"
                }  else {
                    output += String(describing: value) + "\n"
                }
            }
        }
        return output
    }
    
    //Add a description property for dictionaries
    var description: String  {
        return descriptionOfDict(self)
    }
}


print(dict.description)

That outputs:

device: 
   isActive: true
   Data: 
      Manufacturer: "GE"
      Power: 23
      BuildDate: "12/23/96"
   Example: "Foo"
   type: "appliance"

Edit:

The above, defining a String property description, changes the output when you print a [String:Any] dictionary. If you don't want that, rename the property description to something else like dictDescription

Upvotes: 1

Tomas Jablonskis
Tomas Jablonskis

Reputation: 4376

You are dealing with a Recursion problem here.


You could walk the dictionary level by level:

func walk(dictionary: [String: Any]) {
    for (key, value) in dictionary {
        print("\(key): \(value)")
        if let value = value as? [String: Any] {
            walk(dictionary: value)
        }
    }
}

You can change [String: Any] type with your dictionary type (NSDictionary, etc.)

Upvotes: 1

in.disee
in.disee

Reputation: 1112

Assuming you have Dictionary with type [String:Any], function to flatten it will be something like:

    func flatten(_ obj:[String:Any]) -> [String:Any] {
        var result = [String:Any]()
        for (key, val) in obj {
            if let v = val as? [String:Any] {
                result = result.merging(flatten(v), uniquingKeysWith: {
                    $1
                })
            }
            //I also included initial value of internal dictionary
            /if you don't need initial value put next line in 'else'
            result[key] = val 
        }
        return result
    }

To use that:

    let d:[String:Any] = [
        "test1":1,
        "test2":2,
        "test3":[
            "test3.1":31,
            "test3.2":32
        ]
    ]
    let res = flatten(d)
    print(res)

["test2": 2, "test3.2": 32, "test3.1": 31, "test1": 1, "test3": ["test3.2": 32, "test3.1": 31]]

note: dictionaries are not sorted structures

Upvotes: 1

Related Questions