How can I merge two maps in go?
Asked Answered
C

6

141

I have a recursive function that creates objects representing file paths (the keys are paths and the values are info about the file). It's recursive as it's only meant to handle files, so if a directory is encountered, the function is recursively called on the directory.

All that being said, I'd like to do the equivalent of a set union on two maps (i.e. the "main" map updated with the values from the recursive call). Is there an idiomatic way to do this aside from iterating over one map and assigning each key, value in it to the same thing in the other map?

That is: given a,b are of type map [string] *SomeObject, and a and b are eventually populated, is there any way to update a with all the values in b?

Chanteuse answered 24/3, 2014 at 22:17 Comment(2)
Perhaps you can utilize an actual set container for this type of work: github.com/deckarep/golang-setClarindaclarine
Ralph's suggestion is good for sets. However, I would say that in your case it is not so much a union as it is a merge; a set should just be a collection of "keys", while you have two collections of key-value pairs, where one "set" should have priority over the other.Baron
B
221

There is no built in way, nor any method in the standard packages to do such a merge.

The idomatic way is to simply iterate:

for k, v := range b {
    a[k] = v
}
Baron answered 24/3, 2014 at 22:22 Comment(6)
To add to what ANisus answered: Maps are essentially hash tables. There likely isn't any way to compute the union of two maps faster than simply iterating over both maps.Felicita
You could probably use reflection to write a type-agnostic union function, but it would be slower.Attending
Shouldn't this code UNION the value of a[k] and v before assigning v to a[k]? What if a[k] and v are arrays, or maps ?Cupreous
He's looking to union the maps, not necessarily the values in the maps. If you want to do something like that, you just need to change a[k] = v to a[k] = a[k] + v or something like that.Hazelhazelnut
@Hazelhazelnut I think you're right. For the actual union, this can be used: a[k] = append(a[k], v...)Anthropopathy
The correct answer, and equally useful comments should be a guide for people. Go is not a language designed for manipulating large, generic datasets. It is very unwieldy for this purpose. Horses for courses, folks. Go is great at what it was designed for, it doesn't need to eat the world.Adlare
R
59

Updated answer

Since Go 1.21, you can simply use the new maps.Copy function:

package main

import (
    "fmt"
    "maps"
)

func main() {
    src := map[string]int{
        "one": 1,
        "two": 2,
    }
    dst := map[string]int{
        "two":   42,
        "three": 3,
    }
    maps.Copy(dst, src)
    fmt.Println("src:", src)
    fmt.Println("dst:", dst)
}

(Playground)

Output:

src: map[one:1 two:2]
dst: map[one:1 three:3 two:2]

Original answer

Since Go 1.18, you can simply use the Copy function from the golang.org/x/exp/maps package:

package main

import (
    "fmt"

    "golang.org/x/exp/maps"
)

func main() {
    src := map[string]int{
        "one": 1,
        "two": 2,
    }
    dst := map[string]int{
        "two":   42,
        "three": 3,
    }
    maps.Copy(dst, src)
    fmt.Println("src:", src)
    fmt.Println("dst:", dst)
}

(Playground)

Output:

src: map[one:1 two:2]
dst: map[one:1 three:3 two:2]

One caveat of this approach is that, in Go versions 1.18.x to 1.19.x, your map's key type must be concrete, i.e. not an interface type. For instance, the compiler won't allow you to pass values of type map[io.Reader]int to the Copy function:

package main

import (
    "fmt"
    "io"

    "golang.org/x/exp/maps"
)

func main() {
    var src, dst map[io.Reader]int
    maps.Copy(dst, src)
    fmt.Println("src:", src)
    fmt.Println("dst:", dst)
}

Compiler output:

go: finding module for package golang.org/x/exp/maps
go: downloading golang.org/x/exp v0.0.0-20220328175248-053ad81199eb
./prog.go:12:11: io.Reader does not implement comparable

Go build failed.

This limitation was lifted in Go 1.20 (playground).

Rinee answered 28/3, 2022 at 19:29 Comment(2)
maps.Copy function should have been a Variadic method, not sure its not same as append functionClassieclassification
@AnkitKumar Not sure a variadic variant would be that useful... or that easy to write; the source maps would all have to be of the type: func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src ...M2).Rinee
S
11

Starting at go 1.18, thanks to the release of the Generics feature, there are now generic functions that union maps!

You can use a package like https://github.com/samber/lo in order to do so. Note that the key can be of any "comparable" type, while the value can be of any type.

Example:

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    map1 := map[string]interface{}{"k1": "v1", "k2": 2}
    map2 := map[string]interface{}{"k2": "v2new", "k3": true}
    map1 = lo.Assign(map1, map2)
    fmt.Printf("%v", map1)
}

The result is:

map[k1:v1 k2:v2new k3:true]
Slurry answered 20/3, 2022 at 9:5 Comment(0)
J
7

If you have a couple of nested maps, left and right, then this function will recursively add the items from right into left. If the key is already in left then we recurse deeper into the structure and attempt only add keys to left (e.g. never replace them).


type m = map[string]interface{}

// Given two maps, recursively merge right into left, NEVER replacing any key that already exists in left
func mergeKeys(left, right m) m {
    for key, rightVal := range right {
        if leftVal, present := left[key]; present {
            //then we don't want to replace it - recurse
            left[key] = mergeKeys(leftVal.(m), rightVal.(m))
        } else {
            // key not in left so we can just shove it in
            left[key] = rightVal
        }
    }
    return left
}

NOTE: I do not handle the case in which the value is not itself a map[string]interface{}. So if you have left["x"] = 1 and right["x"] = 2 then the above code will panic when attempting leftVal.(m).

Juvenilia answered 26/2, 2020 at 18:25 Comment(1)
this code helped me! thanks. I would add actually you really don't need to return the map as the first argument serves as in input/output parameter, i mean, it get's modified during the execution.Roseannroseanna
E
4

Here is another option,

  • in case you are trying to limit the number of third-party dependencies such github.com/samber/lo, OR
  • you are not comfortable with the experimental nature of golang.org/x/exp (read the warning), OR
  • you would rather the convenience of an append()-like API instead of exp.Copy() from golang.org/x/exp (append accepts any number of lists, whereas Copy() accepts only 2).

However it requires Go 1.18+ as it uses go generics.

Save the following in one of your modules/packages:

func MergeMaps[M ~map[K]V, K comparable, V any](src ...M) M {
    merged := make(M)
    for _, m := range src {
        for k, v := range m {
            merged[k] = v
        }
    }
    return merged
}

Then you can use it very similarly to append():

func main() {
    mergedMaps := MergeMaps(
        map[string]int{"a": 1, "b": 2},
        map[string]int{"b": 3, "c": 4},
        map[string]int{"c": 3, "d": 4},
    )
    fmt.Println(mergedMaps)
}
Estrone answered 10/12, 2022 at 5:44 Comment(1)
This! This is the way. Simple, beautiful and idiomatic. Thank you @Oliver!Aldrich
U
1

Go is limited by what type of map it is. I'd suspect that there isn't built in functions because of the infinite number of type declarations that could exist for a map. So you have to build your own Merge functions depending on what type of map you are using:

func MergeJSONMaps(maps ...map[string]interface{}) (result map[string]interface{}) {
    result = make(map[string]interface{})
    for _, m := range maps {
        for k, v := range m {
            result[k] = v
        }
    }
    return result
}
Unknit answered 2/7, 2021 at 23:5 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.