How can I combine two Dictionary instances in Swift?
Asked Answered
O

12

145

How do I append one Dictionary to another Dictionary using Swift?

I am using the AlamoFire library to send JSON content to a REST server.

Dictionary 1

var dict1: [String: AnyObject] = [
    kFacebook: [
        kToken: token
    ]
]

Dictionary 2

var dict2: [String: AnyObject] = [
    kRequest: [
        kTargetUserId: userId
    ]
]

How do I combine the two dictionaries to make a new dictionary as shown below?

let parameters: [String: AnyObject] = [
    kFacebook: [
        kToken: token
    ],
    kRequest: [
        kTargetUserId: userId
    ]
]

I have tried dict1 += dict2, but I got a compile error:

Binary operator '+=' cannot be applied to two '[String : AnyObject]' operands

Osvaldooswal answered 4/11, 2014 at 5:43 Comment(3)
Possible duplicate of How do you add a Dictionary of items into another DictionaryVerein
developer.apple.com/documentation/swift/dictionary/…New
Hey @the-nomad, could you move your accepted answer to the one with more than double upvotes - please :-)?Domett
D
90
var d1 = ["a": "b"]
var d2 = ["c": "e"]

extension Dictionary {
    mutating func merge(dict: [Key: Value]){
        for (k, v) in dict {
            updateValue(v, forKey: k)
        }
    }
}

d1.merge(d2)

Refer to the awesome Dollar & Cent project https://github.com/ankurp/Cent/blob/master/Sources/Dictionary.swift

Docilla answered 4/11, 2014 at 6:0 Comment(7)
where can I put the extension to have this function available anywhere in my code? Sorry I'm a noob!Osvaldooswal
Put it in any file in your projectDocilla
What if the value is also a dictionary, how do you deep merge?Miscarriage
@RodrigoRuiz I don't think it makes any difference, as long as your original dictionary is also the same architecture, it won't make a difference (unless the original dictionary is of type [Key: Any])Challenging
The extension is not needed anymore since Dictionary got its own merge functions. See https://mcmap.net/q/158418/-how-can-i-combine-two-dictionary-instances-in-swift.Domett
what about making this the accepted answer @the-nomad stackoverflow.com/users/2917742/the-nomad ?Domett
I don't think this would work if d1 has no key/vals.Cute
D
279

I love this approach:

dicFrom.forEach { (key, value) in dicTo[key] = value }

Swift 4 and 5

With Swift 4 Apple introduces a better approach to merge two dictionaries:

let dictionary = ["a": 1, "b": 2]
let newKeyValues = ["a": 3, "b": 4]

let keepingCurrent = dictionary.merging(newKeyValues) { (current, _) in current }
// ["b": 2, "a": 1]

let replacingCurrent = dictionary.merging(newKeyValues) { (_, new) in new }
// ["b": 4, "a": 3]

You have 2 options here (as with most functions operating on containers):

  • merge mutates an existing dictionary
  • merging returns a new dictionary
Domett answered 25/4, 2017 at 15:36 Comment(3)
This is great. It allows selection of the entire dictionary. Is there a way to add custom per key logic?Kirbee
can you post a link to e.g. a github gist to illustrate what you want exactly (or explain :))?Domett
I second mfaani, I want to drop new key values that match a certain criteria. I guess filter could be used for the incoming dictionary to do some pruning...Clinical
D
90
var d1 = ["a": "b"]
var d2 = ["c": "e"]

extension Dictionary {
    mutating func merge(dict: [Key: Value]){
        for (k, v) in dict {
            updateValue(v, forKey: k)
        }
    }
}

d1.merge(d2)

Refer to the awesome Dollar & Cent project https://github.com/ankurp/Cent/blob/master/Sources/Dictionary.swift

Docilla answered 4/11, 2014 at 6:0 Comment(7)
where can I put the extension to have this function available anywhere in my code? Sorry I'm a noob!Osvaldooswal
Put it in any file in your projectDocilla
What if the value is also a dictionary, how do you deep merge?Miscarriage
@RodrigoRuiz I don't think it makes any difference, as long as your original dictionary is also the same architecture, it won't make a difference (unless the original dictionary is of type [Key: Any])Challenging
The extension is not needed anymore since Dictionary got its own merge functions. See https://mcmap.net/q/158418/-how-can-i-combine-two-dictionary-instances-in-swift.Domett
what about making this the accepted answer @the-nomad stackoverflow.com/users/2917742/the-nomad ?Domett
I don't think this would work if d1 has no key/vals.Cute
O
19

