evendeep

package module
v1.1.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 18, 2024 License: Apache-2.0 Imports: 28 Imported by: 3

README

even-deep

Go GitHub go.mod Go version GitHub tag (latest SemVer) go.dev Go Report Card <!-- [![codecov](https://codecov.io/gh/hedzr/evendeep/branch/master/graph/badge.svg)](https://codecov.io/gh/hedzr/evendeep) --> Coverage Status

This is a standard deepcopy library. It provides per-field copying deeply, and compares deeply abilities.

This library is designed for making everything customizable.

Features

  • loosely and reasonable data-types conversions, acrossing primitives, composites and functions, with customizable converters/transformers

  • unexported values (optional), ...

  • circular references immunization

  • fully customizable

    • user-defined value/type converters/transformers
    • user-defined field to field name converting rule via struct Tag
  • easily apply different strategies

    • basic strategies are: copy-n-merge, clone,
    • strategies per struct field: slicecopy, slicemerge, mapcopy, mapmerge, omitempty (keep if source is zero or nil), omitnil, omitzero, keepneq (keep if not equal), cleareq (clear if equal), ...
  • copy fields by name or ordinal

    • field to field
    • field to method, method to field
    • value to function (as input), function result to value
    • slice[0] to struct, struct to slice[0]
    • struct to map, map to struct
    • User-defined extractor/getter on various source
    • User-defined setter for struct or map target (if mapkey is string)
    • ...
  • The deep series

  • Compatibilities

    • Run for Go Modules and Generics enable, and log/slog present (go1.21+ since v1)
      • since v1, debug/buildinfo requires go1.18+, log/slog wants go1.21+.
      • for the v0.x versions, go1.11+ is okay.

History

  • v1.1.10

    • upgraded deps
    • fix go toolchain versions
  • v1.1.9

    • security patch
  • v1.1.8

    • improved errors.v3 - prevent attach itself into nested container
    • upgraded deps
  • v1.1.7

    • improved map -> struct, the lower-case map key names will be mapping to camel-case to match the corresponding export field names.
    • upgraded deps
  • More in CHANGELOG

Usages

deepcopy

eventdeep.New, eventdeep.MakeClone and eventdeep.DeepCopy are main entries.

By default, DeepCopy() will copy and merge source into destination object. That means, a map or a slice will be merged deeply, same to a struct.

New(opts...) gives a most even scalable interface than DeepCopy, it returns a new DeepCopier different to DefaultCopyController and you can make call to DeepCopier.DeepCopy(old, new, opts...).

In copy-n-merge mode, copying [2, 3] to [3, 7] will get [3, 7, 2].

Getting Started

Here is a basic sample code:

func TestExample1(t *testing.T) {
  timeZone, _ := time.LoadLocation("America/Phoenix")
  tm := time.Date(1999, 3, 13, 5, 57, 11, 1901, timeZone)
  src := eventdeep.Employee2{
    Base: eventdeep.Base{
      Name:      "Bob",
      Birthday:  &tm,
      Age:       24,
      EmployeID: 7,
    },
    Avatar: "https://tse4-mm.cn.bing.net/th/id/OIP-C.SAy__OKoxrIqrXWAb7Tj1wHaEC?pid=ImgDet&rs=1",
    Image:  []byte{95, 27, 43, 66, 0, 21, 210},
    Attr:   &eventdeep.Attr{Attrs: []string{"hello", "world"}},
    Valid:  true,
  }
  var dst eventdeep.User

  // direct way but no error report: eventdeep.DeepCopy(src, &dst)
  c := eventdeep.New()
  if err := c.CopyTo(src, &dst); err != nil {
    t.Fatal(err)
  }
  if !reflect.DeepEqual(dst, eventdeep.User{
    Name:      "Bob",
    Birthday:  &tm,
    Age:       24,
    EmployeID: 7,
    Avatar:    "https://tse4-mm.cn.bing.net/th/id/OIP-C.SAy__OKoxrIqrXWAb7Tj1wHaEC?pid=ImgDet&rs=1",
    Image:     []byte{95, 27, 43, 66, 0, 21, 210},
    Attr:      &eventdeep.Attr{Attrs: []string{"hello", "world"}},
    Valid:     true,
  }) {
    t.Fatalf("bad, got %v", dst)
  }
}
Customizing The Field Extractor

For the unconventional deep copy, we can copy field to field via a source extractor.

You need a target struct at first.

func TestStructWithSourceExtractor(t *testing.T) {
  c := context.WithValue(context.TODO(), "Data", map[string]typ.Any{
    "A": 12,
  })

  tgt := struct {
    A int
  }{}

  evendeep.DeepCopy(c, &tgt, evendeep.WithSourceValueExtractor(func(name string) typ.Any {
    if m, ok := c.Value("Data").(map[string]typ.Any); ok {
      return m[name]
    }
    return nil
  }))

  if tgt.A != 12 {
    t.FailNow()
  }
}
Customizing The Target Setter

As a contrary, you might specify a setter to handle the setting action on copying struct and/or map.

func TestStructWithTargetSetter(t *testing.T) {
  type srcS struct {
    A int
    B bool
    C string
  }

  src := &srcS{
    A: 5,
    B: true,
    C: "helloString",
  }
  tgt := map[string]typ.Any{
    "Z": "str",
  }

  err := evendeep.New().CopyTo(src, &tgt,
    evendeep.WithTargetValueSetter(func(value *reflect.Value, sourceNames ...string) (err error) {
      if value != nil {
        name := "Mo" + strings.Join(sourceNames, ".")
        tgt[name] = value.Interface()
      }
      return // ErrShouldFallback to call the evendeep standard processing
    }),
  )

  if err != nil || tgt["MoA"] != 5 || tgt["MoB"] != true || tgt["MoC"] != "helloString" || tgt["Z"] != "str" {
    t.Errorf("err: %v, tgt: %v", err, tgt)
    t.FailNow()
  }
}

NOTE that the feature is only fit for copying on/between struct and/or map.

If you really wanna customize the setter for primitives or others, concern to implement a ValueCopier or ValueConverter.

ByOrdinal or ByName

evendeep enumerates fields in struct/map/slice with two strategies: ByOrdinal and ByName.

  1. Default ByOrdinal assumes the copier loops all source fields and copy them to the corresponding destination with the ordinal order.
  2. ByName strategy assumes the copier loops all target fields, and try copying value from the coressponding source field by its name.

When a name conversion rule is defined in a struct field tag, the copier will look for the name and copy value to, even if it's in ByOrdinal mode.

Customizing A Converter

The customized Type/Value Converter can be applied on transforming the data from source. For more information take a look ValueConverter and ValueCopier. Its take effects on checking the value type of target or source, or both of them.

type MyType struct {
  I int
}

type MyTypeToStringConverter struct{}

// Uncomment this line if you wanna implment a ValueCopier implementation too: 
// func (c *MyTypeToStringConverter) CopyTo(ctx *eventdeep.ValueConverterContext, source, target reflect.Value) (err error) { return }

func (c *MyTypeToStringConverter) Transform(ctx *eventdeep.ValueConverterContext, source reflect.Value, targetType reflect.Type) (target reflect.Value, err error) {
  if source.IsValid() && targetType.Kind() == reflect.String {
    var str string
    if str, err = eventdeep.FallbackToBuiltinStringMarshalling(source); err == nil {
      target = reflect.ValueOf(str)
    }
  }
  return
}

func (c *MyTypeToStringConverter) Match(params *eventdeep.Params, source, target reflect.Type) (ctx *eventdeep.ValueConverterContext, yes bool) {
  sn, sp := source.Name(), source.PkgPath()
  sk, tk := source.Kind(), target.Kind()
  if yes = sk == reflect.Struct && tk == reflect.String &&
    sn == "MyType" && sp == "github.com/hedzr/eventdeep_test"; yes {
    ctx = &eventdeep.ValueConverterContext{Params: params}
  }
  return
}

func TestExample2(t *testing.T) {
  var myData = MyType{I: 9}
  var dst string
  eventdeep.DeepCopy(myData, &dst, eventdeep.WithValueConverters(&MyTypeToStringConverter{}))
  if dst != `{
  "I": 9
}` {
    t.Fatalf("bad, got %v", dst)
  }
}

Instead of WithValueConverters / WithValueCopiers for each times invoking New(), you might register yours once by calling RegisterDefaultConverters / RegisterDefaultCopiers into global registry.

  // a stub call for coverage
  eventdeep.RegisterDefaultCopiers()

  var dst1 string
  eventdeep.RegisterDefaultConverters(&MyTypeToStringConverter{})
  eventdeep.DeepCopy(myData, &dst1)
  if dst1 != `{
  "I": 9
}` {
    t.Fatalf("bad, got %v", dst)
  }
Zero Target Fields If Equals To Source

When we compare two Struct, the target one can be clear to zero except a field value is not equal to source field. This feature can be used for your ORM codes: someone loads a record as a golang struct variable, and make some changes, and invoking eventdeep.DeepCopy(originRec, &newRecord, eventdeep.WithORMDiffOpt), the changes will be kept in newRecord and the others unchanged fields be cleanup at last.

The codes are:

func TestExample3(t *testing.T) {
  timeZone, _ := time.LoadLocation("America/Phoenix")
  tm := time.Date(1999, 3, 13, 5, 57, 11, 1901, timeZone)
  var originRec = eventdeep.User{ ... }
  var newRecord eventdeep.User
  var t0 = time.Unix(0, 0)
  var expectRec = eventdeep.User{Name: "Barbara", Birthday: &t0, Attr: &eventdeep.Attr{}}

  eventdeep.DeepCopy(originRec, &newRecord)
  t.Logf("newRecord: %v", newRecord)

  newRecord.Name = "Barbara"
  eventdeep.DeepCopy(originRec, &newRecord, eventdeep.WithORMDiffOpt)
  ...
  if !reflect.DeepEqual(newRecord, expectRec) {
    t.Fatalf("bad, got %v | %v", newRecord, newRecord.Birthday.Nanosecond())
  }
}
Keep The Target Value If Source Is Empty

Sometimes we would look for a do-not-modify copier, it'll keep the value of target fields while the corresponding source field is empty (zero or nil). Use eventdeep.WithOmitEmptyOpt in the case.

func TestExample4(t *testing.T) {
  timeZone, _ := time.LoadLocation("America/Phoenix")
  tm := time.Date(1999, 3, 13, 5, 57, 11, 1901, timeZone)
  var originRec = eventdeep.User{
    Name:      "Bob",
    Birthday:  &tm,
    Age:       24,
    EmployeID: 7,
    Avatar:    "https://tse4-mm.cn.bing.net/th/id/OIP-C.SAy__OKoxrIqrXWAb7Tj1wHaEC?pid=ImgDet&rs=1",
    Image:     []byte{95, 27, 43, 66, 0, 21, 210},
    Attr:      &eventdeep.Attr{Attrs: []string{"hello", "world"}},
    Valid:     true,
  }
  var dstRecord eventdeep.User
  var t0 = time.Unix(0, 0)
  var emptyRecord = eventdeep.User{Name: "Barbara", Birthday: &t0}
  var expectRecord = eventdeep.User{Name: "Barbara", Birthday: &t0,
    Image: []byte{95, 27, 43, 66, 0, 21, 210},
    Attr:  &eventdeep.Attr{Attrs: []string{"hello", "world"}},
    Valid: true,
  }

  // prepare a hard copy at first
  eventdeep.DeepCopy(originRec, &dstRecord)
  t.Logf("dstRecord: %v", dstRecord)

  // now update dstRecord with the non-empty fields.
  eventdeep.DeepCopy(emptyRecord, &dstRecord, eventdeep.WithOmitEmptyOpt)
  t.Logf("dstRecord: %v", dstRecord)
  if !reflect.DeepEqual(dstRecord, expectRecord) {
    t.Fatalf("bad, got %v\nexpect: %v", dstRecord, expectRecord)
  }
}
String Marshalling

While copying struct, map, slice, or other source to target string, the builtin toStringConverter will be launched. And the default logic includes marshaling the structural source to string, typically json.Marshal.

This marshaller can be customized: RegisterStringMarshaller and WithStringMarshaller enable it:

eventdeep.RegisterStringMarshaller(yaml.Marshal)
eventdeep.RegisterStringMarshaller(json.Marshal)

The default marshaler is a wraper to json.MarshalIndent.

Specify CopyMergeStrategy via struct Tag

Sample struct is (use copy as key):

type AFT struct {
  flags     flags.Flags `copy:",cleareq"`
  converter *ValueConverter
  wouldbe   int `copy:",must,keepneq,omitzero,mapmerge"`
  ignored1 int `copy:"-"`
  ignored2 int `copy:",-"`
}
Name conversions

copy tag has form: nameConversion[,strategies...]. nameConversion gives a target field Name to define a name conversion strategy, or - to ignore the field.

nameConversion has form:

  • -: field is ignored
  • targetName
  • ->targetName
  • sourceName->targetName

Spaces besides of -> are allowed.

Copier will check target field tag at first, and following by a source field tag checking.

You may specify converting rule at either target or source side, Copier assume the target one is prior.

NOTE: nameConversion is fully functional only for cms.ByName mode. It get partial work in cms.ByOrdinal mode ( default mode).

TODO: In cms.ByOrdinal (*) mode, a name converter can be applied in copying field to field.

Sample codes

The test gives a sample to show you how the name-conversion and member function work together:

func TestStructWithNameConversions(t *testing.T) {
  type srcS struct {
    A int    `copy:"A1"`
    B bool   `copy:"B1,std"`
    C string `copy:"C1,"`
  }

  type dstS struct {
    A1 int
    B1 bool
    C1 string
  }

  src := &srcS{A: 6, B: true, C: "hello"}
  var tgt = dstS{A1: 1}

  // use ByName strategy,
  err := evendeep.New().CopyTo(src, &tgt, evendeep.WithByNameStrategyOpt)

  if tgt.A1 != 6 || !tgt.B1 || tgt.C1 != "hello" || err != nil {
    t.Fatalf("BAD COPY, tgt: %+v", tgt)
  }
}
Strategy Names

The available tag names are (Almost newest, see its in flags/cms/copymergestrategy.go):

Tag name Flags Detail
- cms.Ignore field will be ignored
std (*) cms.Default reserved
must cms.Must reserved
cleareq cms.ClearIfEqual set zero if target equal to source
keepneq cms.KeepIfNotEq don't copy source if target not equal to source
clearinvalid cms.ClearIfInvalid if target field is invalid, set to zero value
noomit (*) cms.NoOmit
omitempty cms.OmitIfEmpty if source field is empty, keep destination value
omitnil cms.OmitIfNil
omitzero cms.OmitIfZero
noomittarget (*) cms.NoOmitTarget
omitemptytarget cms.OmitIfTargetEmpty if target field is empty, don't copy from source
omitniltarget cms.OmitIfTargetNil
omitzerotarget cms.OmitIfTargetZero
slicecopy cms.SliceCopy copy elem by subscription
slicecopyappend cms.SliceCopyAppend and append more
slicemerge cms.SliceMerge merge with order-insensitive
mapcopy cms.MapCopy copy elem by key
mapmerge cms.MapMerge merge map deeply
...

*: the flag is on by default.

Notes About DeepCopy()

Many settings are accumulated in multiple calling on DeepCopy(), such as converters, ignoreNames, and so on. The underlying object is DefaultCopyController.

To get a fresh clean copier, New() or NewFlatDeepCopier() are the choices. BTW, sometimes evendeep.ResetDefaultCopyController() might be helpful.

The only exception is copy-n-merge strategies. There flags are saved and restored on each calling on DeepCopy().

Notes About Global Settings

Some settings are global and available to both of DeepCopy() and New().CopyTo(), such as:

  1. WithStringMarshaller or RegisterDefaultStringMarshaller()
  2. RegisterDefaultConverters
  3. RegisterDefaultCopiers

And so on.

deepdiff

DeepDiff can deeply print the differences about two objects.

delta, equal := evendeep.DeepDiff([]int{3, 0, 9}, []int{9, 3, 0}, diff.WithSliceOrderedComparison(true))
t.Logf("delta: %v", delta) // ""

delta, equal := evendeep.DeepDiff([]int{3, 0}, []int{9, 3, 0}, diff.WithSliceOrderedComparison(true))
t.Logf("delta: %v", delta) // "added: [0] = 9\n"

delta, equal := evendeep.DeepDiff([]int{3, 0}, []int{9, 3, 0})
t.Logf("delta: %v", delta)
// Outputs:
//   added: [2] = <zero>
//   modified: [0] = 9 (int) (Old: 3)
//   modified: [1] = 3 (int) (Old: <zero>)

DeepDiff is a rewrote version upon [d4l3k/messagediff](d4l3k/messagediff at v1.2.1 (github.com)). This new code enables user-defined comparer for you.

Ignored Names

diff.WithIgnoredFields(names...) can give a list of names which should be ignored when comparing.

Slice-Order Insensitive

In normal mode, diff is slice-order-sensitive, that means, [1, 2] != [2, 1] . WithSliceOrderedComparison(b bool) can unmind the differences of order and as an equal.

Customizing Comparer

For example, evendeep ships a timeComparer:

type timeComparer struct{}

func (c *timeComparer) Match(typ reflect.Type) bool {
  return typ.String() == "time.Time"
}

func (c *timeComparer) Equal(ctx Context, lhs, rhs reflect.Value, path Path) (equal bool) {
  aTime := lhs.Interface().(time.Time)
  bTime := rhs.Interface().(time.Time)
  if equal = aTime.Equal(bTime); !equal {
    ctx.PutModified(ctx.PutPath(path), Update{Old: aTime.String(), New: bTime.String(), Typ: typfmtlite(&lhs)})
  }
  return
}

And it has been initialized into diff info struct. timeComparer provides a semantic comparing for time.Time objects.

To enable your comparer, use diff.WithComparer(comparer).

deepequal

Our DeepEqual is shortcut to DeepDiff:

equal := evendeep.DeepEqual([]int{3, 0, 9}, []int{9, 3, 0}, diff.WithSliceOrderedComparison(true))
if !equal {
  t.Errorf("expecting equal = true but got false")
}

For the unhandled types and objects, DeepEqual and DeepDiff will fallback to reflect.DeepEqual(). It's no need to call reflect.DeepEqual explicitly.

Roadmap

These features had been planning but still on ice.

  • Name converting and mapping for cms.ByOrdinal (*) mode: a universal name converter can be applied in copying field to field.
  • Use SourceExtractor and TargetSetter together (might be impossible)
  • More builtin converters (might not be a requisite)
  • Handle circular pointer (DONE)

Issue me if you wanna put it or them on the table.

LICENSE

Under Apache 2.0.

Documentation

Index

Constants

View Source
const (
	// Version string for hedzr/evendeep package
	Version = "1.1.10"
)

Variables

View Source
var (
	// ErrUnknownState error.
	ErrUnknownState = errors.New("unknown state, cannot copy to")

	// ErrCannotSet error.
	ErrCannotSet = errors.New("cannot set: %v (%v) -> %v (%v)")

	// ErrCannotCopy error.
	ErrCannotCopy = errors.New("cannot copy: %v (%v) -> %v (%v)")

	// ErrCannotConvertTo error.
	ErrCannotConvertTo = errors.New("cannot convert/set: %v (%v) -> %v (%v)")

	// ErrShouldFallback tells the caller please continue its
	// internal process.
	// The error would be used in your callback function. For
	// instance, you could return it in a target-setter (see
	// also WithTargetValueSetter()) to ask the Copier do a
	// standard processing, typically that will set the field
	// with reflection.
	ErrShouldFallback = errors.New("fallback to evendeep internals")
)
View Source
var (
	// DefaultCopyController provides standard deepcopy feature.
	// copy and merge slice or map to an existed target.
	DefaultCopyController *cpController // by newDeepCopier()

)
View Source
var WithAutoExpandStructOpt = WithAutoExpandForInnerStruct(true) //nolint:gochecknoglobals //i know that

WithAutoExpandStructOpt is synonym of WithAutoExpandForInnerStruct(true).

View Source
var WithAutoNewForStructFieldOpt = WithAutoNewForStructField(true) //nolint:gochecknoglobals //i know that

WithAutoNewForStructFieldOpt is synonym of WithAutoNewForStructField(true).

View Source
var WithByNameStrategyOpt = WithCleanStrategies(cms.ByName) //nolint:gochecknoglobals //i know that

WithByNameStrategyOpt is synonym of cms.ByName by calling WithCleanStrategies.

If you're using WithByNameStrategyOpt and WithStrategies(...) at same time, please notes it will clear any existent flags before setting.

View Source
var WithByOrdinalStrategyOpt = WithCleanStrategies(cms.ByOrdinal) //nolint:gochecknoglobals //i know that

WithByOrdinalStrategyOpt is synonym of cms.ByOrdinal by calling WithCleanStrategies.

If you're using WithByOrdinalStrategyOpt and WithStrategies(...) at same time, please notes it will clear any existent flags before setting.

View Source
var WithCopyFunctionResultToTargetOpt = WithCopyFunctionResultToTarget(true) //nolint:gochecknoglobals //i know that

WithCopyFunctionResultToTargetOpt is shortcut of WithCopyFunctionResultToTarget.

View Source
var WithCopyStrategyOpt = WithCleanStrategies(cms.SliceCopy, cms.MapCopy) //nolint:gochecknoglobals //i know that

WithCopyStrategyOpt is synonym of cms.SliceCopy + cms.MapCopy by calling WithCleanStrategies.

If you're using WithCopyStrategyOpt and WithStrategies(...) at same time, please notes it will clear any existent flags before setting.

View Source
var WithCopyUnexportedFieldOpt = WithCopyUnexportedField(true) //nolint:gochecknoglobals //i know that

WithCopyUnexportedFieldOpt is shortcut of WithCopyUnexportedField.

View Source
var WithMergeStrategyOpt = WithCleanStrategies(cms.SliceMerge, cms.MapMerge) //nolint:gochecknoglobals //i know that

WithMergeStrategyOpt is synonym of cms.SliceMerge + cms.MapMerge by calling WithCleanStrategies.

If you're using WithMergeStrategyOpt and WithStrategies(...) at same time, please notes it will clear any existent flags before setting.

View Source
var WithORMDiffOpt = WithCleanStrategies(cms.ClearIfEq, cms.KeepIfNotEq, cms.ClearIfInvalid) //nolint:gochecknoglobals,lll //i know that

WithORMDiffOpt is synonym of cms.ClearIfEq + cms.KeepIfNotEq + cms.ClearIfInvalid by calling WithCleanStrategies.

If you're using WithORMDiffOpt and WithStrategies(...) at same time, please notes it will clear any existent flags before setting.

View Source
var WithOmitEmptyOpt = WithCleanStrategies(cms.OmitIfEmpty) //nolint:gochecknoglobals //i know that

WithOmitEmptyOpt is synonym of cms.OmitIfEmpty by calling Clean.

If you're using WithOmitEmptyOpt and WithStrategies(...) at same time, please notes it will clear any existent flags before setting.

View Source
var WithPassSourceToTargetFunctionOpt = WithPassSourceToTargetFunction(true) //nolint:gochecknoglobals //i know that

WithPassSourceToTargetFunctionOpt is shortcut of WithPassSourceToTargetFunction.

View Source
var WithSyncAdvancingOpt = WithSyncAdvancing(true) //nolint:gochecknoglobals //i know that

WithSyncAdvancingOpt is synonym of WithAutoExpandForInnerStruct(true).

View Source
var WithTryApplyConverterAtFirstOpt = WithTryApplyConverterAtFirst(true) //nolint:gochecknoglobals //i know that

WithTryApplyConverterAtFirstOpt is shortcut of WithTryApplyConverterAtFirst(true).

View Source
var WithWipeTargetSliceFirstOpt = WithWipeTargetSliceFirst(true) //nolint:gochecknoglobals //i know that

WithWipeTargetSliceFirstOpt is synonym of WithWipeTargetSliceFirst(true).

Functions

func Copy

func Copy(fromObj, toObj interface{}, opts ...Opt) (result interface{})

Copy is a synonym of DeepCopy.

DeepCopy makes a deep clone of a source object or merges it into the target.

func DeepCopy

func DeepCopy(fromObj, toObj interface{}, opts ...Opt) (result interface{})

DeepCopy makes a deep clone of a source object or merges it into the target.

func DeepDiff

func DeepDiff(a, b typ.Any, opts ...diff.Opt) (delta diff.Diff, equal bool)

DeepDiff compares a and b deeply inside.

delta, equal := evendeep.DeepDiff(a, b)
fmt.Println(delta)
fmt.Println(delta.PrettyPrint())

func DeepEqual

func DeepEqual(a, b typ.Any, opts ...diff.Opt) (equal bool)

DeepEqual compares a and b deeply inside.

equal := evendeep.DeepEqual(a, b)
fmt.Println(equal)

func FallbackToBuiltinStringMarshalling

func FallbackToBuiltinStringMarshalling(source reflect.Value) (str string, err error)

FallbackToBuiltinStringMarshalling exposes the builtin string marshaling mechanism for your customized ValueConverter or ValueCopier.

func MakeClone

func MakeClone(fromObj interface{}) (result interface{})

MakeClone makes a deep clone of a source object.

func RegisterDefaultConverters

func RegisterDefaultConverters(ss ...ValueConverter)

RegisterDefaultConverters registers the ValueConverter list into default converters registry.

It takes effects on DefaultCopyController, MakeClone, DeepCopy, and New, ....

func RegisterDefaultCopiers

func RegisterDefaultCopiers(ss ...ValueCopier)

RegisterDefaultCopiers registers the ValueCopier list into default copiers registry.

It takes effects on DefaultCopyController, MakeClone, DeepCopy, and New, ....

func RegisterDefaultStringMarshaller

func RegisterDefaultStringMarshaller(m TextMarshaller)

RegisterDefaultStringMarshaller provides a string marshaller which will be applied when a map is going to be copied to string.

Default is json marshaller (json.MarshalIndent).

If encoding.TextMarshaler/json.Marshaler have been implemented, the source.MarshalText/MarshalJSON() will be applied.

It's synonym of WithStringMarshaller.

func ResetDefaultCopyController

func ResetDefaultCopyController()

ResetDefaultCopyController discards the changes for DefaultCopyController and more.

func TimestampFromFloat64 added in v1.1.1

func TimestampFromFloat64(ts float64) time.Time

TimestampFromFloat64 returns a Timestamp equal to the given float64, assuming it too is an unix timestamp.

The float64 is interpreted as number of seconds, with everything after the decimal indicating milliseconds, microseconds, and nanoseconds

Types

type BoolSlice added in v1.0.0

type BoolSlice[T bool] []T

type Cloneable

type Cloneable interface {
	// Clone return a pointer to copy of source object.
	// But you can return the copy itself with your will.
	Clone() interface{} //nolint:revive
}

Cloneable interface represents a cloneable object that supports Clone() method.

The native Clone algorithm of a Cloneable object can be adapted into DeepCopier.

type ComplexSlice added in v1.0.0

type ComplexSlice[T Complexes] []T

type Complexes added in v1.0.0

type Complexes interface {
	complex64 | complex128
}

type Cvt added in v1.0.0

type Cvt struct{}

func (*Cvt) Bool added in v1.0.0

func (s *Cvt) Bool(data any) bool

func (*Cvt) BoolMap added in v1.0.0

func (s *Cvt) BoolMap(data any) map[string]bool

func (*Cvt) BoolSlice added in v1.0.0

func (s *Cvt) BoolSlice(data any) []bool

func (*Cvt) Complex128 added in v1.0.0

func (s *Cvt) Complex128(data any) complex128

func (*Cvt) Complex128Map added in v1.0.0

func (s *Cvt) Complex128Map(data any) map[string]complex128

func (*Cvt) Complex128Slice added in v1.0.0

func (s *Cvt) Complex128Slice(data any) []complex128

func (*Cvt) Complex64 added in v1.0.0

func (s *Cvt) Complex64(data any) complex64

func (*Cvt) Complex64Map added in v1.0.0

func (s *Cvt) Complex64Map(data any) map[string]complex64

func (*Cvt) Complex64Slice added in v1.0.0

func (s *Cvt) Complex64Slice(data any) []complex64

func (*Cvt) Duration added in v1.0.0

func (s *Cvt) Duration(data any) time.Duration

func (*Cvt) DurationMap added in v1.0.0

func (s *Cvt) DurationMap(data any) map[string]time.Duration

func (*Cvt) DurationSlice added in v1.0.0

func (s *Cvt) DurationSlice(data any) []time.Duration

func (*Cvt) Float32 added in v1.0.0

func (s *Cvt) Float32(data any) float32

func (*Cvt) Float32Map added in v1.0.0

func (s *Cvt) Float32Map(data any) map[string]float32

func (*Cvt) Float32Slice added in v1.0.0

func (s *Cvt) Float32Slice(data any) []float32

func (*Cvt) Float64 added in v1.0.0

func (s *Cvt) Float64(data any) float64

func (*Cvt) Float64Map added in v1.0.0

func (s *Cvt) Float64Map(data any) map[string]float64

func (*Cvt) Float64Slice added in v1.0.0

func (s *Cvt) Float64Slice(data any) []float64

func (*Cvt) Int added in v1.0.0

func (s *Cvt) Int(data any) int64

func (*Cvt) Int16Map added in v1.0.0

func (s *Cvt) Int16Map(data any) map[string]int16

func (*Cvt) Int16Slice added in v1.0.0

func (s *Cvt) Int16Slice(data any) []int16

func (*Cvt) Int32Map added in v1.0.0

func (s *Cvt) Int32Map(data any) map[string]int32

func (*Cvt) Int32Slice added in v1.0.0

func (s *Cvt) Int32Slice(data any) []int32

func (*Cvt) Int64Map added in v1.0.0

func (s *Cvt) Int64Map(data any) map[string]int64

func (*Cvt) Int64Slice added in v1.0.0

func (s *Cvt) Int64Slice(data any) []int64

func (*Cvt) Int8Map added in v1.0.0

func (s *Cvt) Int8Map(data any) map[string]int8

func (*Cvt) Int8Slice added in v1.0.0

func (s *Cvt) Int8Slice(data any) []int8

func (*Cvt) IntMap added in v1.0.0

func (s *Cvt) IntMap(data any) map[string]int

func (*Cvt) IntSlice added in v1.0.0

func (s *Cvt) IntSlice(data any) []int

func (*Cvt) String added in v1.0.0

func (s *Cvt) String(data any) string

func (*Cvt) StringMap added in v1.0.0

func (s *Cvt) StringMap(data any) map[string]string

func (*Cvt) StringSlice added in v1.0.0

func (s *Cvt) StringSlice(data any) []string

func (*Cvt) Time added in v1.0.0

func (s *Cvt) Time(data any) time.Time

func (*Cvt) TimeMap added in v1.0.0

func (s *Cvt) TimeMap(data any) map[string]time.Time

func (*Cvt) TimeSlice added in v1.0.0

func (s *Cvt) TimeSlice(data any) []time.Time

func (*Cvt) Uint added in v1.0.0

func (s *Cvt) Uint(data any) uint64

func (*Cvt) Uint16Map added in v1.0.0

func (s *Cvt) Uint16Map(data any) map[string]uint16

func (*Cvt) Uint16Slice added in v1.0.0

func (s *Cvt) Uint16Slice(data any) []uint16

func (*Cvt) Uint32Map added in v1.0.0

func (s *Cvt) Uint32Map(data any) map[string]uint32

func (*Cvt) Uint32Slice added in v1.0.0

func (s *Cvt) Uint32Slice(data any) []uint32

func (*Cvt) Uint64Map added in v1.0.0

func (s *Cvt) Uint64Map(data any) map[string]uint64

func (*Cvt) Uint64Slice added in v1.0.0

func (s *Cvt) Uint64Slice(data any) []uint64

func (*Cvt) Uint8Map added in v1.0.0

func (s *Cvt) Uint8Map(data any) map[string]uint8

func (*Cvt) Uint8Slice added in v1.0.0

func (s *Cvt) Uint8Slice(data any) []uint8

func (*Cvt) UintMap added in v1.0.0

func (s *Cvt) UintMap(data any) map[string]uint

func (*Cvt) UintSlice added in v1.0.0

func (s *Cvt) UintSlice(data any) []uint

type CvtV added in v1.0.0

type CvtV struct {
	Data any
}

func (*CvtV) String added in v1.0.0

func (s *CvtV) String() string

type DeepCopier

type DeepCopier interface {
	// CopyTo function.
	CopyTo(fromObj, toObj interface{}, opts ...Opt) (err error) //nolint:revive
}

DeepCopier interface.

func New

func New(opts ...Opt) DeepCopier

New gets a new instance of DeepCopier (the underlying is *cpController) different with DefaultCopyController.

Use New:

src, tgt := 123, 0
err = evendeep.New().CopyTo(src, &tgt)

Use package functions (With-opts might cumulate):

evendeep.Copy(src, &tgt) // or synonym: evendeep.DeepCopy(src, &tgt)
tgt = evendeep.MakeClone(src)

Use DefaultCopyController (With-opts might cumulate):

evendeep.DefaultCopyController.CopyTo(src, &tgt)

The most conventional way is:

err := evendeep.New().CopyTo(src, &tgt)

func NewFlatDeepCopier

func NewFlatDeepCopier(opts ...Opt) DeepCopier

NewFlatDeepCopier gets a new instance of DeepCopier (the underlying is *cpController) like NewDeepCopier but no merge strategies (SliceMerge and MapMerge).

type DeepCopyable

type DeepCopyable interface {
	DeepCopy() interface{} //nolint:revive
}

DeepCopyable interface represents a cloneable object that supports DeepCopy() method.

The native DeepCopy algorithm of a DeepCopyable object can be adapted into DeepCopier.

type FloatSlice added in v1.0.0

type FloatSlice[T Floats] []T

type Floats added in v1.0.0

type Floats interface {
	float32 | float64
}

type IntSlice added in v1.0.0

type IntSlice[T Integers] []T

type Integers added in v1.0.0

type Integers interface {
	int | int8 | int16 | int32 | int64
}

type NameConverter

type NameConverter interface {
	ToGoName(ctx *NameConverterContext, fieldName string) (goName string)
	ToFieldName(ctx *NameConverterContext, goName string) (fieldName string)
}

NameConverter for internal used.

type NameConverterContext

type NameConverterContext struct {
	*Params
}

NameConverterContext for internal used.

type NameConverters

type NameConverters []NameConverter

NameConverters for internal used.

type Numerics added in v1.0.0

type Numerics interface {
	Integers | Uintegers | Floats | Complexes
}

type Opt

type Opt func(c *cpController)

Opt options functor.

func WithAutoExpandForInnerStruct

func WithAutoExpandForInnerStruct(autoExpand bool) Opt

WithAutoExpandForInnerStruct does copy fields with flat struct. When autoExpandForInnerStruct is enabled, the iterator will go into any embedded struct and traverse its fields with a flatten mode.

For instance, the iteration on struct:

type A struct {
   F1 string
   F2 int
}
type B struct {
   F1 bool
   F2 A
   F3 float32
}

will produce the sequences:

B.F1, B.F2, B.F2 - A.F1, B.F2 - A.F2, B.F3

Default is true.

func WithAutoNewForStructField

func WithAutoNewForStructField(autoNew bool) Opt

WithAutoNewForStructField does create new instance on ptr field of a struct.

When cloning to a new target object, it might be helpful.

Default is true.

func WithCleanStrategies added in v0.2.51

func WithCleanStrategies(flagsList ...cms.CopyMergeStrategy) Opt

WithCleanStrategies set the given flags into *cpController, older flags will be clear at first.

func WithCloneStyle

func WithCloneStyle() Opt

WithCloneStyle sets the cpController to clone mode. In this mode, source object will be cloned to a new object and returned as new target object.

func WithCopyFunctionResultToTarget

func WithCopyFunctionResultToTarget(b bool) Opt

WithCopyFunctionResultToTarget invoke source function member and pass the result to the responsible target field.

It just works when target field is acceptable.

Default is true.

func WithCopyStyle

func WithCopyStyle() Opt

WithCopyStyle sets the cpController to copier mode. In this mode, source object will be deepcopied to target object.

func WithCopyUnexportedField

func WithCopyUnexportedField(b bool) Opt

WithCopyUnexportedField try to copy the unexported fields with special way.

This feature needs unsafe package present.

Default is true.

func WithIgnoreNames

func WithIgnoreNames(names ...string) Opt

WithIgnoreNames does specify the ignored field names list.

Use the filename wildcard match characters (aka. '*' and '?', and '**') as your advantages, the algor is isWildMatch() and dir.IsWildMatch.

These patterns will only be tested on struct fields.

func WithIgnoreNamesReset

func WithIgnoreNamesReset() Opt

WithIgnoreNamesReset clear the ignored name list set.

func WithPassSourceToTargetFunction

func WithPassSourceToTargetFunction(b bool) Opt

WithPassSourceToTargetFunction invoke target function member and pass the source as its input parameters.

Default is true.

func WithSourceValueExtractor

func WithSourceValueExtractor(e SourceValueExtractor) Opt

WithSourceValueExtractor specify a source field value extractor, which will be applied on each field being copied to target.

Just work for non-nested struct.

For instance:

c := context.WithValue(context.TODO(), "Data", map[string]typ.Any{
	"A": 12,
})

tgt := struct {
	A int
}{}

evendeep.DeepCopy(c, &tgt,
  evendeep.WithSourceValueExtractor(func(targetName string) typ.Any {
	if m, ok := c.Value("Data").(map[string]typ.Any); ok {
		return m[targetName]
	}
	return nil
}))

if tgt.A != 12 {
	t.FailNow()
}

func WithStrategies

func WithStrategies(flagsList ...cms.CopyMergeStrategy) Opt

WithStrategies appends more flags into *cpController.

For example:

WithStrategies(cms.OmitIfZero, cms.OmitIfNil, cms.OmitIfEmpty, cms.NoOmit)
WithStrategies(cms.ClearIfMissed, cms.ClearIfInvalid)
WithStrategies(cms.KeepIfNotEq, cms.ClearIfEq)

func WithStrategiesReset

func WithStrategiesReset(flagsList ...cms.CopyMergeStrategy) Opt

WithStrategiesReset clears the exists flags in a *cpController. So that you can append new ones (with WithStrategies(flags...)).

In generally, WithStrategiesReset is synonym of cms.SliceCopy + cms.MapCopy, since all strategies are cleared. A nothing Flags means that a set of default strategies will be applied, in other words, its include:

cms.Default, cms.NoOmit, cms.NoOmitTarget,
cms.SliceCopy, cms.MapCopy,
cms.ByOrdinal,

If a flagsList supplied, WithStrategiesReset will add them and set the state to false.

func WithStringMarshaller

func WithStringMarshaller(m TextMarshaller) Opt

WithStringMarshaller provides a string marshaller which will be applied when a map is going to be copied to string.

Default is json marshaller.

If BinaryMarshaler has been implemented, the source.Marshal() will be applied.

It's synonym of RegisterDefaultStringMarshaller.

func WithStructTagName

func WithStructTagName(name string) Opt

WithStructTagName set the name which is used for retrieve the struct tag pieces.

Default is "copy", the corresponding struct with tag looks like:

type AFT struct {
    flags     flags.Flags `copy:",cleareq"`
    converter *ValueConverter
    wouldbe   int `copy:",must,keepneq,omitzero,mapmerge"`
    ignored1  int `copy:"-"`
    ignored2  int `copy:",-"`
}

func WithSyncAdvancing

func WithSyncAdvancing(syncAdvancing bool) Opt

WithSyncAdvancing decides how to advance to next field especially a source field had been ignored. By default, (false), the target field won't be advanced while the source field had been ignored. For sync-advanced flag is true, the target field step to next.

Just for cms.ByOrdinal mode.

func WithTargetValueSetter

func WithTargetValueSetter(e TargetValueSetter) Opt

WithTargetValueSetter _

In the TargetValueSetter you could return evendeep.ErrShouldFallback to call the evendeep standard processing.

TargetValueSetter can work for struct and map.

NOTE that the sourceNames[0] is current field name, and the whole sourceNames slice includes the path of the nested struct(s), in reversal order.

For instance:

type srcS struct {
  A int
  B bool
  C string
}

src := &srcS{
  A: 5,
  B: true,
  C: helloString,
}
tgt := map[string]typ.Any{
  "Z": "str",
}

err := evendeep.New().CopyTo(src, &tgt,
  evendeep.WithTargetValueSetter(
  func(value *reflect.Value, sourceNames ...string) (err error) {
    if value != nil {
      name := "Mo" + strings.Join(sourceNames, ".")
      tgt[name] = value.Interface()
    }
    return // ErrShouldFallback to call the evendeep standard processing
  }),
)

if err != nil || tgt["MoA"] != 5 || tgt["MoB"] != true || tgt["MoC"] != helloString || tgt["Z"] != "str" {
  t.Errorf("err: %v, tgt: %v", err, tgt)
  t.FailNow()
}

func WithTryApplyConverterAtFirst

func WithTryApplyConverterAtFirst(b bool) Opt

WithTryApplyConverterAtFirst specifies which is first when trying/applying ValueConverters and ValueCopiers.

func WithValueConverters

func WithValueConverters(cvt ...ValueConverter) Opt

WithValueConverters gives a set of ValueConverter. The value converters will be applied on its Match returns ok.

func WithValueCopiers

func WithValueCopiers(cvt ...ValueCopier) Opt

WithValueCopiers gives a set of ValueCopier. The value copiers will be applied on its Match returns ok.

func WithWipeTargetSliceFirst added in v0.2.51

func WithWipeTargetSliceFirst(wipe bool) Opt

WithWipeTargetSliceFirst enables the option which assumes the target Slice or Map will be wipe out at first before copying/merging from source field.

func WithoutPanic

func WithoutPanic() Opt

WithoutPanic disable panic() call internally.

Default is true.

type Params

type Params struct {
	// contains filtered or unexported fields
}

Params is params package.

type Slice added in v1.0.0

type Slice[T Integers | Uintegers | Floats] []T

type SourceValueExtractor

type SourceValueExtractor func(targetName string) typ.Any

SourceValueExtractor provides a hook for handling the extraction from source field.

SourceValueExtractor can work for non-nested struct.

type StringSlice added in v1.0.0

type StringSlice[T string] []T

type Stringer added in v1.0.0

type Stringer interface {
	String() string
}

type TargetValueSetter

type TargetValueSetter func(value *reflect.Value, sourceNames ...string) (err error)

TargetValueSetter provide a hook for handling the setup to a target field.

In the TargetValueSetter you could return evendeep.ErrShouldFallback to call the evendeep standard processing.

TargetValueSetter can work for struct and map.

NOTE that the sourceNames[0] is current field name, and the whole sourceNames slice includes the path of the nested struct(s), in reversal order.

type TextMarshaller

type TextMarshaller func(v interface{}) ([]byte, error) //nolint:revive

TextMarshaller for string marshaling.

type ToString added in v1.0.0

type ToString interface {
	ToString(args ...any) string
}

type UintSlice added in v1.0.0

type UintSlice[T Uintegers] []T

type Uintegers added in v1.0.0

type Uintegers interface {
	uint | uint8 | uint16 | uint32 | uint64
}

type ValueConverter

type ValueConverter interface {
	Transform(ctx *ValueConverterContext, source reflect.Value, targetType reflect.Type) (target reflect.Value, err error)
	Match(params *Params, source, target reflect.Type) (ctx *ValueConverterContext, yes bool)
}

ValueConverter for internal used.

type ValueConverterContext

type ValueConverterContext struct {
	*Params
}

ValueConverterContext for internal used.

func (*ValueConverterContext) IsCopyFunctionResultToTarget

func (ctx *ValueConverterContext) IsCopyFunctionResultToTarget() bool

IsCopyFunctionResultToTarget does SAFELY test if copyFunctionResultToTarget is true or not.

func (*ValueConverterContext) IsPassSourceToTargetFunction

func (ctx *ValueConverterContext) IsPassSourceToTargetFunction() bool

IsPassSourceToTargetFunction does SAFELY test if passSourceAsFunctionInArgs is true or not.

func (*ValueConverterContext) Preprocess

func (ctx *ValueConverterContext) Preprocess(source reflect.Value, targetType reflect.Type, cvtOuter ValueConverter) (processed bool, target reflect.Value, err error)

Preprocess find out a converter to transform source to target. If no comfortable converter found, the return processed is false.

type ValueConverters

type ValueConverters []ValueConverter

ValueConverters for internal used.

type ValueCopier

type ValueCopier interface {
	CopyTo(ctx *ValueConverterContext, source, target reflect.Value) (err error)
	Match(params *Params, source, target reflect.Type) (ctx *ValueConverterContext, yes bool)
}

ValueCopier for internal used.

type ValueCopiers

type ValueCopiers []ValueCopier

ValueCopiers for internal used.

Directories

Path Synopsis
_examples
cli
cms
cl
Package cl provides a compatible layer for golang versions
Package cl provides a compatible layer for golang versions
natsort
Package natsort implements natural sort.
Package natsort implements natural sort.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL