1

I have three dictionaries, and I want to merge these. Please Help!

Dictionary 1:

"Abc": {
    "Def": {
        "Jkl": "xxx",
    }
}

Dictionary 2:

"Abc": {
    "Ghi": {
        "Mno": "yyy",
    }
}

Dictionary 3:

"Abc": {
    "Ghi": {
        "Pqr": "zzz"
    }
}

I want the resulting dictionary to group similar values together and it should look like this:

"Abc": {
    "Def: {
        "Jkl": "xxx"
    },
    "Ghi": {
        "Mno": "yyy",
        "Pqr": "zzz"
    }
}
Aleksey Potapov
  • 3,683
  • 5
  • 42
  • 65
Deepanshu
  • 29
  • 1
  • 8

3 Answers3

2

Use this recursive method to join two dictionaries

func deepMerge(a: [String: Any], b: [String: Any]) -> [String: Any] {
    var result = a
    for (key, value) in b {
        //Key matches
        if let aValue = result[key] {
            //Both values are dictionaries
            if let aValDict = aValue as? [String: Any], let bValDict = value as? [String: Any] {
                result[key] = deepMerge(a: aValDict, b: bValDict)
            } else {
                //One/both values aren't dictionaries
                print("Expected two dictionaries to merge")
            }
        } else {
            //Different keys
            return a.merging(b) { (_, new) in new }
        }
    }
    return result
}

Usage

let dict1 = ["Abc": ["Def": ["Jkl": "xxx"]]]
let dict2 = ["Abc": ["Ghi": ["Mno": "yyy"]]]
let dict3 = ["Abc": ["Ghi": ["Pqr": "zzz"]]]

var result = [String: Any]()
for dict in [dict1, dict2, dict3] {
    result = deepMerge(a: result, b: dict)
}
print(result)
//["Abc": ["Ghi": ["Pqr": "zzz", "Mno": "yyy"], "Def": ["Jkl": "xxx"]]]
iOSDev
  • 199
  • 11
  • Try `let a = ["Abc": ["Def": ["Jkl": "xxx"]]]; let b = ["Abc": ["Ghi": ["Mno": "yyy", "Pqr": "xxx"]]]; let c = ["Abc": ["Ghi": ["Pqr": "zzz"]]]` as input. You have the same issue here as I, heh – Aleksey Potapov Feb 28 '20 at 10:26
1

iOSDev solution is good and self-explaining recursive function. I would add here another way to handle dictionaries in using Swift method merging(_:uniquingKeysWith:)

Creates a dictionary by merging key-value pairs in a sequence into the dictionary, using a combining closure to determine the value for duplicate keys.

It is available in Swift since version 4.2

Your solution may look natively

func mergeStandard(_ one: [String: [String: [String: String]]],
                   _ two: [String: [String: [String: String]]]) -> [String: [String: [String: String]]] {
    return one.merging(two) {
        $0.merging($1) {
            $0.merging($1) { value1, value2 -> String in
                return value1 // which is logically wrong, as we should have both values, see the Further Steps section
            }
        }
    }
}

let a = ["Abc": ["Def": ["Jkl": "xxx"]]]
let b = ["Abc": ["Ghi": ["Mno": "yyy"]]]
let c = ["Abc": ["Ghi": ["Pqr": "zzz"]]]
let d = mergeStandard(a, b)
let e = mergeStandard(d, c)

print(e)
//["Abc": ["Def": ["Jkl": "xxx"], "Ghi": ["Mno": "yyy", "Pqr": "zzz"]]]

N.B.! when you try some different input, like

let a = ["Abc": ["Def": ["Jkl": "xxx"]]]
let b = ["Abc": ["Ghi": ["Mno": "yyy", "Pqr": "qqq"]]]
let c = ["Abc": ["Ghi": ["Pqr": "zzz"]]]

Because you have two "Pqr" keys. The code above will select the value "qqq" for the key "Pqr". The another answer - from iOSDev has the same issue. Please, see the solution for this case below.


Further Steps

I would recommend you to change your data structure to [String: [String: [String: [String]]]], so you will have the last value as Sequence of String. And then you could use the following:

func mergeUpgraded(_ one: [String: [String: [String: [String]]]],
                   _ two: [String: [String: [String: [String]]]]) -> [String: [String: [String: [String]]]] {
    return one.merging(two) {
        $0.merging($1) {
            $0.merging($1) { (arr1, arr2) -> [String] in
                var a = arr1
                var b = arr2
                a.append(contentsOf: b)
                return a
            }
        }
    }
}

let a = ["Abc": ["Def": ["Jkl": ["xxx"]]]]
let b = ["Abc": ["Ghi": ["Mno": "yyy", "Pqr": "qqq"]]]
let c = ["Abc": ["Ghi": ["Pqr": "zzz"]]]
let d = mergeUpgraded(a, b)
let e = mergeUpgraded(d, c)

print(e)
// ["Abc": ["Ghi": ["Mno": ["yyy"], "Pqr": ["qqq", "zzz"]], "Def": ["Jkl": ["xxx"]]]]
Community
  • 1
  • 1
Aleksey Potapov
  • 3,683
  • 5
  • 42
  • 65
  • Samples given in the question don't have duplicate values as yours. And the expected result doesn't contain any array – iOSDev Feb 28 '20 at 10:48
  • This is no more but an advice to the question asker regarding improving their input model to a flexible one. Of course, otherwise they could use the `mergeStandard` func. – Aleksey Potapov Feb 28 '20 at 10:54
0

Thank you everyone for help! This piece of code worked for me:

    private func deepMerge(_ d1: [String: Any], _ d2: [String: Any]) -> [String: Any] {
    var result = d1
    for (k2, v2) in d2 {
        if let v1 = result[k2] as? [String: Any], let v2 = v2 as? [String: Any] {
            result[k2] = deepMerge(v1, v2)
        } else {
            result[k2] = v2
        }
    }
    return result
}
Deepanshu
  • 29
  • 1
  • 8