Reputation: 6095
I'm trying to write a function to deep merge two dictionaries. By this I mean that when the values for a key in both dictionaries are being merged and those values are both dictionaries, they will be merged instead of one being replaced by the other.
Here's what I have:
extension Dictionary {
public func deepMerged(with other: [Key: Value]) -> [Key: Value] {
var result: [Key: Value] = self
for (key, value) in other {
if let value = value as? [Key: Value], let existing = result[key] as? [Key: Value] {
result[key] = existing.deepMerged(with: value)
} else {
result[key] = value
}
}
return result
}
}
But unfortunately it doesn't compile. I get the error
Cannot convert value of type '[Key : Value]' to expected argument type [_ : _]' on the use of value in the recursive call to `deepMerged(with:).
I'm able to work around this by scoping the extension:
extension Dictionary where Key == String, Value == Any {
...
}
This works for my particular use case at this time, but I don't understand why the more generic code doesn't work.
Upvotes: 1
Views: 273
Reputation: 7634
Note that the error is on the assignment to result[key]
, not on the function call itself:
let merged: [Key:Value] = existing.deepMerged(with: value) //works fine
result[key] = merged //error
The compiler knows that:
result
is a [Key: Value]
other
is a [Key: Value]
key
is a Key
value
is a Value
merged
is a [Key:Value]
result
has a subscript(key: Key) -> Value? { get set }
(i.e. a writable subscript of type Value?
which accepts a Key
)But it does not know that [Key:Value]
is a Value
, so it does not know that merged
can be passed to the subscript of result
. Since value
is a Value
and value
is also [Key: Value]
, we know that [Key: Value]
must be a Value
, but the compiler does not know that.
A solution is to cast merged
to a Value
:
extension Dictionary {
public func deepMerged(with other: [Key: Value]) -> [Key: Value]
{
var result: [Key: Value] = self
for (key, value) in other {
if let value = value as? [Key: Value],
let existing = result[key] as? [Key: Value],
let merged = existing.deepMerged(with: value) as? Value
{
result[key] = merged
} else {
result[key] = value
}
}
return result
}
}
Upvotes: 3