详情

首页手游攻略 diff:实践指南

diff:实践指南

佚名 2026-09-13 17:10:01

团队讨论diff时,我会先把用途说清楚:用于比较 golang 结构的库。对日常自动化任务来说,输入边界、依赖和失败处理如果不清楚就很难稳定复用往往决定它能否落地,不能只用安装成功来判断。先用一项范围明确的真实任务完成最小试跑更稳妥;过程中要观察配置时间、输出质量、异常信息和维护痕迹,失败也应能解释原因。编辑判断上,愿意先做小范围验证并复查原始文档的团队可以优先研究它;其他团队不必为了热门标签勉强接入。

差异

用于比较 golang 结构和值的库。

利用字段标签和反射,它能够比较相同类型的两个结构并创建所有修改值的更改日志。生成的变更日志可以轻松序列化为 json。

NOTE:所有活跃的开发现在都在 v3 分支上进行。

安装

对于版本 3:

go get github.com/r3labs/diff/v3

支持的类型

diffable 值可以是 be/contain 以下任何类型:

类型 支持
结构体
切片
字符串
整数
布尔值
地图
指针
自定义类型

请参阅文档了解更多支持的类型、选项和功能。

标签

为了比较结构体字段,必须用给定名称标记它们。所有标签值均以 diff 为前缀。 i.e。 diff:"items"

标签 用途
- 将某个值排除在差异之外
identifier 如果您需要通过匹配标识符而不是顺序来比较数组,则可以指定 identifier 标记。如果在 from 和 to 结构中都找到了可识别元素,则将直接比较它们。 i.e。 diff:"name, identifier"
immutable 将在比较时忽略此结构字段。当使用 diff.StructValues() 时,这些值将被添加到返回的变更日志中。它的用例是当我们没有任何东西可以与结构进行比较并且想要显示其所有相关值时。
nocreate 默认补丁操作是在目标支柱、映射或切片中分配实例(如果它们不存在)。添加此标志将告诉 patch 跳过原本需要分配的元素。这与不可变是分开的,在修补时也尊重不可变。
omitunequal 修补是一种“尽力而为”的操作,并且默认情况下会尝试更新目标的“正确”成员,即使基础值已更改为更改日志“from”中的值以外的值。此标记将有选择地忽略不是 100% 匹配的值。

用途

基本示例

可以使用 diff 函数来完成一组基本值的比较。任何使用名称指定“diff”标签的项目都将进行比较。

import "github.com/r3labs/diff/v3"

type Order struct {
    ID    string `diff:"id"`
    Items []int  `diff:"items"`
}

func main() {
    a := Order{
        ID: "1234",
        Items: []int{1, 2, 3, 4},
    }

    b := Order{
        ID: "1234",
        Items: []int{1, 2, 4},
    }

    changelog, err := diff.Diff(a, b)
    ...
}

在此示例中,更改日志中生成的输出将指示值为“3”的第三个元素已从项目中删除。 将变更日志编组为 json 时,输出将如下所示:

[
    {
        "type": "delete",
        "path": ["items", "2"],
        "from": 3,
        "to": null
    }
]

选项和配置

可以在调用时设置差异选项,这会影响构建更改日志时差异的行为。

import "github.com/r3labs/diff/v3"

type Order struct {
    ID    string `diff:"id"`
    Items []int  `diff:"items"`
}

func main() {
    a := Order{
        ID: "1234",
        Items: []int{1, 2, 3, 4},
    }

    b := Order{
        ID: "1234",
        Items: []int{1, 2, 4},
    }

    changelog, err := diff.Diff(a, b, diff.DisableStructValues(), diff.AllowTypeMismatch(true))
    ...
}

您还可以创建一个允许设置选项的新实例。

import "github.com/r3labs/diff/v3"

type Order struct {
    ID    string `diff:"id"`
    Items []int  `diff:"items"`
}

