diff --git a/README.md b/README.md index f818418..e068f9c 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,18 @@ DeepCopy makes deep copies of things: unexported field values are not copied. ## Usage cpy := deepcopy.Copy(orig) + +## Tags + +The following struct tags is supported: + +``` +type A struct { + Field1 SomeType `deepcopy:"-"` // skip, will have zero-value in copy + Field2 *SomeType `deepcopy:"="` // treat like with "=" assignment operator +} +``` + +Specifically the `=` tag is usable when you want to copy a struct containing +something like `*sync.Mutex` or `*os.File` and don't want it to be deeply copied +but simply assigned. \ No newline at end of file diff --git a/deepcopy.go b/deepcopy.go index ba763ad..3b95b6a 100644 --- a/deepcopy.go +++ b/deepcopy.go @@ -87,12 +87,22 @@ func copyRecursive(original, cpy reflect.Value) { } // Go through each field of the struct and copy it. for i := 0; i < original.NumField(); i++ { + field := original.Type().Field(i) // The Type's StructField for a given field is checked to see if StructField.PkgPath // is set to determine if the field is exported or not because CanSet() returns false // for settable fields. I'm not sure why. -mohae - if original.Type().Field(i).PkgPath != "" { + if field.PkgPath != "" { continue } + + switch field.Tag.Get("deepcopy") { + case "-": + continue + case "=": + cpy.Field(i).Set(original.Field(i)) + continue + } + copyRecursive(original.Field(i), cpy.Field(i)) } diff --git a/deepcopy_test.go b/deepcopy_test.go index f150b1a..cc7a0e0 100644 --- a/deepcopy_test.go +++ b/deepcopy_test.go @@ -908,6 +908,45 @@ func TestPointerToStruct(t *testing.T) { } } +func TestFieldSkipByTag(t *testing.T) { + type Foo struct { + Bar int `deepcopy:"-"` + Thing int + } + + f := Foo{Bar: 42, Thing: 44} + cpy := Copy(f).(Foo) + + if !reflect.DeepEqual(cpy, Foo{Thing: 44}) { // Bar is skipped + t.Errorf("expected the copy to be equal to the original (except for memory location); it wasn't: got %#v; want %#v", f, cpy) + } +} + +func TestFieldAssignByTag(t *testing.T) { + type Inner struct { + Field int + } + type Foo struct { + Bar *Inner `deepcopy:"="` + Thing int + } + + inner := &Inner{Field: 100} + f := Foo{Bar: inner, Thing: 44} + cpy := Copy(f).(Foo) + + if !reflect.DeepEqual(cpy, Foo{Bar: inner, Thing: 44}) { // Bar is skipped + t.Errorf("expected the copy to be equal to the original (except for memory location); it wasn't: got %#v; want %#v", f, cpy) + } + + inner.Field = 101 + if inner.Field != f.Bar.Field || f.Bar.Field != cpy.Bar.Field { + t.Errorf("expected that pointer-assigned value will change in both original and copy: %d != %d != %d", + inner.Field, f.Bar.Field, cpy.Bar.Field) + } + +} + func TestIssue9(t *testing.T) { // simple pointer copy x := 42 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..702b9d1 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/mtfelian/deepcopy + +go 1.12