Skip to content

Commit faaae30

Browse files
Merge pull request #50 from r3labs/v2
merge v2 branch
2 parents f54a072 + 9e5baa4 commit faaae30

16 files changed

+504
-81
lines changed

README.md

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ A diffable value can be/contain any of the following types:
5252

5353

5454
| Type | Supported |
55-
|--------------|-----------|
55+
| ------------ | --------- |
5656
| struct ||
5757
| slice ||
5858
| string ||
@@ -69,12 +69,12 @@ Please see the docs for more supported types, options and features.
6969

7070
In order for struct fields to be compared, they must be tagged with a given name. All tag values are prefixed with `diff`. i.e. `diff:"items"`.
7171

72-
| Tag | Usage |
73-
|--------------|------------------------------------|
74-
| `-` | Excludes a value from being diffed |
75-
| `identifier` | If you need to compare arrays by a matching identifier and not based on order, you can specify the `identifier` tag. If an identifiable element is found in both the from and to structures, they will be directly compared. i.e. `diff:"name, identifier"` |
76-
| `immutable` | Will omit this struct field from diffing. When using `diff.StructValues()` these values will be added to the returned changelog. It's use case is for when we have nothing to compare a struct to and want to show all of its relevant values. |
77-
| `nocreate` | The default patch action is to allocate instances in the target strut, map or slice should they not exist. Adding this flag will tell patch to skip elements that it would otherwise need to allocate. This is separate from immutable, which is also honored while patching. |
72+
| Tag | Usage |
73+
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
74+
| `-` | Excludes a value from being diffed |
75+
| `identifier` | If you need to compare arrays by a matching identifier and not based on order, you can specify the `identifier` tag. If an identifiable element is found in both the from and to structures, they will be directly compared. i.e. `diff:"name, identifier"` |
76+
| `immutable` | Will omit this struct field from diffing. When using `diff.StructValues()` these values will be added to the returned changelog. It's use case is for when we have nothing to compare a struct to and want to show all of its relevant values. |
77+
| `nocreate` | The default patch action is to allocate instances in the target strut, map or slice should they not exist. Adding this flag will tell patch to skip elements that it would otherwise need to allocate. This is separate from immutable, which is also honored while patching. |
7878
| `omitunequal` | Patching is a 'best effort' operation, and will by default attempt to update the 'correct' member of the target even if the underlying value has already changed to something other than the value in the change log 'from'. This tag will selectively ignore values that are not a 100% match. |
7979

8080
## Usage
@@ -84,7 +84,7 @@ In order for struct fields to be compared, they must be tagged with a given name
8484
Diffing a basic set of values can be accomplished using the diff functions. Any items that specify a "diff" tag using a name will be compared.
8585

8686
```go
87-
import "github.com/r3labs/diff"
87+
import "github.com/r3labs/diff/v2"
8888

8989
type Order struct {
9090
ID string `diff:"id"`
@@ -125,7 +125,7 @@ When marshalling the changelog to json, the output will look like:
125125

126126
Options can be set on the differ at call time which effect how diff acts when building the change log.
127127
```go
128-
import "github.com/r3labs/diff"
128+
import "github.com/r3labs/diff/v2"
129129

