英文:
How to diff entire struct with r3labs/diff
问题
我遇到了用于Go语言的github.com/r3labs/diff
库,用于比较两个相同类型的结构体。
这个库工作得很好,除了一个使用情况:我使用Date
结构体来表示日期:
type Date struct {
Year int
Month int
Day int
}
现在,还有一些更复杂的结构体,它们使用了Date
结构体,比如:
type Student struct {
DateOfBirth Date
}
如果我要比较两个学生,像这样:
diff.Diff(
Student{DateOfBirth: Date{2021, 11, 13}},
Student{DateOfBirth: Date{2021, 10, 9}},
)
我得到的结果是一个包含两个条目的更改日志,一个是DateOfBirth > Month
,另一个是DateOfBirth > Day
。
我希望的结果是一个包含一个条目(DateOfBirth
)和值2021-10-09
的更改日志。
这个库是否有可能实现这个要求?
英文:
I've come across the github.com/r3labs/diff
library for Go language to compare two structs of the same type.
Library is working quite well, except for one use-case which is the following: I am using the Date
struct to represent a date:
type Date struct {
Year int
Month int
Day int
}
Now, there are some other more complex structs that make use of the Date
struct let's say for example:
type Student struct {
DateOfBirth Date
}
If I am about the compare two students, like
diff.Diff(
Student{DateOfBirth: Date{2021, 11, 13}},
Student{DateOfBirth: Date{2021, 10, 9}},
)
what I would get as a result is a changelog with 2 items, one for the DateOfBirth > Month
and the other for the DateOfBirth > Day
.
My desired result would be a changelog with a single item (DateOfBirth
) and the value of 2021-10-09
.
Is that possible somehow with the library?
答案1
得分: 2
注意,此解决方案使用 github.com/r3labs/diff/v2
。
没有这样的选项。diff 只是递归处理结构字段,并为每个不同的字段生成更改日志。
要实现您想要的输出,您可以实现自己的 ValueDiffer
。这样,您可以以您想要的格式对结构进行差异比较,并将其附加到更改日志中。
下面是一个人为的示例,部分内容来自包内部:
type DateDiffer struct {
}
// 是否应使用此 differ 匹配特定类型
func (d *DateDiffer) Match(a, b reflect.Value) bool {
return diff.AreType(a, b, reflect.TypeOf(Date{}))
}
// 实际的差异函数,您还可以使用自定义格式将其附加到更改日志中
func (d *DateDiffer) Diff(cl *diff.Changelog, path []string, a, b reflect.Value) error {
if a.Kind() == reflect.Invalid {
cl.Add(diff.CREATE, path, nil, b.Interface())
return nil
}
if b.Kind() == reflect.Invalid {
cl.Add(diff.DELETE, path, a.Interface(), nil)
return nil
}
var d1, d2 Date
d1, _ = a.Interface().(Date)
d2, _ = b.Interface().(Date)
if d1.Day != d2.Day || d1.Month != d2.Month || d1.Year != d2.Year {
cl.Add(diff.UPDATE, path, fmt.Sprintf("%d-%d-%d", d1.Year, d1.Month, d1.Day), fmt.Sprintf("%d-%d-%d", d2.Year, d2.Month, d2.Day))
}
return nil
}
// 不确定这实际上是用来做什么的,但无论如何您都必须实现它
func (d *DateDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
return
}
然后您可以这样使用它:
d2, _ := diff.NewDiffer(diff.CustomValueDiffers(&DateDiffer{}))
s1 := Student{DateOfBirth: Date{2021, 11, 13}}
s2 := Student{DateOfBirth: Date{2021, 10, 9}}
ch2, _ := d2.Diff(s1, s2)
输出结果(以 JSON 格式进行编组和缩进):
[
{
"type": "update",
"path": [
"DateOfBirth"
],
"from": "2021-11-13",
"to": "2021-10-9"
}
]
英文:
NOTE this solution is using github.com/r3labs/diff/v2
.
There isn't such an option. The diff just processes struct fields recursively and produces the changelog for each different field.
To achieve the output you want, you can implement your own ValueDiffer
. That way you can diff structs "atomically" and append to the changelog in the format you want.
A contrived example, partially copied from the package internals:
type DateDiffer struct {
}
// Whether this differ should be used to match a specific type
func (d *DateDiffer) Match(a, b reflect.Value) bool {
return diff.AreType(a, b, reflect.TypeOf(Date{}))
}
// The actual diff function, where you also append to the changelog
// using your custom format
func (d *DateDiffer) Diff(cl *diff.Changelog, path []string, a, b reflect.Value) error {
if a.Kind() == reflect.Invalid {
cl.Add(diff.CREATE, path, nil, b.Interface())
return nil
}
if b.Kind() == reflect.Invalid {
cl.Add(diff.DELETE, path, a.Interface(), nil)
return nil
}
var d1, d2 Date
d1, _ = a.Interface().(Date)
d2, _ = b.Interface().(Date)
if d1.Day != d2.Day || d1.Month != d2.Month || d1.Year != d2.Year {
cl.Add(diff.UPDATE, path, fmt.Sprintf("%d-%d-%d", d1.Year, d1.Month, d1.Day), fmt.Sprintf("%d-%d-%d", d2.Year, d2.Month, d2.Day))
}
return nil
}
// unsure what this is actually for, but you must implement it either way
func (d *DateDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
return
}
And then you use it as such:
d2, _ := diff.NewDiffer(diff.CustomValueDiffers(&DateDiffer{}))
s1 := Student{DateOfBirth: Date{2021, 11, 13}}
s2 := Student{DateOfBirth: Date{2021, 10, 9}}
ch2, _ := d2.Diff(s1, s2)
Output (json marshalled and indented):
[
{
"type": "update",
"path": [
"DateOfBirth"
],
"from": "2021-11-13",
"to": "2021-10-9"
}
]
答案2
得分: 1
经过一些研究,我找到了解决方案。
我需要为Date
创建一个自定义的差异比较器,并且还需要使用包中的DisableStructValues
选项。
这个选项很有用,因为它禁止为结构体中的每个项创建单独的更改,并在将其与nil
值进行比较时返回整个对象。
diff.Diff(
Student{DateOfBirth: Date{2021, 11, 13}},
Student{DateOfBirth: Date{2021, 10, 9}},
diff.CustomValueDiffers(differ.DateDiffer{}),
diff.DisableStructValues()
)
要实现一个自定义的差异比较器,需要一个新的结构体,实现以下接口:
type ValueDiffer interface {
Match(a, b reflect.Value) bool
Diff(cl *Changelog, path []string, a, b reflect.Value) error
InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error)
}
这是我自定义差异比较器的实现。
type DateDiffer struct {
DiffFunc (func(path []string, a, b reflect.Value, p interface{}) error)
}
func (differ DateDiffer) Match(a, b reflect.Value) bool {
return diff.AreType(a, b, reflect.TypeOf(Date{}))
}
func (differ DateDiffer) Diff(cl *diff.Changelog, path []string, a, b reflect.Value) error {
if a.Kind() == reflect.Invalid {
cl.Add(diff.CREATE, path, nil, b.Interface())
return nil
}
if b.Kind() == reflect.Invalid {
cl.Add(diff.DELETE, path, a.Interface(), nil)
return nil
}
var source, target Date
source, _ = a.Interface().(Date)
target, _ = b.Interface().(Date)
if !source.Equal(target) {
cl.Add(diff.UPDATE, path, a.Interface(), b.Interface())
}
return nil
}
func (differ DateDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
differ.DiffFunc = dfunc
}
希望这对有类似需求的人有所帮助。
英文:
After some research I've found the solution.
I needed to create a custom differ for the Date
and also use the DisableStructValues
option from the package.
This option is useful as it disables populating a separate change for each item in a struct, and returns the whole object when comparing it to a nil
value.
diff.Diff(
Student{DateOfBirth: Date{2021, 11, 13}},
Student{DateOfBirth: Date{2021, 10, 9}},
diff.CustomValueDiffers(differ.DateDiffer{}),
diff.DisableStructValues()
)
To implement a custom differ, one needs a new struct that implements the following interface:
type ValueDiffer interface {
Match(a, b reflect.Value) bool
Diff(cl *Changelog, path []string, a, b reflect.Value) error
InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error)
}
And here is the implementation for my custom differ.
type DateDiffer struct {
DiffFunc (func(path []string, a, b reflect.Value, p interface{}) error)
}
func (differ DateDiffer) Match(a, b reflect.Value) bool {
return diff.AreType(a, b, reflect.TypeOf(Date{}))
}
func (differ DateDiffer) Diff(cl *diff.Changelog, path []string, a, b reflect.Value) error {
if a.Kind() == reflect.Invalid {
cl.Add(diff.CREATE, path, nil, b.Interface())
return nil
}
if b.Kind() == reflect.Invalid {
cl.Add(diff.DELETE, path, a.Interface(), nil)
return nil
}
var source, target Date
source, _ = a.Interface().(Date)
target, _ = b.Interface().(Date)
if !source.Equal(target) {
cl.Add(diff.UPDATE, path, a.Interface(), b.Interface())
}
return nil
}
func (differ DateDiffer) InsertParentDiffer(dfunc func(path []string, a, b reflect.Value, p interface{}) error) {
differ.DiffFunc = dfunc
}
Hope this will help someone with a similar use-case.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论