mirror of https://github.com/docker/cli.git
bump imdario/mergo v0.3.12
full diff: imdario/mergo@v0.3.8...v0.3.12 includes: - imdario/mergo@c085d66e6b use src map if dst is nil and can't be set - fixes imdario/mergo#90 panic: reflect: reflect.Value.Set using unaddressable value merging nested structures Signed-off-by: Jonathan Warriss-Simmons <misterws@diogenes.ws>
This commit is contained in:
parent
e3023ca3e3
commit
221bf5761f
|
@ -36,7 +36,7 @@ github.com/grpc-ecosystem/go-grpc-middleware 3c51f7f332123e8be5a157c0802a
|
||||||
github.com/grpc-ecosystem/grpc-gateway 1a03ca3bad1e1ebadaedd3abb76bc58d4ac8143b
|
github.com/grpc-ecosystem/grpc-gateway 1a03ca3bad1e1ebadaedd3abb76bc58d4ac8143b
|
||||||
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
|
||||||
github.com/hashicorp/golang-lru 7f827b33c0f158ec5dfbba01bb0b14a4541fd81d # v0.5.3
|
github.com/hashicorp/golang-lru 7f827b33c0f158ec5dfbba01bb0b14a4541fd81d # v0.5.3
|
||||||
github.com/imdario/mergo 1afb36080aec31e0d1528973ebe6721b191b0369 # v0.3.8
|
github.com/imdario/mergo 29fb3d3bdc5512887f1dc9aedde6a0fed407fa8f # v0.3.12
|
||||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 # v1.0.0
|
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 # v1.0.0
|
||||||
github.com/jaguilar/vt100 ad4c4a5743050fb7f88ce968dca9422f72a0e3f2 git://github.com/tonistiigi/vt100.git
|
github.com/jaguilar/vt100 ad4c4a5743050fb7f88ce968dca9422f72a0e3f2 git://github.com/tonistiigi/vt100.git
|
||||||
github.com/json-iterator/go a1ca0830781e007c66b225121d2cdb3a649421f6 # v1.1.10
|
github.com/json-iterator/go a1ca0830781e007c66b225121d2cdb3a649421f6 # v1.1.10
|
||||||
|
|
|
@ -1,44 +1,54 @@
|
||||||
# Mergo
|
# Mergo
|
||||||
|
|
||||||
|
|
||||||
|
[![GoDoc][3]][4]
|
||||||
|
[![GitHub release][5]][6]
|
||||||
|
[![GoCard][7]][8]
|
||||||
|
[![Build Status][1]][2]
|
||||||
|
[![Coverage Status][9]][10]
|
||||||
|
[![Sourcegraph][11]][12]
|
||||||
|
[![FOSSA Status][13]][14]
|
||||||
|
|
||||||
|
[![GoCenter Kudos][15]][16]
|
||||||
|
|
||||||
|
[1]: https://travis-ci.org/imdario/mergo.png
|
||||||
|
[2]: https://travis-ci.org/imdario/mergo
|
||||||
|
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||||
|
[4]: https://godoc.org/github.com/imdario/mergo
|
||||||
|
[5]: https://img.shields.io/github/release/imdario/mergo.svg
|
||||||
|
[6]: https://github.com/imdario/mergo/releases
|
||||||
|
[7]: https://goreportcard.com/badge/imdario/mergo
|
||||||
|
[8]: https://goreportcard.com/report/github.com/imdario/mergo
|
||||||
|
[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
|
||||||
|
[10]: https://coveralls.io/github/imdario/mergo?branch=master
|
||||||
|
[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
|
||||||
|
[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
|
||||||
|
[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
|
||||||
|
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
|
||||||
|
[15]: https://search.gocenter.io/api/ui/badge/github.com%2Fimdario%2Fmergo
|
||||||
|
[16]: https://search.gocenter.io/github.com/imdario/mergo
|
||||||
|
|
||||||
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||||
|
|
||||||
|
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
|
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
|
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
|
||||||
|
|
||||||
[![GoDoc][3]][4]
|
|
||||||
[![GoCard][5]][6]
|
|
||||||
[![Build Status][1]][2]
|
|
||||||
[![Coverage Status][7]][8]
|
|
||||||
[![Sourcegraph][9]][10]
|
|
||||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield)
|
|
||||||
|
|
||||||
[1]: https://travis-ci.org/imdario/mergo.png
|
|
||||||
[2]: https://travis-ci.org/imdario/mergo
|
|
||||||
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
|
||||||
[4]: https://godoc.org/github.com/imdario/mergo
|
|
||||||
[5]: https://goreportcard.com/badge/imdario/mergo
|
|
||||||
[6]: https://goreportcard.com/report/github.com/imdario/mergo
|
|
||||||
[7]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
|
|
||||||
[8]: https://coveralls.io/github/imdario/mergo?branch=master
|
|
||||||
[9]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
|
|
||||||
[10]: https://sourcegraph.com/github.com/imdario/mergo?badge
|
|
||||||
|
|
||||||
### Latest release
|
|
||||||
|
|
||||||
[Release v0.3.7](https://github.com/imdario/mergo/releases/tag/v0.3.7).
|
|
||||||
|
|
||||||
### Important note
|
### Important note
|
||||||
|
|
||||||
Please keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2) Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). An optional/variadic argument has been added, so it won't break existing code.
|
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds suppot for go modules.
|
||||||
|
|
||||||
If you were using Mergo **before** April 6th 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause (I hope it won't!) in existing projects after the change (release 0.2.0).
|
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
|
||||||
|
|
||||||
|
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||||
|
|
||||||
### Donations
|
### Donations
|
||||||
|
|
||||||
If Mergo is useful to you, consider buying me a coffee, a beer or making a monthly donation so I can keep building great free software. :heart_eyes:
|
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
|
||||||
|
|
||||||
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||||
[![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo)
|
[![Beerpay](https://beerpay.io/imdario/mergo/badge.svg)](https://beerpay.io/imdario/mergo)
|
||||||
|
@ -87,8 +97,9 @@ If Mergo is useful to you, consider buying me a coffee, a beer or making a month
|
||||||
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
||||||
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
||||||
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
|
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
|
||||||
|
- [containerssh/containerssh](https://github.com/containerssh/containerssh)
|
||||||
|
|
||||||
## Installation
|
## Install
|
||||||
|
|
||||||
go get github.com/imdario/mergo
|
go get github.com/imdario/mergo
|
||||||
|
|
||||||
|
@ -99,7 +110,7 @@ If Mergo is useful to you, consider buying me a coffee, a beer or making a month
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are not considered zero values](https://golang.org/ref/spec#The_zero_value) either. Also maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if err := mergo.Merge(&dst, src); err != nil {
|
if err := mergo.Merge(&dst, src); err != nil {
|
||||||
|
@ -125,9 +136,7 @@ if err := mergo.Map(&dst, srcMap); err != nil {
|
||||||
|
|
||||||
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
|
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
|
||||||
|
|
||||||
More information and examples in [godoc documentation](http://godoc.org/github.com/imdario/mergo).
|
Here is a nice example:
|
||||||
|
|
||||||
### Nice example
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
@ -175,10 +184,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type timeTransfomer struct {
|
type timeTransformer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t timeTransfomer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
if typ == reflect.TypeOf(time.Time{}) {
|
if typ == reflect.TypeOf(time.Time{}) {
|
||||||
return func(dst, src reflect.Value) error {
|
return func(dst, src reflect.Value) error {
|
||||||
if dst.CanSet() {
|
if dst.CanSet() {
|
||||||
|
@ -202,7 +211,7 @@ type Snapshot struct {
|
||||||
func main() {
|
func main() {
|
||||||
src := Snapshot{time.Now()}
|
src := Snapshot{time.Now()}
|
||||||
dest := Snapshot{}
|
dest := Snapshot{}
|
||||||
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransfomer{}))
|
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||||
fmt.Println(dest)
|
fmt.Println(dest)
|
||||||
// Will print
|
// Will print
|
||||||
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||||
|
|
|
@ -4,41 +4,140 @@
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Package mergo merges same-type structs and maps by setting default values in zero-value fields.
|
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||||
|
|
||||||
Mergo won't merge unexported (private) fields but will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
|
Status
|
||||||
|
|
||||||
|
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
|
||||||
|
|
||||||
|
Important note
|
||||||
|
|
||||||
|
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
|
||||||
|
|
||||||
|
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
|
||||||
|
|
||||||
|
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u github.com/imdario/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||||
|
|
||||||
|
Install
|
||||||
|
|
||||||
|
Do your usual installation procedure:
|
||||||
|
|
||||||
|
go get github.com/imdario/mergo
|
||||||
|
|
||||||
|
// use in your .go code
|
||||||
|
import (
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
|
|
||||||
From my own work-in-progress project:
|
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||||
|
|
||||||
type networkConfig struct {
|
if err := mergo.Merge(&dst, src); err != nil {
|
||||||
Protocol string
|
// ...
|
||||||
Address string
|
|
||||||
ServerType string `json: "server_type"`
|
|
||||||
Port uint16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FssnConfig struct {
|
Also, you can merge overwriting values using the transformer WithOverride.
|
||||||
Network networkConfig
|
|
||||||
|
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||||
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
var fssnDefault = FssnConfig {
|
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
|
||||||
networkConfig {
|
|
||||||
"tcp",
|
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||||
"127.0.0.1",
|
// ...
|
||||||
"http",
|
|
||||||
31560,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inside a function [...]
|
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
|
||||||
|
|
||||||
if err := mergo.Merge(&config, fssnDefault); err != nil {
|
Here is a nice example:
|
||||||
log.Fatal(err)
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
A string
|
||||||
|
B int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// More code [...]
|
func main() {
|
||||||
|
src := Foo{
|
||||||
|
A: "one",
|
||||||
|
B: 2,
|
||||||
|
}
|
||||||
|
dest := Foo{
|
||||||
|
A: "two",
|
||||||
|
}
|
||||||
|
mergo.Merge(&dest, src)
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// {two 2}
|
||||||
|
}
|
||||||
|
|
||||||
|
Transformers
|
||||||
|
|
||||||
|
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/imdario/mergo"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeTransformer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||||
|
if typ == reflect.TypeOf(time.Time{}) {
|
||||||
|
return func(dst, src reflect.Value) error {
|
||||||
|
if dst.CanSet() {
|
||||||
|
isZero := dst.MethodByName("IsZero")
|
||||||
|
result := isZero.Call([]reflect.Value{})
|
||||||
|
if result[0].Bool() {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot struct {
|
||||||
|
Time time.Time
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
src := Snapshot{time.Now()}
|
||||||
|
dest := Snapshot{}
|
||||||
|
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||||
|
fmt.Println(dest)
|
||||||
|
// Will print
|
||||||
|
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||||
|
}
|
||||||
|
|
||||||
|
Contact me
|
||||||
|
|
||||||
|
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
|
||||||
|
|
||||||
|
About
|
||||||
|
|
||||||
|
Written by Dario Castañé: https://da.rio.hn
|
||||||
|
|
||||||
|
License
|
||||||
|
|
||||||
|
BSD 3-Clause license, as Go language.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package mergo
|
package mergo
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/imdario/mergo
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require gopkg.in/yaml.v2 v2.3.0
|
|
@ -141,6 +141,9 @@ func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func _map(dst, src interface{}, opts ...func(*Config)) error {
|
func _map(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return ErrNonPointerAgument
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
vDst, vSrc reflect.Value
|
vDst, vSrc reflect.Value
|
||||||
err error
|
err error
|
||||||
|
|
|
@ -13,18 +13,30 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
func hasExportedField(dst reflect.Value) (exported bool) {
|
func hasMergeableFields(dst reflect.Value) (exported bool) {
|
||||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
field := dst.Type().Field(i)
|
field := dst.Type().Field(i)
|
||||||
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
||||||
exported = exported || hasExportedField(dst.Field(i))
|
exported = exported || hasMergeableFields(dst.Field(i))
|
||||||
} else {
|
} else if isExportedComponent(&field) {
|
||||||
exported = exported || len(field.PkgPath) == 0
|
exported = exported || len(field.PkgPath) == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isExportedComponent(field *reflect.StructField) bool {
|
||||||
|
pkgPath := field.PkgPath
|
||||||
|
if len(pkgPath) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := field.Name[0]
|
||||||
|
if 'a' <= c && c <= 'z' || c == '_' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Overwrite bool
|
Overwrite bool
|
||||||
AppendSlice bool
|
AppendSlice bool
|
||||||
|
@ -32,6 +44,8 @@ type Config struct {
|
||||||
Transformers Transformers
|
Transformers Transformers
|
||||||
overwriteWithEmptyValue bool
|
overwriteWithEmptyValue bool
|
||||||
overwriteSliceWithEmptyValue bool
|
overwriteSliceWithEmptyValue bool
|
||||||
|
sliceDeepCopy bool
|
||||||
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transformers interface {
|
type Transformers interface {
|
||||||
|
@ -46,7 +60,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
typeCheck := config.TypeCheck
|
typeCheck := config.TypeCheck
|
||||||
overwriteWithEmptySrc := config.overwriteWithEmptyValue
|
overwriteWithEmptySrc := config.overwriteWithEmptyValue
|
||||||
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
|
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
|
||||||
config.overwriteWithEmptyValue = false
|
sliceDeepCopy := config.sliceDeepCopy
|
||||||
|
|
||||||
if !src.IsValid() {
|
if !src.IsValid() {
|
||||||
return
|
return
|
||||||
|
@ -74,21 +88,34 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
|
|
||||||
switch dst.Kind() {
|
switch dst.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
if hasExportedField(dst) {
|
if hasMergeableFields(dst) {
|
||||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||||
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
|
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
if dst.IsNil() && !src.IsNil() {
|
if dst.IsNil() && !src.IsNil() {
|
||||||
|
if dst.CanSet() {
|
||||||
dst.Set(reflect.MakeMap(dst.Type()))
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
} else {
|
||||||
|
dst = src
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if src.Kind() != reflect.Map {
|
||||||
|
if overwrite {
|
||||||
|
dst.Set(src)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, key := range src.MapKeys() {
|
for _, key := range src.MapKeys() {
|
||||||
srcElement := src.MapIndex(key)
|
srcElement := src.MapIndex(key)
|
||||||
if !srcElement.IsValid() {
|
if !srcElement.IsValid() {
|
||||||
|
@ -98,6 +125,9 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
switch srcElement.Kind() {
|
switch srcElement.Kind() {
|
||||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
||||||
if srcElement.IsNil() {
|
if srcElement.IsNil() {
|
||||||
|
if overwrite {
|
||||||
|
dst.SetMapIndex(key, srcElement)
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
@ -132,7 +162,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
dstSlice = reflect.ValueOf(dstElement.Interface())
|
dstSlice = reflect.ValueOf(dstElement.Interface())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
|
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy {
|
||||||
if typeCheck && srcSlice.Type() != dstSlice.Type() {
|
if typeCheck && srcSlice.Type() != dstSlice.Type() {
|
||||||
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||||
}
|
}
|
||||||
|
@ -142,6 +172,24 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||||
}
|
}
|
||||||
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
||||||
|
} else if sliceDeepCopy {
|
||||||
|
i := 0
|
||||||
|
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
|
||||||
|
srcElement := srcSlice.Index(i)
|
||||||
|
dstElement := dstSlice.Index(i)
|
||||||
|
|
||||||
|
if srcElement.CanInterface() {
|
||||||
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||||
|
}
|
||||||
|
if dstElement.CanInterface() {
|
||||||
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
dst.SetMapIndex(key, dstSlice)
|
dst.SetMapIndex(key, dstSlice)
|
||||||
}
|
}
|
||||||
|
@ -161,27 +209,36 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
if !dst.CanSet() {
|
if !dst.CanSet() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
|
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
} else if config.AppendSlice {
|
} else if config.AppendSlice {
|
||||||
if src.Type() != dst.Type() {
|
if src.Type() != dst.Type() {
|
||||||
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
|
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
|
||||||
}
|
}
|
||||||
dst.Set(reflect.AppendSlice(dst, src))
|
dst.Set(reflect.AppendSlice(dst, src))
|
||||||
|
} else if sliceDeepCopy {
|
||||||
|
for i := 0; i < src.Len() && i < dst.Len(); i++ {
|
||||||
|
srcElement := src.Index(i)
|
||||||
|
dstElement := dst.Index(i)
|
||||||
|
if srcElement.CanInterface() {
|
||||||
|
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||||
|
}
|
||||||
|
if dstElement.CanInterface() {
|
||||||
|
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
fallthrough
|
fallthrough
|
||||||
case reflect.Interface:
|
case reflect.Interface:
|
||||||
if src.IsNil() {
|
if isReflectNil(src) {
|
||||||
break
|
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
|
||||||
}
|
|
||||||
|
|
||||||
if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) {
|
|
||||||
if dst.IsNil() || overwrite {
|
|
||||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,16 +260,28 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if dst.IsNil() || overwrite {
|
if dst.IsNil() || overwrite {
|
||||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
}
|
}
|
||||||
} else if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst.Elem().Kind() == src.Elem().Kind() {
|
||||||
|
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
if dst.CanSet() && (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) {
|
mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc)
|
||||||
|
if mustSet {
|
||||||
|
if dst.CanSet() {
|
||||||
dst.Set(src)
|
dst.Set(src)
|
||||||
|
} else {
|
||||||
|
dst = src
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +315,13 @@ func WithOverride(config *Config) {
|
||||||
config.Overwrite = true
|
config.Overwrite = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithOverride will make merge override empty dst slice with empty src slice.
|
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
|
||||||
|
func WithOverwriteWithEmptyValue(config *Config) {
|
||||||
|
config.Overwrite = true
|
||||||
|
config.overwriteWithEmptyValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
|
||||||
func WithOverrideEmptySlice(config *Config) {
|
func WithOverrideEmptySlice(config *Config) {
|
||||||
config.overwriteSliceWithEmptyValue = true
|
config.overwriteSliceWithEmptyValue = true
|
||||||
}
|
}
|
||||||
|
@ -261,7 +336,16 @@ func WithTypeCheck(config *Config) {
|
||||||
config.TypeCheck = true
|
config.TypeCheck = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
|
||||||
|
func WithSliceDeepCopy(config *Config) {
|
||||||
|
config.sliceDeepCopy = true
|
||||||
|
config.Overwrite = true
|
||||||
|
}
|
||||||
|
|
||||||
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
|
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||||
|
return ErrNonPointerAgument
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
vDst, vSrc reflect.Value
|
vDst, vSrc reflect.Value
|
||||||
err error
|
err error
|
||||||
|
@ -281,3 +365,16 @@ func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||||
}
|
}
|
||||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsReflectNil is the reflect value provided nil
|
||||||
|
func isReflectNil(v reflect.Value) bool {
|
||||||
|
k := v.Kind()
|
||||||
|
switch k {
|
||||||
|
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
|
||||||
|
// Both interface and slice are nil if first word is 0.
|
||||||
|
// Both are always bigger than a word; assume flagIndir.
|
||||||
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ var (
|
||||||
ErrNotSupported = errors.New("only structs and maps are supported")
|
ErrNotSupported = errors.New("only structs and maps are supported")
|
||||||
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||||
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
||||||
|
ErrNonPointerAgument = errors.New("dst must be a pointer")
|
||||||
)
|
)
|
||||||
|
|
||||||
// During deepMerge, must keep track of checks that are
|
// During deepMerge, must keep track of checks that are
|
||||||
|
@ -75,23 +76,3 @@ func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverses recursively both values, assigning src's fields values to dst.
|
|
||||||
// The map argument tracks comparisons that have already been seen, which allows
|
|
||||||
// short circuiting on recursive types.
|
|
||||||
func deeper(dst, src reflect.Value, visited map[uintptr]*visit, depth int) (err error) {
|
|
||||||
if dst.CanAddr() {
|
|
||||||
addr := dst.UnsafeAddr()
|
|
||||||
h := 17 * addr
|
|
||||||
seen := visited[h]
|
|
||||||
typ := dst.Type()
|
|
||||||
for p := seen; p != nil; p = p.next {
|
|
||||||
if p.ptr == addr && p.typ == typ {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remember, remember...
|
|
||||||
visited[h] = &visit{addr, typ, seen}
|
|
||||||
}
|
|
||||||
return // TODO refactor
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue