-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathgopartial.go
89 lines (76 loc) · 2.68 KB
/
gopartial.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
package gopartial
import (
"errors"
"log"
"reflect"
)
var errDestinationMustBeStructType = errors.New("Destination must be a struct type")
var errDestinationMustBePointerType = errors.New("Destination must be pointer to struct")
// PartialUpdate updates destination object (Must be a pointer to a struct)
// from a map[string]interface{} where struct tag name is equals to the map key.
// This function can extended through updaters. A list of function that accepts
// destination Value and the to be assigned Value and return true if updates is successful
// Returns list of struct field names that was successfully updated.
func PartialUpdate(dest interface{}, partial map[string]interface{}, tagName string, skipConditions []func(reflect.StructField) bool, updaters []func(reflect.Value, reflect.Value) bool) ([]string, error) {
valueOfDest := reflect.ValueOf(dest)
// Must be a pointer to a struct so that it can be updated
if valueOfDest.Kind() != reflect.Ptr {
return nil, errDestinationMustBePointerType
}
valueOfDest = valueOfDest.Elem()
typeOfDest := valueOfDest.Type()
// Must be a pointer to a struct so that it can be updated
if typeOfDest.Kind() != reflect.Struct {
return nil, errDestinationMustBeStructType
}
// fieldsUpdated is to keep track all the field names that were successfuly updated
fieldsUpdated := make([]string, 0)
for i := 0; i < typeOfDest.NumField(); i++ {
field := typeOfDest.Field(i)
// skip this field if it cant be set
if !valueOfDest.Field(i).CanSet() {
continue
}
skip := false
// go through all extended skip conditions
for _, skipCondition := range skipConditions {
skip = skipCondition(field)
if skip {
// break on the first skip condition found
break
}
}
if skip {
continue
}
// get the partial value based on the tagName
if val, ok := partial[field.Tag.Get(tagName)]; ok {
v := reflect.ValueOf(val)
updateSuccess := false
// easily assign the value if both end's kinds are the same
if valueOfDest.Field(i).Kind() == v.Kind() {
valueOfDest.Field(i).Set(v)
updateSuccess = true
} else {
// go through all extended process types
for _, updater := range updaters {
updateSuccess = updater(valueOfDest.Field(i), v)
if updateSuccess {
// the first updateSuccess found, break the loop
break
}
}
}
if updateSuccess {
fieldsUpdated = append(fieldsUpdated, field.Name)
} else {
if !v.IsValid() {
log.Printf("%v.%v cannot be assigned with value null", typeOfDest.Name(), field.Name)
} else {
log.Printf("%v.%v cannot be assigned with value %v", typeOfDest.Name(), field.Name, v.Interface())
}
}
}
}
return fieldsUpdated, nil
}