func main() {
    a := Order{
        ID: "1234",
        Items: []int{1, 2, 3, 4},
    }

    b := Order{
        ID: "1234",
        Items: []int{1, 2, 4},
    }

    d, err := diff.NewDiffer(diff.SliceOrdering(true))
    if err != nil {
        panic(err)
    }

    changelog, err := d.Diff(a, b)
    ...
}

支持的选项有:

SliceOrdering 确保考虑切片中项目的顺序

DiscardComplexOrigin 是一个 diff 指令,用于省略有关结构的附加来源信息。这会改变补丁的行为,如果使用的话可能会导致一些陷阱和不直观的行为。另一方面,它可以显着减少大型复杂差异的内存占用。

AllowTypeMismatch 是一个全局指令,如果“from”不相等,则允许(true)或不允许(false)补丁应用更改。这实际上是 omitunequal 标签的全局版本。

Filter 提供回调,允许您确定差异下降到哪些字段

DisableStructValues 禁止为结构中的每个项目填充单独的更改,其中结构与 nil 值进行比较。

TagName 设置获取字段名称和选项时要使用的标签名称。

补丁和合并支持

Diff 还支持合并和修补。概念上与文本修补/合并 Patch 函数类似,给定 更改日志和目标实例将尽最大努力将更改日志中的更改应用到变量 指出。目的是目标指针具有相同的类型,但是,这不一定是 是的。例如,不同结构的两个切片可能足够相似,可以以多态方式应用更改,并且 补丁肯定会尝试。

补丁功能实际上不会失败,即使存在错误,它也可能足以成功完成手头的任务。 为了适应这个补丁,它会跟踪它尝试应用的每个更改日志选项,并报告其详细信息 发生的情况有待进一步审查。

import "github.com/r3labs/diff/v3"

type Order struct {
    ID    string `diff:"id"`
    Items []int  `diff:"items"`
}

func main() {
    a := Order{
        ID: "1234",
        Items: []int{1, 2, 3, 4},
    }

    b := Order{
        ID: "1234",
        Items: []int{1, 2, 4},
    }

    c := Order{}
    changelog, err := diff.Diff(a, b)

    patchlog := diff.Patch(changelog, &c)
    //Note the lack of an error. Patch is best effort and uses flags to indicate actions taken
    //and keeps any errors encountered along the way for review
    fmt.Printf("Encountered %d errors while patching", patchlog.ErrorCount())
    ...
}

修补时也可以使用不同选项集的实例。

package main

import "github.com/r3labs/diff/v3"

type Order struct {
	ID    string `json:"id"`
	Items []int  `json:"items"`
}

func main() {
    a := Order{
        ID:    "1234",
        Items: []int{1, 2, 3, 4},
        }

    b := Order{
        ID:    "1234",
        Items: []int{1, 2, 4},
    }

    d, _ := diff.NewDiffer(diff.TagName("json"))

    changelog, _ := d.Diff(a, b)

    d.Patch(changelog, &a)
    // reflect.DeepEqual(a, b) == true
}

为了方便起见,有一个合并功能,允许一个人采用三个界面并同时执行所有任务 时间。

import "github.com/r3labs/diff/v3"

type Order struct {
    ID    string `diff:"id"`
    Items []int  `diff:"items"`
}

func main() {
    a := Order{
        ID: "1234",
        Items: []int{1, 2, 3, 4},
    }

    b := Order{
        ID: "1234",
        Items: []int{1, 2, 4},
    }

    c := Order{}
    patchlog, err := diff.Merge(a, b, &c)
    if err != nil {
        fmt.Printf("Error encountered while diffing a & b")
    }
    fmt.Printf("Encountered %d errors while patching", patchlog.ErrorCount())
    ...
}

运行测试

make test

版本控制

为了使我们的发布周期保持透明并努力保持落后 兼容性,该项目根据 语义版本控制指南 进行维护。

点击查看更多
推荐专题
热门阅读