{"id":20355883,"url":"https://github.com/hedzr/evendeep","last_synced_at":"2026-02-14T03:27:14.107Z","repository":{"id":57694556,"uuid":"487416545","full_name":"hedzr/evendeep","owner":"hedzr","description":"Per-field copying deeply, and comparing deeply abilities: deepcopy, deepdiff and more...","archived":false,"fork":false,"pushed_at":"2025-11-16T09:34:56.000Z","size":1024,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-16T11:23:23.215Z","etag":null,"topics":["clone","deepcopy","deepdiff","deepequal","diff","go-library","reflect"],"latest_commit_sha":null,"homepage":"https://docs.hedzr.com/docs/evendeep/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hedzr.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-05-01T01:11:29.000Z","updated_at":"2025-11-16T09:34:55.000Z","dependencies_parsed_at":"2023-02-16T14:31:41.175Z","dependency_job_id":"97943d7a-eb4d-4c65-bf60-086ecf6848fa","html_url":"https://github.com/hedzr/evendeep","commit_stats":{"total_commits":302,"total_committers":1,"mean_commits":302.0,"dds":0.0,"last_synced_commit":"3190f11c25d2db926d79def413430893bcc175ee"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"purl":"pkg:github/hedzr/evendeep","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hedzr%2Fevendeep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hedzr%2Fevendeep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hedzr%2Fevendeep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hedzr%2Fevendeep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hedzr","download_url":"https://codeload.github.com/hedzr/evendeep/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hedzr%2Fevendeep/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29433426,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T02:20:56.896Z","status":"ssl_error","status_checked_at":"2026-02-14T02:11:29.478Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clone","deepcopy","deepdiff","deepequal","diff","go-library","reflect"],"created_at":"2024-11-14T23:14:23.130Z","updated_at":"2026-02-14T03:27:14.096Z","avatar_url":"https://github.com/hedzr.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# even-deep\n\n![Go](https://github.com/hedzr/evendeep/workflows/Go/badge.svg)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/hedzr/evendeep)\n[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/hedzr/evendeep.svg?label=release)](https://github.com/hedzr/evendeep/releases)\n[![go.dev](https://img.shields.io/badge/go.dev-reference-green)](https://pkg.go.dev/github.com/hedzr/evendeep)\n[![Go Report Card](https://goreportcard.com/badge/github.com/hedzr/evendeep)](https://goreportcard.com/report/github.com/hedzr/evendeep) \u003c!--\n[![codecov](https://codecov.io/gh/hedzr/evendeep/branch/master/graph/badge.svg)](https://codecov.io/gh/hedzr/evendeep) --\u003e\n[![Coverage Status](https://coveralls.io/repos/github/hedzr/evendeep/badge.svg?branch=master)](https://coveralls.io/github/hedzr/evendeep?branch=master)\n\nThis is a standard deepcopy library. It provides per-field copying deeply, and compares deeply abilities.\n\nThis library is designed for making everything customizable.\n\n## Features\n\n- loosely and reasonable data-types conversions, acrossing primitives, composites and functions, with customizable\n  converters/transformers\n- unexported values (optional), ...\n- circular references immunization\n- fully customizable\n\n  - user-defined value/type converters/transformers\n  - user-defined field to field name converting rule via struct Tag\n- easily apply different strategies\n\n  - basic strategies are: copy-n-merge, clone,\n  - strategies per struct field:\n    `slicecopy`, `slicemerge`, `mapcopy`, `mapmerge`,\n    `omitempty` (keep if source is zero or nil), `omitnil`, `omitzero`,\n    `keepneq` (keep if not equal), `cleareq` (clear if equal), ...\n- copy fields by name or ordinal\n\n  - field to field\n  - field to method, method to field\n  - value to function (as input), function result to value\n  - slice[0] to struct, struct to slice[0]\n  - struct to map, map to struct\n  - User-defined extractor/getter on various source\n  - User-defined setter for struct or map target (if mapkey is string)\n  - ...\n- The deep series\n\n  - deepcopy: [`DeepCopy()`](https://github.com/hedzr/evendeep/blob/master/deepcopy.go#L48),\n    or [`New()`](https://github.com/hedzr/evendeep/blob/master/deepcopy.go#L32)\n  - deepclone:[`MakeClone()`](https://github.com/hedzr/evendeep/blob/master/deepcopy.go#L63)\n  - deepequal: [`DeepEqual()`](https://github.com/hedzr/evendeep/blob/master/deepequal.go#L12)\n  - deepdiff: [`DeepDiff()`](https://github.com/hedzr/evendeep/blob/master/deepdiff.go#L13)\n- Compatibilities\n\n  - Run for Go Modules and Generics enable, and log/slog present (go1.21+ since v1)\n    - since v1, `debug/buildinfo` requires go1.18+, `log/slog` wants go1.21+.\n    - for the v0.x versions, go1.11+ is okay.\n\n## History\n\n- More in [CHANGELOG](https://github.com/hedzr/evendeep/blob/master/CHANGELOG)\n\n## Usages\n\n### deepcopy\n\n`eventdeep.New`, `eventdeep.MakeClone` and `eventdeep.DeepCopy` are main entries.\n\nBy default, `DeepCopy()` will copy and **merge** source into destination object. That means, a map or a slice will be\nmerged\ndeeply, same to a struct.\n\n[`New(opts...)`](https://github.com/hedzr/evendeep/blob/master/deepcopy.go#L48) gives a most even scalable interface\nthan `DeepCopy`, it returns a new `DeepCopier` different to `DefaultCopyController` and you can make call\nto `DeepCopier.DeepCopy(old, new, opts...)`.\n\nIn copy-n-merge mode, copying `[2, 3]` to `[3, 7]` will get `[3, 7, 2]`.\n\n#### Getting Started\n\nHere is a basic sample code:\n\n```go\nfunc TestExample1(t *testing.T) {\n  timeZone, _ := time.LoadLocation(\"America/Phoenix\")\n  tm := time.Date(1999, 3, 13, 5, 57, 11, 1901, timeZone)\n  src := eventdeep.Employee2{\n    Base: eventdeep.Base{\n      Name:      \"Bob\",\n      Birthday:  \u0026tm,\n      Age:       24,\n      EmployeID: 7,\n    },\n    Avatar: \"https://tse4-mm.cn.bing.net/th/id/OIP-C.SAy__OKoxrIqrXWAb7Tj1wHaEC?pid=ImgDet\u0026rs=1\",\n    Image:  []byte{95, 27, 43, 66, 0, 21, 210},\n    Attr:   \u0026eventdeep.Attr{Attrs: []string{\"hello\", \"world\"}},\n    Valid:  true,\n  }\n  var dst eventdeep.User\n\n  // direct way but no error report: eventdeep.DeepCopy(src, \u0026dst)\n  c := eventdeep.New()\n  if err := c.CopyTo(src, \u0026dst); err != nil {\n    t.Fatal(err)\n  }\n  if !reflect.DeepEqual(dst, eventdeep.User{\n    Name:      \"Bob\",\n    Birthday:  \u0026tm,\n    Age:       24,\n    EmployeID: 7,\n    Avatar:    \"https://tse4-mm.cn.bing.net/th/id/OIP-C.SAy__OKoxrIqrXWAb7Tj1wHaEC?pid=ImgDet\u0026rs=1\",\n    Image:     []byte{95, 27, 43, 66, 0, 21, 210},\n    Attr:      \u0026eventdeep.Attr{Attrs: []string{\"hello\", \"world\"}},\n    Valid:     true,\n  }) {\n    t.Fatalf(\"bad, got %v\", dst)\n  }\n}\n```\n\n#### Customizing The Field Extractor\n\nFor the unconventional deep copy, we can copy field to field via a source extractor.\n\nYou need a target struct at first.\n\n```go\nfunc TestStructWithSourceExtractor(t *testing.T) {\n  c := context.WithValue(context.TODO(), \"Data\", map[string]typ.Any{\n    \"A\": 12,\n  })\n\n  tgt := struct {\n    A int\n  }{}\n\n  evendeep.DeepCopy(c, \u0026tgt, evendeep.WithSourceValueExtractor(func(name string) typ.Any {\n    if m, ok := c.Value(\"Data\").(map[string]typ.Any); ok {\n      return m[name]\n    }\n    return nil\n  }))\n\n  if tgt.A != 12 {\n    t.FailNow()\n  }\n}\n```\n\n#### Customizing The Target Setter\n\nAs a contrary, you might specify a setter to handle the setting action on copying struct and/or map.\n\n```go\nfunc TestStructWithTargetSetter(t *testing.T) {\n  type srcS struct {\n    A int\n    B bool\n    C string\n  }\n\n  src := \u0026srcS{\n    A: 5,\n    B: true,\n    C: \"helloString\",\n  }\n  tgt := map[string]typ.Any{\n    \"Z\": \"str\",\n  }\n\n  err := evendeep.New().CopyTo(src, \u0026tgt,\n    evendeep.WithTargetValueSetter(func(value *reflect.Value, sourceNames ...string) (err error) {\n      if value != nil {\n        name := \"Mo\" + strings.Join(sourceNames, \".\")\n        tgt[name] = value.Interface()\n      }\n      return // ErrShouldFallback to call the evendeep standard processing\n    }),\n  )\n\n  if err != nil || tgt[\"MoA\"] != 5 || tgt[\"MoB\"] != true || tgt[\"MoC\"] != \"helloString\" || tgt[\"Z\"] != \"str\" {\n    t.Errorf(\"err: %v, tgt: %v\", err, tgt)\n    t.FailNow()\n  }\n}\n```\n\nNOTE that the feature is only fit for copying on/between struct and/or map.\n\nIf you really wanna customize the setter for primitives or others, concern to implement a ValueCopier or ValueConverter.\n\n#### `ByOrdinal` or `ByName`\n\n`evendeep` enumerates fields in struct/map/slice with two strategies: `ByOrdinal` and `ByName`.\n\n1. Default `ByOrdinal` assumes the copier loops all source fields and copy them to the corresponding destination with\n   the ordinal order.\n2. `ByName` strategy assumes the copier loops all target fields, and try copying value from the coressponding source\n   field by its name.\n\nWhen a name conversion rule is defined in a struct field tag, the copier will look for the name and copy value to, even\nif it's in `ByOrdinal` mode.\n\n#### Customizing A Converter\n\nThe customized Type/Value Converter can be applied on transforming the data from source. For more information take a\nlook [`ValueConverter`](https://github.com/hedzr/evendeep/blob/master/cvts.go#L97)\nand [`ValueCopier`](https://github.com/hedzr/evendeep/blob/master/cvts.go#L103). Its take effects on checking the value\ntype of target or source, or both of them.\n\n```go\ntype MyType struct {\n  I int\n}\n\ntype MyTypeToStringConverter struct{}\n\n// Uncomment this line if you wanna implment a ValueCopier implementation too: \n// func (c *MyTypeToStringConverter) CopyTo(ctx *eventdeep.ValueConverterContext, source, target reflect.Value) (err error) { return }\n\nfunc (c *MyTypeToStringConverter) Transform(ctx *eventdeep.ValueConverterContext, source reflect.Value, targetType reflect.Type) (target reflect.Value, err error) {\n  if source.IsValid() \u0026\u0026 targetType.Kind() == reflect.String {\n    var str string\n    if str, err = eventdeep.FallbackToBuiltinStringMarshalling(source); err == nil {\n      target = reflect.ValueOf(str)\n    }\n  }\n  return\n}\n\nfunc (c *MyTypeToStringConverter) Match(params *eventdeep.Params, source, target reflect.Type) (ctx *eventdeep.ValueConverterContext, yes bool) {\n  sn, sp := source.Name(), source.PkgPath()\n  sk, tk := source.Kind(), target.Kind()\n  if yes = sk == reflect.Struct \u0026\u0026 tk == reflect.String \u0026\u0026\n    sn == \"MyType\" \u0026\u0026 sp == \"github.com/hedzr/eventdeep_test\"; yes {\n    ctx = \u0026eventdeep.ValueConverterContext{Params: params}\n  }\n  return\n}\n\nfunc TestExample2(t *testing.T) {\n  var myData = MyType{I: 9}\n  var dst string\n  eventdeep.DeepCopy(myData, \u0026dst, eventdeep.WithValueConverters(\u0026MyTypeToStringConverter{}))\n  if dst != `{\n  \"I\": 9\n}` {\n    t.Fatalf(\"bad, got %v\", dst)\n  }\n}\n```\n\nInstead of `WithValueConverters` / `WithValueCopiers` for each times invoking `New()`, you might register yours once by\ncalling `RegisterDefaultConverters` / `RegisterDefaultCopiers` into global registry.\n\n```go\n  // a stub call for coverage\n  eventdeep.RegisterDefaultCopiers()\n\n  var dst1 string\n  eventdeep.RegisterDefaultConverters(\u0026MyTypeToStringConverter{})\n  eventdeep.DeepCopy(myData, \u0026dst1)\n  if dst1 != `{\n  \"I\": 9\n}` {\n    t.Fatalf(\"bad, got %v\", dst)\n  }\n```\n\n#### Zero Target Fields If Equals To Source\n\nWhen we compare two Struct, the target one can be clear to zero except a field value is not equal to source field. This\nfeature can be used for your ORM codes: someone loads a record as a golang struct variable, and make some changes, and\ninvoking `eventdeep.DeepCopy(originRec, \u0026newRecord, eventdeep.WithORMDiffOpt)`, the changes will be kept in `newRecord`\nand the others unchanged fields be cleanup at last.\n\nThe codes are:\n\n```go\nfunc TestExample3(t *testing.T) {\n  timeZone, _ := time.LoadLocation(\"America/Phoenix\")\n  tm := time.Date(1999, 3, 13, 5, 57, 11, 1901, timeZone)\n  var originRec = eventdeep.User{ ... }\n  var newRecord eventdeep.User\n  var t0 = time.Unix(0, 0)\n  var expectRec = eventdeep.User{Name: \"Barbara\", Birthday: \u0026t0, Attr: \u0026eventdeep.Attr{}}\n\n  eventdeep.DeepCopy(originRec, \u0026newRecord)\n  t.Logf(\"newRecord: %v\", newRecord)\n\n  newRecord.Name = \"Barbara\"\n  eventdeep.DeepCopy(originRec, \u0026newRecord, eventdeep.WithORMDiffOpt)\n  ...\n  if !reflect.DeepEqual(newRecord, expectRec) {\n    t.Fatalf(\"bad, got %v | %v\", newRecord, newRecord.Birthday.Nanosecond())\n  }\n}\n```\n\n#### Keep The Target Value If Source Is Empty\n\nSometimes we would look for a do-not-modify copier, it'll keep the value of target fields while the corresponding source\nfield is empty (zero or nil). Use `eventdeep.WithOmitEmptyOpt` in the case.\n\n```go\nfunc TestExample4(t *testing.T) {\n  timeZone, _ := time.LoadLocation(\"America/Phoenix\")\n  tm := time.Date(1999, 3, 13, 5, 57, 11, 1901, timeZone)\n  var originRec = eventdeep.User{\n    Name:      \"Bob\",\n    Birthday:  \u0026tm,\n    Age:       24,\n    EmployeID: 7,\n    Avatar:    \"https://tse4-mm.cn.bing.net/th/id/OIP-C.SAy__OKoxrIqrXWAb7Tj1wHaEC?pid=ImgDet\u0026rs=1\",\n    Image:     []byte{95, 27, 43, 66, 0, 21, 210},\n    Attr:      \u0026eventdeep.Attr{Attrs: []string{\"hello\", \"world\"}},\n    Valid:     true,\n  }\n  var dstRecord eventdeep.User\n  var t0 = time.Unix(0, 0)\n  var emptyRecord = eventdeep.User{Name: \"Barbara\", Birthday: \u0026t0}\n  var expectRecord = eventdeep.User{Name: \"Barbara\", Birthday: \u0026t0,\n    Image: []byte{95, 27, 43, 66, 0, 21, 210},\n    Attr:  \u0026eventdeep.Attr{Attrs: []string{\"hello\", \"world\"}},\n    Valid: true,\n  }\n\n  // prepare a hard copy at first\n  eventdeep.DeepCopy(originRec, \u0026dstRecord)\n  t.Logf(\"dstRecord: %v\", dstRecord)\n\n  // now update dstRecord with the non-empty fields.\n  eventdeep.DeepCopy(emptyRecord, \u0026dstRecord, eventdeep.WithOmitEmptyOpt)\n  t.Logf(\"dstRecord: %v\", dstRecord)\n  if !reflect.DeepEqual(dstRecord, expectRecord) {\n    t.Fatalf(\"bad, got %v\\nexpect: %v\", dstRecord, expectRecord)\n  }\n}\n```\n\n#### String Marshalling\n\nWhile copying struct, map, slice, or other source to target string, the builtin `toStringConverter` will be launched.\nAnd the default logic includes marshaling the structural source to string, typically `json.Marshal`.\n\nThis marshaller can be customized: `RegisterStringMarshaller` and `WithStringMarshaller` enable it:\n\n```go\neventdeep.RegisterStringMarshaller(yaml.Marshal)\neventdeep.RegisterStringMarshaller(json.Marshal)\n```\n\nThe default marshaler is a wraper to `json.MarshalIndent`.\n\n#### Specify CopyMergeStrategy via struct Tag\n\nSample struct is (use `copy` as key):\n\n```go\ntype AFT struct {\n  flags     flags.Flags `copy:\",cleareq\"`\n  converter *ValueConverter\n  wouldbe   int `copy:\",must,keepneq,omitzero,mapmerge\"`\n  ignored1 int `copy:\"-\"`\n  ignored2 int `copy:\",-\"`\n}\n```\n\n##### Name conversions\n\n`copy` tag has form: `nameConversion[,strategies...]`. `nameConversion` gives a target field Name to define a name\nconversion strategy, or `-` to ignore the field.\n\n\u003e `nameConversion` has form:\n\u003e\n\u003e - `-`: field is ignored\n\u003e - `targetName`\n\u003e - `-\u003etargetName`\n\u003e - `sourceName-\u003etargetName`\n\u003e\n\u003e Spaces besides of `-\u003e` are allowed.\n\nCopier will check target field tag at first, and following by a source field tag checking.\n\nYou may specify converting rule at either target or source side, Copier assume the target one is prior.\n\n**NOTE**: `nameConversion` is fully functional only for `cms.ByName` mode. It get partial work in `cms.ByOrdinal` mode (\ndefault mode).\n\n*TODO*: In `cms.ByOrdinal` (`*`) mode, a name converter can be applied in copying field to field.\n\n##### Sample codes\n\nThe test gives a sample to show you how the name-conversion and member function work together:\n\n```go\nfunc TestStructWithNameConversions(t *testing.T) {\n  type srcS struct {\n    A int    `copy:\"A1\"`\n    B bool   `copy:\"B1,std\"`\n    C string `copy:\"C1,\"`\n  }\n\n  type dstS struct {\n    A1 int\n    B1 bool\n    C1 string\n  }\n\n  src := \u0026srcS{A: 6, B: true, C: \"hello\"}\n  var tgt = dstS{A1: 1}\n\n  // use ByName strategy,\n  err := evendeep.New().CopyTo(src, \u0026tgt, evendeep.WithByNameStrategyOpt)\n\n  if tgt.A1 != 6 || !tgt.B1 || tgt.C1 != \"hello\" || err != nil {\n    t.Fatalf(\"BAD COPY, tgt: %+v\", tgt)\n  }\n}\n```\n\n#### Strategy Names\n\nThe available tag names are (Almost newest, see its\nin [flags/cms/copymergestrategy.go](https://github.com/hedzr/evendeep/blob/master/flags/cms/copymergestrategy.go#L23)):\n\n| Tag name             | Flags                     | Detail                                           |\n| -------------------- | ------------------------- | ------------------------------------------------ |\n| `-`                | `cms.Ignore`            | field will be ignored                            |\n| `std` (*)          | `cms.Default`           | reserved                                         |\n| `must`             | `cms.Must`              | reserved                                         |\n| `cleareq`          | `cms.ClearIfEqual`      | set zero if target equal to source               |\n| `keepneq`          | `cms.KeepIfNotEq`       | don't copy source if target not equal to source  |\n| `clearinvalid`     | `cms.ClearIfInvalid`    | if target field is invalid, set to zero value    |\n| `noomit` (*)       | `cms.NoOmit`            |                                                  |\n| `omitempty`        | `cms.OmitIfEmpty`       | if source field is empty, keep destination value |\n| `omitnil`          | `cms.OmitIfNil`         |                                                  |\n| `omitzero`         | `cms.OmitIfZero`        |                                                  |\n| `noomittarget` (*) | `cms.NoOmitTarget`      |                                                  |\n| `omitemptytarget`  | `cms.OmitIfTargetEmpty` | if target field is empty, don't copy from source |\n| `omitniltarget`    | `cms.OmitIfTargetNil`   |                                                  |\n| `omitzerotarget`   | `cms.OmitIfTargetZero`  |                                                  |\n| `slicecopy`        | `cms.SliceCopy`         | copy elem by subscription                        |\n| `slicecopyappend`  | `cms.SliceCopyAppend`   | and append more                                  |\n| `slicemerge`       | `cms.SliceMerge`        | merge with order-insensitive                     |\n| `mapcopy`          | `cms.MapCopy`           | copy elem by key                                 |\n| `mapmerge`         | `cms.MapMerge`          | merge map deeply                                 |\n| ...                  |                           |                                                  |\n\n\u003e `*`: the flag is on by default.\n\n#### Notes About `DeepCopy()`\n\nMany settings are accumulated in multiple calling on `DeepCopy()`, such as `converters`, `ignoreNames`, and so on. The\nunderlying object is `DefaultCopyController`.\n\nTo get a fresh clean copier, `New()` or `NewFlatDeepCopier()` are the choices. BTW,\nsometimes `evendeep.ResetDefaultCopyController()` might be helpful.\n\nThe only exception is copy-n-merge strategies. There flags are saved and restored on each calling on `DeepCopy()`.\n\n#### Notes About Global Settings\n\nSome settings are global and available to both of `DeepCopy()` and `New().CopyTo()`, such as:\n\n1. `WithStringMarshaller` or `RegisterDefaultStringMarshaller()`\n2. `RegisterDefaultConverters`\n3. `RegisterDefaultCopiers`\n\nAnd so on.\n\n### deepdiff\n\n`DeepDiff` can deeply print the differences about two objects.\n\n```go\ndelta, equal := evendeep.DeepDiff([]int{3, 0, 9}, []int{9, 3, 0}, diff.WithSliceOrderedComparison(true))\nt.Logf(\"delta: %v\", delta) // \"\"\n\ndelta, equal := evendeep.DeepDiff([]int{3, 0}, []int{9, 3, 0}, diff.WithSliceOrderedComparison(true))\nt.Logf(\"delta: %v\", delta) // \"added: [0] = 9\\n\"\n\ndelta, equal := evendeep.DeepDiff([]int{3, 0}, []int{9, 3, 0})\nt.Logf(\"delta: %v\", delta)\n// Outputs:\n//   added: [2] = \u003czero\u003e\n//   modified: [0] = 9 (int) (Old: 3)\n//   modified: [1] = 3 (int) (Old: \u003czero\u003e)\n\n```\n\n`DeepDiff` is a rewrote version\nupon [d4l3k/messagediff]([d4l3k/messagediff at v1.2.1 (github.com)](https://github.com/d4l3k/messagediff)). This new\ncode enables user-defined comparer for you.\n\n#### Ignored Names\n\n[`diff.WithIgnoredFields(names...)`](https://github.com/hedzr/evendeep/blob/master/diff/diff.go#L41) can give a list of\nnames which should be ignored when comparing.\n\n#### Slice-Order Insensitive\n\nIn normal mode, `diff` is slice-order-sensitive, that means, `[1, 2] != [2, 1]`\n. [`WithSliceOrderedComparison(b bool)`](https://github.com/hedzr/evendeep/blob/master/diff/diff.go#L41) can unmind the\ndifferences of order and as an equal.\n\n#### Customizing Comparer\n\nFor example, `evendeep` ships a `timeComparer`:\n\n```go\ntype timeComparer struct{}\n\nfunc (c *timeComparer) Match(typ reflect.Type) bool {\n  return typ.String() == \"time.Time\"\n}\n\nfunc (c *timeComparer) Equal(ctx Context, lhs, rhs reflect.Value, path Path) (equal bool) {\n  aTime := lhs.Interface().(time.Time)\n  bTime := rhs.Interface().(time.Time)\n  if equal = aTime.Equal(bTime); !equal {\n    ctx.PutModified(ctx.PutPath(path), Update{Old: aTime.String(), New: bTime.String(), Typ: typfmtlite(\u0026lhs)})\n  }\n  return\n}\n```\n\nAnd it has been initialized into diff info struct. `timeComparer` provides a semantic comparing for `time.Time` objects.\n\nTo enable your comparer,\nuse [`diff.WithComparer(comparer)`](https://github.com/hedzr/evendeep/blob/master/diff/diff.go#L65).\n\n### deepequal\n\nOur `DeepEqual` is shortcut to `DeepDiff`:\n\n```go\nequal := evendeep.DeepEqual([]int{3, 0, 9}, []int{9, 3, 0}, diff.WithSliceOrderedComparison(true))\nif !equal {\n  t.Errorf(\"expecting equal = true but got false\")\n}\n```\n\nFor the unhandled types and objects, DeepEqual and DeepDiff will fallback to `reflect.DeepEqual()`. It's no need to\ncall `reflect.DeepEqual` explicitly.\n\n## Roadmap\n\nThese features had been planning but still on ice.\n\n- [X] Name converting and mapping for `cms.ByOrdinal` (`*`) mode: a universal `name converter` can be applied in copying\n  field to field.\n- [ ] *Use SourceExtractor and TargetSetter together (might be impossible)*\n- [ ] More builtin converters (*might not be a requisite*)\n- [X] Handle circular pointer (DONE)\n\nIssue me if you wanna put it or them on the table.\n\n## LICENSE\n\nUnder Apache 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhedzr%2Fevendeep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhedzr%2Fevendeep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhedzr%2Fevendeep/lists"}