This package provides a way to deep merge two structs of the same type.
It's useful for merging default values with user-provided values (e.g. configs). Values that are not present in the user-provided struct will be taken from the default struct.
Only exported fields are merged.
go get github.com/cardinalby/go-dto-merge
Example Config
struct has a nested UserConfig
struct.
import "github.com/cardinalby/go-dto-merge"
type UserConfig struct {
Role string // for non-pointer fields zero value indicates it's not specified
Name string
}
type Config struct {
Verbose *bool // it is a pointer to distinguish between "not specified" and false
User UserConfig
}
Given defaults, we can merge them with user-provided values:
// ptr is some helper function to create a pointer to a value
defaults := Config{ // it's called "src"
Verbose: ptr(true),
User: UserConfig{
Role: "admin",
Name: "John",
},
}
userProvided := Config{ // it's called "patch"
User: UserConfig{
Name: "Jane",
},
}
res, err := dtomerge.Merge(defaults, userProvided) // (src, patch)
// res == Config{
// Verbose: (*bool) true,
// User: UserConfig{
// Role: "admin",
// Name: "Jane",
// },
// }
Pointers can be used to distinguish between "not specified" and "explicit zero value" fields.
- If
patch
field contains a nil pointer, it will not overridedefaults.Verbose
- If
patch
field contains a pointer to zero value, it will overridesrc
field only in casesrc
pointer field is nil (value will be copied) - If
patch
field contains a pointer tp non-zero value, it will overridesrc
field (value will be copied)
Use dtomerge.OptDeRefPointers(false)
option to handle pointers as regular fields.
Setting additional option you can merge slices and maps as well.
import (
"github.com/cardinalby/go-dto-merge"
"github.com/cardinalby/go-dto-merge/opt"
)
type Config struct {
Roles []string
Permissions map[string]bool
}
defaults := Config{
Roles: []string{"admin", "user"},
Permissions: map[string]bool{
"read": true,
"write": false,
},
}
userProvided := Config{
Roles: []string{"user", "guest"},
Permissions: map[string]bool{
"write": true,
},
}
res, err := dtomerge.Merge(defaults, userProvided,
// merge map keys
dtomerge.OptIterateMaps(true),
// merge slices as unique sets
dtomerge.OptMergeSlices(dtomerge.SlicesMergeStrategyUnique),
)
// res == Config{
// Roles: []string{"admin", "user", "guest"},
// Permissions: map[string]bool{
// "read": true,
// "write": true,
// },
// }
Possible MergeSlices
strategies:
dtomerge.SlicesMergeStrategyUnique
:[1, 2, 3] + [4, 2, 1] → [1, 2, 3, 4]
dtomerge.SlicesMergeStrategyByIndex
:[1, 2, 3] + [11, 12] → [11, 12, 3]
dtomerge.SlicesMergeStrategyAtomic
: merge as a whole, default
You can:
- specify a custom merge function for a specific type.
- specify a custom merge options for a specific type.
See options godoc for more details.