130130
type Order struct {
131131
ID string `diff:"id"`
@@ -151,7 +151,7 @@ func main() {
151151
You can also create a new instance of a differ that allows options to be set.
152152

153153
```go
154-
import "github.com/r3labs/diff"
154+
import "github.com/r3labs/diff/v2"
155155

156156
type Order struct {
157157
ID string `diff:"id"`
@@ -169,10 +169,10 @@ func main() {
169169
Items: []int{1, 2, 4},
170170
}
171171

172-
d, err := diff.NewDiffer(diff.SliceOrdering(true))
173-
if err != nil {
174-
panic(err)
175-
}
172+
d, err := diff.NewDiffer(diff.SliceOrdering(true))
173+
if err != nil {
174+
panic(err)
175+
}
176176

177177
changelog, err := d.Diff(a, b)
178178
...
@@ -205,7 +205,7 @@ To accommodate this patch keeps track of each change log option it attempts to a
205205
happened for further scrutiny.
206206

207207
```go
208-
import "github.com/r3labs/diff"
208+
import "github.com/r3labs/diff/v2"
209209

210210
type Order struct {
211211
ID string `diff:"id"`
@@ -234,11 +234,44 @@ func main() {
234234
}
235235
```
236236

237+
Instances of differ with options set can also be used when patching.
238+
239+
```go
240+
package main
241+
242+
import "github.com/r3labs/diff/v2"
243+
244+
type Order struct {
245+
ID string `json:"id"`
246+
Items []int `json:"items"`
247+
}
248+
249+
func main() {
250+
a := Order{
251+
ID: "1234",
252+
Items: []int{1, 2, 3, 4},
253+
}
254+
255+
b := Order{
256+
ID: "1234",
257+
Items: []int{1, 2, 4},
258+
}
259+
260+
d, _ := diff.NewDiffer(diff.TagName("json"))
261+
262+
changelog, _ := d.Diff(a, b)
263+
264+
d.Patch(changelog, &a)
265+
// reflect.DeepEqual(a, b) == true
266+
}
267+
268+
```
269+
237270
As a convenience, there is a Merge function that allows one to take three interfaces and perform all the tasks at the same
238271
time.
239272

240273
```go
241-
import "github.com/r3labs/diff"
274+
import "github.com/r3labs/diff/v2"
242275

243276
type Order struct {
244277
ID string `diff:"id"`

change_value.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package diff
22

33
import (
4+
"fmt"
45
"reflect"
56
)
67

@@ -73,14 +74,24 @@ func (c ChangeValue) ParentLen() (ret int) {
7374
}
7475

7576
//ParentSet - nil safe parent set
76-
func (c *ChangeValue) ParentSet(value reflect.Value) {
77+
func (c *ChangeValue) ParentSet(value reflect.Value, convertCompatibleTypes bool) {
7778
if c != nil && c.parent != nil {
7879
defer func() {
7980
if r := recover(); r != nil {
8081
c.SetFlag(FlagParentSetFailed)
8182
}
8283
}()
83-
c.parent.Set(value)
84+
85+
if convertCompatibleTypes {
86+
if !value.Type().ConvertibleTo(c.parent.Type()) {
87+
c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.parent.Type().String()))
88+
c.SetFlag(FlagParentSetFailed)
89+
return
90+
}
91+
c.parent.Set(value.Convert(c.parent.Type()))
92+
} else {
93+
c.parent.Set(value)
94+
}
8495
c.SetFlag(FlagParentSetApplied)
8596
}
8697
}
@@ -91,7 +102,7 @@ func (c ChangeValue) Len() int {
91102
}
92103

93104
//Set echos reflect set
94-
func (c *ChangeValue) Set(value reflect.Value) {
105+
func (c *ChangeValue) Set(value reflect.Value, convertCompatibleTypes bool) {
95106
if c != nil {
96107
defer func() {
97108
if r := recover(); r != nil {
@@ -103,7 +114,17 @@ func (c *ChangeValue) Set(value reflect.Value) {
103114
c.SetFlag(FlagIgnored)
104115
return
105116
}
106-
c.target.Set(value)
117+
118+
if convertCompatibleTypes {
119+
if !value.Type().ConvertibleTo(c.target.Type()) {
120+
c.AddError(fmt.Errorf("Value of type %s is not convertible to %s", value.Type().String(), c.target.Type().String()))
121+
c.SetFlag(FlagFailed)
122+
return
123+
}
124+
c.target.Set(value.Convert(c.target.Type()))
125+
} else {
126+
c.target.Set(value)
127+
}
107128
c.SetFlag(FlagApplied)
108129
}
109130
}

diff.go

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ package diff
77
import (
88
"errors"
99
"fmt"
10-
"github.com/vmihailenco/msgpack"
1110
"reflect"
1211
"strconv"
1312
"strings"
13+
14+
"github.com/vmihailenco/msgpack"
1415
)
1516

1617
const (
@@ -24,15 +25,17 @@ const (
2425

2526
// Differ a configurable diff instance
2627
type Differ struct {
27-
TagName string
28-
SliceOrdering bool
29-
DisableStructValues bool
30-
customValueDiffers []ValueDiffer
31-
cl Changelog
32-
AllowTypeMismatch bool
33-
DiscardParent bool
34-
StructMapKeys bool
35-
Filter FilterFunc
28+
TagName string
29+
SliceOrdering bool
30+
DisableStructValues bool
31+
customValueDiffers []ValueDiffer
32+
cl Changelog
33+
AllowTypeMismatch bool
34+
DiscardParent bool
35+
StructMapKeys bool
36+
FlattenEmbeddedStructs bool
37+
ConvertCompatibleTypes bool
38+
Filter FilterFunc
3639
}
3740

3841
// Changelog stores a list of changed items
@@ -250,11 +253,11 @@ func swapChange(t string, c Change) Change {
250253
}
251254

252255
func idComplex(v interface{}) string {
253-
switch v.(type) {
256+
switch v := v.(type) {
254257
case string:
255-
return v.(string)
258+
return v
256259
case int:
257-
return strconv.Itoa(v.(int))
260+
return strconv.Itoa(v)
258261
default:
259262
b, err := msgpack.Marshal(v)
260263
if err != nil {
@@ -265,11 +268,11 @@ func idComplex(v interface{}) string {
265268

266269
}
267270
func idstring(v interface{}) string {
268-
switch v.(type) {
271+
switch v := v.(type) {
269272
case string:
270-
return v.(string)
273+
return v
271274
case int:
272-
return strconv.Itoa(v.(int))
275+
return strconv.Itoa(v)
273276
default:
274277
return fmt.Sprint(v)
275278
}

diff_map.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ package diff
66

77
import (
88
"fmt"
9-
"github.com/vmihailenco/msgpack"
109
"reflect"
10+
11+
"github.com/vmihailenco/msgpack"
1112
)
1213

1314
func (d *Differ) diffMap(path []string, a, b reflect.Value) error {
@@ -57,7 +58,8 @@ func (d *Differ) mapValues(t string, path []string, a reflect.Value) error {
5758
if d.StructMapKeys {
5859
//it's not enough to turn k to a string, we need to able to marshal a type when
5960
//we apply it in patch so... we'll marshal it to JSON
60-
if b, err := msgpack.Marshal(k.Interface()); err == nil {
61+
var b []byte
62+
if b, err = msgpack.Marshal(k.Interface()); err == nil {
6163
err = d.diff(append(path, string(b)), xe, ae, a.Interface())
6264
}
6365
} else {

diff_slice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ type sliceTracker []bool
9090

9191
func (st *sliceTracker) has(s, v reflect.Value) bool {
9292
if len(*st) != s.Len() {
93-
(*st) = make([]bool, s.Len(), s.Len())
93+
(*st) = make([]bool, s.Len())
9494
}
9595

9696
for i := 0; i < s.Len(); i++ {

diff_string.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ func (d *Differ) diffString(path []string, a, b reflect.Value, parent interface{
2222
}
2323

2424
if a.String() != b.String() {
25-
d.cl.Add(UPDATE, path, a.String(), b.String(), parent)
25+
if a.CanInterface() {
26+
// If a and/or b is of a type that is an alias for String, store that type in changelog
27+
d.cl.Add(UPDATE, path, a.Interface(), b.Interface(), parent)
28+
} else {
29+
d.cl.Add(UPDATE, path, a.String(), b.String(), parent)
30+
}
2631
}
2732

2833
return nil

diff_struct.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ func (d *Differ) diffStruct(path []string, a, b reflect.Value) error {
4545
af := a.Field(i)
4646
bf := b.FieldByName(field.Name)
4747

48-
fpath := copyAppend(path, tname)
48+
fpath := path
49+
if !(d.FlattenEmbeddedStructs && field.Anonymous) {
50+
fpath = copyAppend(fpath, tname)
51+
}
4952

5053
if d.Filter != nil && !d.Filter(fpath, a.Type(), field) {
5154
continue

0 commit comments

Comments
 (0)