deepmerge

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 0 Imported by: 0

Documentation

Overview

Package deepmerge deeply merges maps, modeled on the npm "deepmerge" package. It recursively combines a target map with a source map, producing a brand new map in which nested objects are merged key by key rather than being replaced wholesale. The public API is Merge for the common case, MergeWith for callers that need to customize how slices combine, and MergeAll for folding an arbitrary number of maps together left to right.

Reach for deepmerge whenever you need to layer configuration or state: default settings overlaid by user settings, a base document patched by an override, or several partial fragments assembled into one. Because later values win only at the leaves where they actually appear, a source map that sets a single nested field does not clobber sibling fields that only exist in the target. This is the behavior that a shallow copy or a plain map assignment cannot give you.

Internally the merge walks both maps in parallel. Keys present only in the target are cloned into the result; keys present only in the source are cloned in as well; keys present in both are reconciled by mergeValues. When both values are map[string]any they are merged recursively. When both values are []any they are combined by the ArrayMerge strategy (concatenation by default). For every other combination, including a type mismatch such as a slice in the target and a map in the source, the source value simply replaces the target value. Scalars, functions, and any non-map/non-slice values are always treated as opaque leaves.

Merging never mutates its inputs. Every value that ends up in the result is deep-cloned first, so nested maps and slices in the returned map are fully independent of the originals: mutating the result cannot reach back and change target or source, and vice versa. Nil inputs are accepted and treated as empty maps, so Merge(nil, m) and Merge(m, nil) both yield a clone of the non-nil argument, and MergeAll with no arguments returns an empty, non-nil map. Because Go maps have no defined iteration order the result is order-independent, which matters only for the default array behavior where target elements precede source elements deterministically regardless of map traversal.

Compared with the Node original, this port keeps the same default semantics of recursive object merge plus array concatenation and the same guarantee of no input mutation. The main difference is idiomatic: it operates on Go's map[string]any and []any rather than arbitrary JavaScript objects, it has no notion of a customMerge-per-key callback beyond the single ArrayMerge hook, and it does not special-case class instances or non-plain objects the way the JavaScript isMergeableObject check does. Only map[string]any is treated as a mergeable object; everything else is a leaf.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Merge

func Merge(target, source map[string]any) map[string]any

Merge returns a new map that is the deep merge of target and source. Neither input is mutated. Slices are concatenated (target then source).

Example

ExampleMerge deeply combines two maps into a brand new map. Keys present in only one input are carried over, and keys present in both are reconciled at the leaves: the nested map under "nested" is merged key by key rather than replaced wholesale, so both "x" from the target and "y" from the source survive. Neither input is mutated. This is the behavior you want when layering default settings under user overrides.

package main

import (
	"fmt"

	"github.com/malcolmston/express/deepmerge"
)

func main() {
	target := map[string]any{"a": 1, "nested": map[string]any{"x": 1}}
	source := map[string]any{"b": 2, "nested": map[string]any{"y": 2}}

	merged := deepmerge.Merge(target, source)
	fmt.Println(merged)
}
Output:
map[a:1 b:2 nested:map[x:1 y:2]]

func MergeAll

func MergeAll(maps ...map[string]any) map[string]any

MergeAll deeply merges any number of maps left to right and returns a new map. Later maps take precedence over earlier ones, slices are concatenated, and no input is mutated.

Example

ExampleMergeAll folds any number of maps together left to right, with later maps taking precedence over earlier ones. It is equivalent to reducing the maps with Merge, so nested objects still merge key by key rather than clobbering siblings. Here three fragments assemble into a single map. Calling it with no arguments returns an empty, non-nil map.

package main

import (
	"fmt"

	"github.com/malcolmston/express/deepmerge"
)

func main() {
	merged := deepmerge.MergeAll(
		map[string]any{"a": 1},
		map[string]any{"b": 2},
		map[string]any{"a": 3},
	)
	fmt.Println(merged)
}
Output:
map[a:3 b:2]

func MergeWith

func MergeWith(target, source map[string]any, opts Options) map[string]any

MergeWith returns a new map that is the deep merge of target and source using opts. Neither input is mutated.

Types

type Options

type Options struct {
	// ArrayMerge, when non-nil, combines a target slice and a source slice into
	// the merged result. Both arguments are clones owned by the merge, so the
	// function may reuse or return them freely. When nil, slices are
	// concatenated (target then source).
	ArrayMerge func(target, source []any) []any
}

Options configures MergeWith.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL