forked from darccio/mergo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmergo.go
129 lines (121 loc) · 3.57 KB
/
mergo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright 2013 Dario Castañé. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Based on src/pkg/reflect/deepequal.go from official
// golang's stdlib.
package mergo
import (
"errors"
"reflect"
)
// Errors reported by Mergo when it finds invalid arguments.
var (
NilArgumentsErr = errors.New("src and dst must not be nil")
DifferentArgumentsTypesErr = errors.New("src and dst must be of same type")
NotSupportedErr = errors.New("only structs and maps are supported")
)
// During deepMerge, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited are stored in a map indexed by 17 * a1 + a2;
type visit struct {
ptr uintptr
typ reflect.Type
next *visit
}
// From src/pkg/encoding/json.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return false
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return false
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return false
case reflect.Float32, reflect.Float64:
return false
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
// 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 deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int) error {
if !src.IsValid() {
return nil
}
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}
}
switch dst.Kind() {
case reflect.Struct:
for i, n := 0, dst.NumField(); i < n; i++ {
if err := deepMerge(dst.Field(i), src.Field(i), visited, depth + 1); err != nil {
return err
}
}
case reflect.Map:
for _, key := range src.MapKeys() {
srcElement := src.MapIndex(key)
if !srcElement.IsValid() {
continue
}
dstElement := dst.MapIndex(key)
switch reflect.TypeOf(srcElement.Interface()).Kind() {
case reflect.Struct:
fallthrough
case reflect.Map:
if err := deepMerge(dstElement, srcElement, visited, depth + 1); err != nil {
return err
}
}
if !dstElement.IsValid() {
dst.SetMapIndex(key, srcElement)
}
}
case reflect.Interface:
if err := deepMerge(dst.Elem(), src.Elem(), visited, depth + 1); err != nil {
return err
}
default:
if dst.CanSet() && !isEmptyValue(src) {
dst.Set(src)
}
}
return nil
}
// Merge sets fields' values in dst from src if they have a zero
// value of their type.
// dst and src must be valid same-type structs and dst must be
// a pointer to struct.
// It won't merge unexported (private) fields and will do recursively
// any exported field.
func Merge(dst interface{}, src interface{}) error {
if dst == nil || src == nil {
return NilArgumentsErr
}
vDst := reflect.ValueOf(dst).Elem()
vSrc := reflect.ValueOf(src)
if vDst.Type() != vSrc.Type() {
return DifferentArgumentsTypesErr
}
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map {
return NotSupportedErr
}
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0)
}