For Swift >= 2.2:
let parameters = dict1.reduce(dict2) { r, e in var r = r; r[e.0] = e.1; return r }

For Swift < 2.2:
let parameters = dict1.reduce(dict2) { (var r, e) in r[e.0] = e.1; return r }

Swift 4 has a new function: let parameters = dict1.reduce(into: dict2) { (r, e) in r[e.0] = e.1 }

It's really important to dig around the standard library: map, reduce, dropFirst, forEach etc. are staples of terse code. The functional bits are fun!

Orelie answered 31/1, 2016 at 20:7 Comment(1)
Swift 2.2 compiler throws a warning: "'var' parameters are deprecated and will be removed in Swift 3"Michalmichalak
E
16
extension Dictionary {
    static func +=(lhs: inout Self, rhs: Self) {
        lhs.merge(rhs) { _ , new in new }
    }
    static func +=<S: Sequence>(lhs: inout Self, rhs: S) where S.Element == (Key, Value) {
        lhs.merge(rhs) { _ , new in new }
    }
}

var dic = ["test1": 1]
dic += ["test2": 2]
dic  // ["test1": 1, "test2": 2]
dic += [("test2", 3),("test3", 4)]
dic  // ["test3": 4, "test1": 1, "test2": 3]
Elane answered 2/3, 2016 at 6:41 Comment(0)
D
9

SequenceType.forEach (implemented by Dictionary) provides an elegant solution to add a dictionary's elements to another dictionary.

dic1.forEach { dic2[$0] = $1 }

For example

func testMergeDictionaries() {
    let dic1 = [1:"foo"]
    var dic2 = [2:"bar"]

    dic1.forEach { dic2[$0] = $1 }

    XCTAssertEqual(dic2[1], "foo")
}
Digitalin answered 26/11, 2015 at 3:27 Comment(0)
A
5

Swift 4+ solution for merging 2 dictionaries:

let dict1 = ["a": 1, "b": 2]
let dict2 = ["a": 3, "c": 4]

//In case of duplicate keys, values of dict1 will replace dict2
 let result1 = dict1.merging(dict2) { (current, _) in current } //["a": 1, "b": 2, "c": 4]

//In case of duplicate keys, values  of dict2 will replace dict1
 let result2 = dict1.merging(dict2) { (_, new) in new } //["a": 3, "b": 2, "c": 4]
Appoggiatura answered 21/3, 2023 at 9:16 Comment(0)
L
4

My needs were different, I wanted to merge and not clobber.

merging:
    ["b": [1, 2], "s": Set([5, 6]), "a": 1, "d": ["x": 2]]
with
    ["b": [3, 4], "s": Set([6, 7]), "a": 2, "d": ["y": 4]]
yields:
    ["b": [1, 2, 3, 4], "s": Set([5, 6, 7]), "a": 2, "d": ["y": 4, "x": 2]]

I was hoping for a simpler solution, but this is what I ended up with. The challenge was in hopping from dynamic typing to static typing, and I used protocols to solve this.

Also worthy of note is that when you use the dictionary literal syntax, you actually get the foundation types, which do not pick up the protocol extensions. I aborted my efforts to support those as I couldn't find an easy to to validate the uniformity of the collection elements.

import UIKit


private protocol Mergable {
    func mergeWithSame<T>(right: T) -> T?
}



public extension Dictionary {

    /**
    Merge Dictionaries

    - Parameter left: Dictionary to update
    - Parameter right:  Source dictionary with values to be merged

    - Returns: Merged dictionay
    */


    func merge(right:Dictionary) -> Dictionary {
        var merged = self
        for (k, rv) in right {

            // case of existing left value
            if let lv = self[k] {

                if let lv = lv as? Mergable where lv.dynamicType == rv.dynamicType {
                    let m = lv.mergeWithSame(rv)
                    merged[k] = m
                }

                else if lv is Mergable {
                    assert(false, "Expected common type for matching keys!")
                }

                else if !(lv is Mergable), let _ = lv as? NSArray {
                    assert(false, "Dictionary literals use incompatible Foundation Types")
                }

                else if !(lv is Mergable), let _ = lv as? NSDictionary {
                    assert(false, "Dictionary literals use incompatible Foundation Types")
                }

                else {
                    merged[k] = rv
                }
            }

                // case of no existing value
            else {
                merged[k] = rv
            }
        }

        return merged
    }
}




