How to diff entire struct with r3labs/diff

huangapple go评论82阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2021年11月20日 07:13:55
  • 转载请务必保留本文链接:https://go.coder-hub.com/70041893.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定