Files
go-config/internal/merge/merge.go
eSliderandCursor c575341178 feat(go-config): multi-format codecs, envc CLI, fixtures, ASRs
- Module github.com/eslider/go-config with env/yaml/json/ini packages
- internal: source, keymap, merge, structconv, bytesutil
- cmd/envc: convert, get, merge
- docs/asr ASR-0001..0008, README/CHANGELOG/AGENTS refresh
- golangci-lint v2 config; tests + fixtures

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-02 14:04:01 +01:00

78 lines
1.7 KiB
Go

// Package merge provides deep map merging with configurable slice behaviour.
package merge
// SliceStrategy controls how two []any values are combined at the same key.
type SliceStrategy int
const (
// Replace overwrites the destination slice with the source slice.
Replace SliceStrategy = iota
// Concat appends source elements to the destination slice.
Concat
)
// Option configures DeepMerge.
type Option func(*config)
type config struct {
slice SliceStrategy
}
// WithSliceStrategy sets slice merge behaviour (default Replace).
func WithSliceStrategy(s SliceStrategy) Option {
return func(c *config) { c.slice = s }
}
// DeepMerge folds src into dst in place. Maps recurse; scalar leaves use
// last-write-wins (src overwrites dst). For slice values, behaviour follows cfg.slice.
func DeepMerge(dst, src map[string]any, opts ...Option) {
cfg := config{slice: Replace}
for _, o := range opts {
o(&cfg)
}
if dst == nil || src == nil {
return
}
for k, sv := range src {
dv, ok := dst[k]
if !ok {
dst[k] = cloneValue(sv)
continue
}
dm, dIsMap := dv.(map[string]any)
sm, sIsMap := sv.(map[string]any)
if dIsMap && sIsMap {
DeepMerge(dm, sm, opts...)
dst[k] = dm
continue
}
if dsa, dIsSlice := dv.([]any); dIsSlice {
if ssa, sIsSlice := sv.([]any); sIsSlice {
switch cfg.slice {
case Concat:
dst[k] = append(append([]any{}, dsa...), ssa...)
default:
dst[k] = append([]any{}, ssa...)
}
continue
}
}
dst[k] = cloneValue(sv)
}
}
func cloneValue(v any) any {
switch t := v.(type) {
case map[string]any:
out := make(map[string]any, len(t))
DeepMerge(out, t)
return out
case []any:
cp := make([]any, len(t))
copy(cp, t)
return cp
default:
return v
}
}