extension Array: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Array {
            return (self + right) as? T
        }

        assert(false)
        return nil
    }
}


extension Dictionary: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Dictionary {
            return self.merge(right) as? T
        }

        assert(false)
        return nil
    }
}


extension Set: Mergable {

    func mergeWithSame<T>(right: T) -> T? {

        if let right = right as? Set {
            return self.union(right) as? T
        }

        assert(false)
        return nil
    }
}



var dsa12 = Dictionary<String, Any>()
dsa12["a"] = 1
dsa12["b"] = [1, 2]
dsa12["s"] = Set([5, 6])
dsa12["d"] = ["c":5, "x": 2]


var dsa34 = Dictionary<String, Any>()
dsa34["a"] = 2
dsa34["b"] = [3, 4]
dsa34["s"] = Set([6, 7])
dsa34["d"] = ["c":-5, "y": 4]


//let dsa2 = ["a": 1, "b":a34]
let mdsa3 = dsa12.merge(dsa34)
print("merging:\n\t\(dsa12)\nwith\n\t\(dsa34) \nyields: \n\t\(mdsa3)")
Leomaleon answered 22/4, 2016 at 23:46 Comment(0)
M
3

Try This Approach

    let dict1: [String: AnyObject] = ["kFacebook": ["kToken": "token"]]
    let dict2: [String: AnyObject] = ["kRequest": ["kTargetUserId": "userId"]]

    var combinedAttributes : NSMutableDictionary!

    combinedAttributes = NSMutableDictionary(dictionary: dict1)

    combinedAttributes.addEntriesFromDictionary(dict2)

    println(combinedAttributes)

It will Print Following :

{
kFacebook =     {
    kToken = token;
};
kRequest =     {
    kTargetUserId = userId;
};

}

Hope it helps !!

Medamedal answered 4/11, 2014 at 6:6 Comment(0)
M
3

You can use the below code to combine two dictionary instances in Swift:

extension Dictionary {
    func merge(dict: Dictionary<Key,Value>) -> Dictionary<Key,Value> {
        var mutableCopy = self
        for (key, value) in dict {
            // If both dictionaries have a value for same key, the value of the other dictionary is used.
            mutableCopy[key] = value
        }
        return mutableCopy
    }
}
Muskogean answered 16/4, 2019 at 11:46 Comment(1)
you should rename It to merging. merge would be the mutating version following the Swift naming convention. Note that there is already native implementations of both methods.Elane
M
1

Why not use reduce:

let combined = dict1.reduce(into: dict2, { partialResult, dict1 in
    partialResult.updateValue(dict1.value, forKey: dict1.key)
})

Complexity
O(n), where n is the length of the sequence.

Miffy answered 23/2, 2023 at 17:48 Comment(3)
merging works via the same way but has much simpler syntaxBower
I like this method. Using $0 and $1 makes it short and sweet.Fate
We could also add this code to a + operator like func + <Self>(a: Self, b: Self) -> Self {...}Fate
C
0

Swift 5+ Merging Two Dictionary with Common keys and Conditions on value

var dept1 : [String : String] = ["Production":"03/08/2024", "Quality":"02/14/2024"]
let dept2 : [String : String] = ["Development":"01/26/2024", "Production":"02/20/2024"]
    
dept1.merge(dept2){(firstValue, secondValue) in
     return firstValue.dateValue > firstValue.dateValue ? firstValue : secondValue
}


extension String {
     var dateValue: Date {
          let dateFormatter = DateFormatter()
          dateFormatter.dateFormat = "mm/dd/YYYY"
          return dateFormatter.date(from: self) ?? Date()
   }
}

Result is dept1

["Quality": "02/14/2024", "Development": "01/26/2024", "Production": "03/08/2024"]
Calcar answered 14/3 at 5:7 Comment(0)
G
-1

You are using let keyword to declare the dictionary so you can't make the changes to your dictionary as it is used to declare constant.

Change it to var keyword then it will work for you.

var dict1: [String: AnyObject] = [
            kFacebook: [
                kToken: token
            ]
        ]

var dict2: [String: AnyObject] = [
        kRequest: [
            kTargetUserId: userId
        ]
    ]

dict1 += dict2
Gallop answered 4/11, 2014 at 6:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.