dtomerge

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 17, 2024 License: MIT Imports: 4 Imported by: 2

README

test list Go Reference

dtomerge package

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.

Example

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

Pointers can be used to distinguish between "not specified" and "explicit zero value" fields.

  • If patch field contains a nil pointer, it will not override defaults.Verbose
  • If patch field contains a pointer to zero value, it will override src field only in case src pointer field is nil (value will be copied)
  • If patch field contains a pointer tp non-zero value, it will override src field (value will be copied)

Use dtomerge.OptDeRefPointers(false) option to handle pointers as regular fields.

Slices and maps

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

Other options

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.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsMerger

func AsMerger(
	value reflect.Value,
) (
	mergeFn func(override reflect.Value) (reflect.Value, error),
	isMerger bool,
)

func Merge

func Merge[T any](src T, patch T, opts ...Option) (T, error)

Types

type AtomicTypes

type AtomicTypes []reflect.Type

type CustomMergeFunc

type CustomMergeFunc[T any] func(src, override T) (T, error)

type CustomMergeFuncs

type CustomMergeFuncs map[reflect.Type]any

CustomMergeFuncs is a map of types to custom merge functions. Values should be of CustomMergeFunc[T] type where T is the same type as the key points to.

type CustomMergeOptions

type CustomMergeOptions map[reflect.Type]Options

type MergeOptionsProvider

type MergeOptionsProvider interface {
	GetMergeOptions() Options
}

type Merger

type Merger[T any] interface {
	Merge(override T) (T, error)
}

type Option

type Option func(*Options)

func OptAtomicTypes

func OptAtomicTypes(atomicTypes AtomicTypes) Option

OptAtomicTypes sets Options.AtomicTypes

func OptCustomMergeFuncs

func OptCustomMergeFuncs(customMergeFuncs CustomMergeFuncs) Option

OptCustomMergeFuncs sets Options.CustomMergeFuncs

func OptCustomMergeOptions

func OptCustomMergeOptions(customMergeOptions CustomMergeOptions) Option

OptCustomMergeOptions sets Options.CustomMergeOptions

func OptDeRefPointers

func OptDeRefPointers(deRefPointers bool) Option

OptDeRefPointers Sets Options.DeRefPointers

func OptIterateMaps

func OptIterateMaps(iterateMaps bool) Option

OptIterateMaps sets Options.IterateMaps

func OptMergeSlices

func OptMergeSlices(iterateSlices SlicesMergeStrategy) Option

OptMergeSlices sets Options.SlicesMerge

func OptRespectMergeOptionsProviders

func OptRespectMergeOptionsProviders(respectMergeOptionsProviders bool) Option

OptRespectMergeOptionsProviders sets Options.RespectMergeOptionsProviders

func OptRespectMergers

func OptRespectMergers(respectMergers bool) Option

OptRespectMergers sets Options.RespectMergers

type Options

type Options struct {

	// RespectMergers sets whether to respect Merger interface for fields. If true, fields that implement
	// Merger will be merged using their Merge method.
	// Default: true
	// Use OptRespectMergers to set this option
	RespectMergers bool

	// CustomMergeFuncs sets custom merge functions for specific types. If merge function is set for a type,
	// it will be used instead of default merge logic.
	// See CustomMergeFuncs for more details about functions signature.
	// Use OptCustomMergeFuncs to set this option
	CustomMergeFuncs CustomMergeFuncs

	// RespectMergeOptionsProviders sets whether to respect MergeOptionsProvider interface for fields. If true,
	// fields that implement MergeOptionsProvider will be merged with options returned from their GetMergeOptions method.
	// If CustomMergeOptions is set for a type, it will be used instead.
	// Default: true
	// Use OptRespectMergeOptionsProviders to set this option
	RespectMergeOptionsProviders bool

	// CustomMergeOptions sets custom merge options for specific types. If merge options are set for a type,
	// they will be used instead of merge options passed to Merge function.
	// Use OptCustomMergeOptions to set this option
	CustomMergeOptions CustomMergeOptions

	// DeRefPointers sets comparison mode for fields containing pointers (only if they are not Merger or
	// have defined custom merge function)
	//   - If true, pointers will be de-referenced (if not nil) and their values will be compared.
	//     if `patch` pointer is nil, `src` pointer will be used.
	//   - If false, pointers will be compared directly.
	//
	// Default: true
	// Use OptDeRefPointers to set this option
	DeRefPointers bool

	// sets types that should be treated as atomic. For a struct it means fields will not be iterated
	// and two structs will be compared with reflect.DeepEqual. If they are not equal, patch value will be used.
	// Use OptAtomicTypes to set this option
	AtomicTypes AtomicTypes

	// IterateMaps sets whether to iterate maps considering their keys as individual values.
	// If true, maps will be iterated and merged by keys (calling merge func with options recursively for values).
	// If false, maps will be compared with reflect.DeepEqual and patch value will be used if they are not equal.
	// Default: false
	// Use OptIterateMaps to set this option
	IterateMaps bool

	// MergeSlices sets how to merge slices.
	// See SlicesMergeStrategy for more details.
	// Default: SlicesMergeStrategyAtomic
	// Use OptMergeSlices to set this option
	SlicesMerge SlicesMergeStrategy
	// contains filtered or unexported fields
}

func NewOptions

func NewOptions(opts ...Option) Options

type SlicesMergeStrategy

type SlicesMergeStrategy string

SlicesMergeStrategy defines how to merge slices

const (
	// SlicesMergeStrategyAtomic considers slices as atomic values. Patch will entirely replace src if patch is not nil.
	SlicesMergeStrategyAtomic SlicesMergeStrategy = "atomic"

	// SlicesMergeStrategyUnique considers slices as sets of unique elements.
	// Patch element will be appended to src slice if it's not present in src.
	// If slice contains not comparable elements, it roll backs SlicesMergeStrategyAtomic
	SlicesMergeStrategyUnique SlicesMergeStrategy = "unique"

	// SlicesMergeStrategyByIndex merges elements with the same indexes.
	// Patch slice elements will replace the src element if the patch slice element is not a zero value
	// If patch slice is longer than src slice, remaining patch slice elements will be appended to src slice
	SlicesMergeStrategyByIndex SlicesMergeStrategy = "by_index"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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