From c68e5a2e67792b73473f65a3c71ef32abf08b7bf Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 02:50:45 +0200 Subject: [PATCH 01/11] feat: added override struct tag --- copier.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/copier.go b/copier.go index 0cc6465..15fc481 100644 --- a/copier.go +++ b/copier.go @@ -22,6 +22,9 @@ const ( // Ignore a destination field from being copied to. tagIgnore + // Denotes the fact that the field show be overridden no matter if the field is nil + tagOverride + // Denotes that the value as been copied hasCopied @@ -331,7 +334,8 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType) srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping) - if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) { + + if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, srcFieldName, destFieldName, flgs, opt.IgnoreEmpty) { // process for nested anonymous field destFieldNotSet := false if f, ok := dest.Type().FieldByName(destFieldName); ok { @@ -406,7 +410,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) fromMethod = source.MethodByName(srcFieldName) } - if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, opt.IgnoreEmpty) { + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, srcFieldName, destFieldName, flgs, opt.IgnoreEmpty) { if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { values := fromMethod.Call([]reflect.Value{}) if len(values) >= 1 { @@ -504,8 +508,8 @@ func copyUnexportedStructFields(to, from reflect.Value) { to.Set(tmp) } -func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool { - return ignoreEmpty && v.IsZero() +func shouldIgnore(v reflect.Value, from, to string, flgs flags, ignoreEmpty bool) bool { + return ignoreEmpty && (flgs.BitFlags[from]&tagOverride == 0 && flgs.BitFlags[to]&tagOverride == 0) && v.IsZero() } var deepFieldsLock sync.RWMutex @@ -693,6 +697,8 @@ func parseTags(tag string) (flg uint8, name string, err error) { flg = flg | tagMust case "nopanic": flg = flg | tagNoPanic + case "override": + flg = flg | tagOverride default: if unicode.IsUpper([]rune(t)[0]) { name = strings.TrimSpace(t) @@ -717,6 +723,7 @@ func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, er TagToFieldName: map[string]string{}, }, } + var toTypeFields, fromTypeFields []reflect.StructField if dest.IsValid() { toTypeFields = deepFields(toType) @@ -746,6 +753,7 @@ func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, er if tags != "" { var name string var err error + if _, name, err = parseTags(tags); err != nil { return flags{}, err } else if name != "" { @@ -754,6 +762,7 @@ func getFlags(dest, src reflect.Value, toType, fromType reflect.Type) (flags, er } } } + return flgs, nil } From 59a6c7a4585a8dae8d6a9fe9b30a23f5de6092a4 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 04:22:18 +0200 Subject: [PATCH 02/11] feat: override tag is now more streamlined, docs updated for better legibility --- .gitignore | 1 + README.md | 335 +++++++++++++++++++++++++++++++++++++++-------------- copier.go | 8 +- 3 files changed, 255 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 6d742b3..b280430 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/ ttt/ +cmd/ diff --git a/README.md b/README.md index 079dc57..29d481e 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,58 @@ # Copier - I am a copier, I copy everything from one to another +I am a copier, I copy everything from one to another [![test status](https://github.com/jinzhu/copier/workflows/tests/badge.svg?branch=master "test status")](https://github.com/jinzhu/copier/actions) -## Features +## Key Features -* Copy from field to field with same name -* Copy from method to field with same name -* Copy from field to method with same name -* Copy from slice to slice -* Copy from struct to slice -* Copy from map to map -* Enforce copying a field with a tag -* Ignore a field with a tag -* Deep Copy +- Field-to-field and method-to-field copying based on matching names +- Support for copying data: + - From slice to slice + - From struct to slice + - From map to map +- Field manipulation through tags: + - Enforce field copying with `copier:"must"` + - Override fields even when `IgnoreEmpty` is set with `copier:"override"` + - Exclude fields from being copied with `copier:"-"` -## Usage +## Getting Started + +### Installation + +To start using Copier, install Go and run go get: + +```bash +go get -u github.com/jinzhu/copier +``` + +## Basic + +Import Copier into your application to access its copying capabilities ```go -package main +import "github.com/jinzhu/copier" +``` -import ( - "fmt" - "github.com/jinzhu/copier" -) +### Basic Copying +```go type User struct { - Name string - Role string - Age int32 - EmployeeCode int64 `copier:"EmployeeNum"` // specify field name - - // Explicitly ignored in the destination struct. - Salary int + Name string + Role string + Age int32 } func (user *User) DoubleAge() int32 { return 2 * user.Age } -// Tags in the destination Struct provide instructions to copier.Copy to ignore -// or enforce copying and to panic or return an error if a field was not copied. type Employee struct { - // Tell copier.Copy to panic if this field is not copied. - Name string `copier:"must"` - - // Tell copier.Copy to return an error if this field is not copied. - Age int32 `copier:"must,nopanic"` - - // Tell copier.Copy to explicitly ignore copying this field. - Salary int `copier:"-"` + Name string + Role string + Age int32 DoubleAge int32 - EmployeeId int64 `copier:"EmployeeNum"` // specify field name - SuperRole string } func (employee *Employee) Role(role string) { @@ -62,59 +60,226 @@ func (employee *Employee) Role(role string) { } func main() { - var ( - user = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000} - users = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}} - employee = Employee{Salary: 150000} - employees = []Employee{} - ) - - copier.Copy(&employee, &user) - - fmt.Printf("%#v \n", employee) - // Employee{ - // Name: "Jinzhu", // Copy from field - // Age: 18, // Copy from field - // Salary:150000, // Copying explicitly ignored - // DoubleAge: 36, // Copy from method - // EmployeeId: 0, // Ignored - // SuperRole: "Super Admin", // Copy to method - // } - - // Copy struct to slice - copier.Copy(&employees, &user) - - fmt.Printf("%#v \n", employees) - // []Employee{ - // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"} - // } - - // Copy slice to slice - employees = []Employee{} - copier.Copy(&employees, &users) - - fmt.Printf("%#v \n", employees) - // []Employee{ - // {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"}, - // {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeeId: 0, SuperRole: "Super Dev"}, - // } - - // Copy map to map - map1 := map[int]int{3: 6, 4: 8} - map2 := map[int32]int8{} - copier.Copy(&map2, map1) - - fmt.Printf("%#v \n", map2) - // map[int32]int8{3:6, 4:8} + user := User{Name: "Jinzhu", Age: 18, Role: "Admin"} + employee := Employee{} + + copier.Copy(&employee, &user) + fmt.Printf("%#v\n", employee) + // Output: Employee{Name: "Jinzhu", Role: "Admin", Age: 18} +} +``` + +## Tag Usage Examples + +### `copier:"-"` - Ignoring Fields + +Fields tagged with `copier:"-"` are explicitly ignored by Copier during the copying process. + +```go +func main() { + user := User{Name: "Jinzhu", Salary: 200000} + employee := Employee{Salary: 150000} + + copier.Copy(&employee, &user) + fmt.Printf("Salary: %d\n", employee.Salary) + // Output: Salary: 150000, demonstrating that Salary field was ignored. +} +``` + +### `copier:"must"` - Enforcing Field Copy + +The `copier:"must"` tag forces a field to be copied, resulting in a panic or an error if the field cannot be copied. + +```go +func main() { + user := User{} + employee := Employee{} + + err := copier.Copy(&employee, &user) + if err != nil { + log.Fatal(err) + } + // Note: This example assumes modification in the library to handle 'must' flag appropriately. +} +``` + +### `copier:"must,nopanic"` - Enforcing Field Copy Without Panic + +Similar to `copier:"must"`, but Copier returns an error instead of panicking if the field is not copied. + +```go +func main() { + src := Source{} + dest := Destination{} + + err := copier.Copy(&dest, &src) + if err != nil { + log.Printf("Error: %v\n", err) + } +} +``` + +### `copier:"override"` - Overriding Fields with IgnoreEmpty + +Fields tagged with `copier:"override"` are copied even if IgnoreEmpty is set to true in Copier options and works for nil values. + +```go +func main() { + src := Destination{Name: nil} + dest := Source{&name} + + copier.CopyWithOption(&dest, &src, copier.Option{IgnoreEmpty: true}) + fmt.Printf("Name: %s\n", *dest.Name) + // Output shows that Name was copied despite being nil in `src`. +} +``` + +### Specifying Custom Field Names + +Use field tags to specify a custom field name when the source and destination field names do not match. + +```go +func main() { + user := User{EmployeeCode: 12345} + employee := Employee{} + + copier.Copy(&employee, &user) + fmt.Printf("%#v\n", employee) + // Output: Employee{EmployeeId: 12345}, demonstrating custom field name mapping. +} +``` + +### Copy from Method to Field with Same Name + +Illustrates copying from a method to a field and vice versa. + +```go +// Assuming User and Employee structs defined earlier with method and field respectively. + +func main() { + user := User{Name: "Jinzhu", Age: 18} + employee := Employee{} + + copier.Copy(&employee, &user) + fmt.Printf("DoubleAge: %d\n", employee.DoubleAge) + // Output: DoubleAge: 36, demonstrating method to field copying. +} +``` + +### Copy Struct to Slice + +```go +func main() { + user := User{Name: "Jinzhu", Age: 18, Role: "Admin"} + var employees []Employee + + copier.Copy(&employees, &user) + fmt.Printf("%#v\n", employees) + // Output: []Employee{{Name: "Jinzhu", Age: 18, DoubleAge: 36, SuperRole: "Super Admin"}} +} +``` + +### Copy Slice to Slice + +```go +func main() { + users := []User{{Name: "Jinzhu", Age: 18, Role: "Admin"}, {Name: "jinzhu 2", Age: 30, Role: "Dev"}} + var employees []Employee + + copier.Copy(&employees, &users) + fmt.Printf("%#v\n", employees) + // Output: []Employee{{Name: "Jinzhu", Age: 18, DoubleAge: 36, SuperRole: "Super Admin"}, {Name: "jinzhu 2", Age: 30, DoubleAge: 60, SuperRole: "Super Dev"}} +} +``` + +### Copy Map to Map + +```go +func main() { + map1 := map[int]int{3: 6, 4: 8} + map2 := map[int32]int8{} + + copier.Copy(&map2, map1) + fmt.Printf("%#v\n", map2) + // Output: map[int32]int8{3:6, 4:8} } ``` -### Copy with Option +## Complex Data Copying: Nested Structures with Slices + +This example demonstrates how Copier can be used to copy data involving complex, nested structures, including slices of structs, to showcase its ability to handle intricate data copying scenarios. ```go -copier.CopyWithOption(&to, &from, copier.Option{IgnoreEmpty: true, DeepCopy: true}) +package main + +import ( + "fmt" + "github.com/jinzhu/copier" +) + +type Address struct { + City string + Country string +} + +type Contact struct { + Email string + Phones []string +} + +type Employee struct { + Name string + Age int32 + Addresses []Address + Contact *Contact +} + +type Manager struct { + Name string `copier:"must"` + Age int32 `copier:"must,nopanic"` + ManagedCities []string + PrimaryContact *Contact `copier:"override"` + SecondaryEmails []string +} + +func main() { + employee := Employee{ + Name: "John Doe", + Age: 30, + Addresses: []Address{ + {City: "New York", Country: "USA"}, + {City: "San Francisco", Country: "USA"}, + }, + Contact: &Contact{ + Email: "john.doe@example.com", + Phones: []string{"123-456-7890", "098-765-4321"}, + }, + } + + manager := Manager{ + ManagedCities: []string{"Los Angeles", "Boston"}, + PrimaryContact: nil, // Intentionally empty to demonstrate `override` + SecondaryEmails: []string{"secondary@example.com"}, + } + + copier.CopyWithOption(&manager, &employee, copier.Option{IgnoreEmpty: true, DeepCopy: true}) + + fmt.Printf("Manager: %#v\n", manager) + // Output: Manager struct showcasing copied fields from Employee, + // including overridden and deeply copied nested slices. +} ``` +## Available tags + +| Tag | Description | +| ------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `copier:"-"` | Explicitly ignores the field during copying. | +| `copier:"must"` | Forces the field to be copied; Copier will panic or return an error if the field is not copied. | +| `copier:"nopanic"` | Copier will return an error instead of panicking. | +| `copier:"override"` | Forces the field to be copied even if `IgnoreEmpty` is set. Useful for overriding existing values with empty ones | +| `FieldName` | Specifies a custom field name for copying when field names do not match between structs. | + ## Contributing You can help to make the project better, check out [http://gorm.io/contribute.html](http://gorm.io/contribute.html) for things you can do. @@ -123,9 +288,9 @@ You can help to make the project better, check out [http://gorm.io/contribute.ht **jinzhu** -* -* -* +- +- +- ## License diff --git a/copier.go b/copier.go index 15fc481..3fee4c0 100644 --- a/copier.go +++ b/copier.go @@ -335,7 +335,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping) - if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, srcFieldName, destFieldName, flgs, opt.IgnoreEmpty) { + if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, fieldFlags, opt.IgnoreEmpty) { // process for nested anonymous field destFieldNotSet := false if f, ok := dest.Type().FieldByName(destFieldName); ok { @@ -410,7 +410,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) fromMethod = source.MethodByName(srcFieldName) } - if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, srcFieldName, destFieldName, flgs, opt.IgnoreEmpty) { + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, flgs.BitFlags[destFieldName], opt.IgnoreEmpty) { if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { values := fromMethod.Call([]reflect.Value{}) if len(values) >= 1 { @@ -508,8 +508,8 @@ func copyUnexportedStructFields(to, from reflect.Value) { to.Set(tmp) } -func shouldIgnore(v reflect.Value, from, to string, flgs flags, ignoreEmpty bool) bool { - return ignoreEmpty && (flgs.BitFlags[from]&tagOverride == 0 && flgs.BitFlags[to]&tagOverride == 0) && v.IsZero() +func shouldIgnore(v reflect.Value, bitFlags uint8, ignoreEmpty bool) bool { + return ignoreEmpty && bitFlags&tagOverride == 0 && v.IsZero() } var deepFieldsLock sync.RWMutex From 2de596713768d6e2e0386da37cc9da5a55b3ae4b Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 04:24:52 +0200 Subject: [PATCH 03/11] chore: fixed typo --- copier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copier.go b/copier.go index 3fee4c0..eb2c5aa 100644 --- a/copier.go +++ b/copier.go @@ -22,7 +22,7 @@ const ( // Ignore a destination field from being copied to. tagIgnore - // Denotes the fact that the field show be overridden no matter if the field is nil + // Denotes the fact that the field should be overridden, no matter if the IgnoreEmpty is set tagOverride // Denotes that the value as been copied From 1950134cb243e0c2542c6b2c10127e734697bd31 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 04:28:01 +0200 Subject: [PATCH 04/11] chore: fixed naming --- copier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copier.go b/copier.go index eb2c5aa..afce2bc 100644 --- a/copier.go +++ b/copier.go @@ -410,7 +410,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) fromMethod = source.MethodByName(srcFieldName) } - if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, flgs.BitFlags[destFieldName], opt.IgnoreEmpty) { + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, flgs.BitFlags[name], opt.IgnoreEmpty) { if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() { values := fromMethod.Call([]reflect.Value{}) if len(values) >= 1 { From 289db09a0cc1f6ad615bcb72144add4a68ff13b9 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 14:29:07 +0200 Subject: [PATCH 05/11] chore: fixed readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29d481e..ea522e7 100644 --- a/README.md +++ b/README.md @@ -254,11 +254,15 @@ func main() { Email: "john.doe@example.com", Phones: []string{"123-456-7890", "098-765-4321"}, }, + PrimaryContact: nil, // Intentionally empty to demonstrate `override` } manager := Manager{ ManagedCities: []string{"Los Angeles", "Boston"}, - PrimaryContact: nil, // Intentionally empty to demonstrate `override` + PrimaryContact: &Contact{ + Email: "john.doe@example.com", + Phones: []string{"123-456-7890", "098-765-4321"}, + }, // since override is set this should be overridden with nil SecondaryEmails: []string{"secondary@example.com"}, } From 7ba6f570b3b2368ba41e4e0e17907f612ffb3509 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 15:19:39 +0200 Subject: [PATCH 06/11] chore: fixed readme examples for better clarity --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index ea522e7..a27e16e 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,23 @@ func main() { Fields tagged with `copier:"-"` are explicitly ignored by Copier during the copying process. ```go +type Source struct { + Name string + Secret string // We do not want this to be copied. +} + +type Target struct { + Name string + Secret string `copier:"-"` +} + func main() { - user := User{Name: "Jinzhu", Salary: 200000} - employee := Employee{Salary: 150000} + source := Source{Name: "John", Secret: "so_secret"} + target := Target{} - copier.Copy(&employee, &user) - fmt.Printf("Salary: %d\n", employee.Salary) - // Output: Salary: 150000, demonstrating that Salary field was ignored. + copier.Copy(&target, &source) + fmt.Printf("Name: %s, Secret: '%s'\n", target.Name, target.Secret) + // Output: Name: John, Secret: '' } ``` @@ -91,15 +101,23 @@ func main() { The `copier:"must"` tag forces a field to be copied, resulting in a panic or an error if the field cannot be copied. ```go +type MandatorySource struct { + ID int +} + +type MandatoryTarget struct { + ID int `copier:"must"` // This field must be copied, or it will panic/error. +} + func main() { - user := User{} - employee := Employee{} + source := MandatorySource{} + target := MandatoryTarget{} - err := copier.Copy(&employee, &user) + // This will result in a panic or an error since ID is a must field but is empty in source. + err := copier.Copy(&target, &source) if err != nil { log.Fatal(err) } - // Note: This example assumes modification in the library to handle 'must' flag appropriately. } ``` @@ -108,14 +126,23 @@ func main() { Similar to `copier:"must"`, but Copier returns an error instead of panicking if the field is not copied. ```go +type SafeSource struct { + Code string +} + +type SafeTarget struct { + Code string `copier:"must,nopanic"` // Enforce copying without panic. +} + func main() { - src := Source{} - dest := Destination{} + source := SafeSource{} + target := SafeTarget{} - err := copier.Copy(&dest, &src) + err := copier.Copy(&target, &source) if err != nil { - log.Printf("Error: %v\n", err) + fmt.Println("Error:", err) } + // This will not panic, but will return an error due to missing mandatory field. } ``` @@ -124,13 +151,23 @@ func main() { Fields tagged with `copier:"override"` are copied even if IgnoreEmpty is set to true in Copier options and works for nil values. ```go +type SourceWithNil struct { + Details *string +} + +type TargetOverride struct { + Details *string `copier:"override"` // Even if source is nil, copy it. +} + func main() { - src := Destination{Name: nil} - dest := Source{&name} + details := "Important details" + source := SourceWithNil{Details: nil} + target := TargetOverride{Details: &details} - copier.CopyWithOption(&dest, &src, copier.Option{IgnoreEmpty: true}) - fmt.Printf("Name: %s\n", *dest.Name) - // Output shows that Name was copied despite being nil in `src`. + copier.CopyWithOption(&target, &source, copier.Option{IgnoreEmpty: true}) + if target.Details == nil { + fmt.Println("Details field was overridden to nil.") + } } ``` @@ -139,16 +176,26 @@ func main() { Use field tags to specify a custom field name when the source and destination field names do not match. ```go +type SourceEmployee struct { + Identifier int64 +} + +type TargetWorker struct { + ID int64 `copier:"Identifier"` // Map Identifier from SourceEmployee to ID in TargetWorker +} + func main() { - user := User{EmployeeCode: 12345} - employee := Employee{} + source := SourceEmployee{Identifier: 1001} + target := TargetWorker{} - copier.Copy(&employee, &user) - fmt.Printf("%#v\n", employee) - // Output: Employee{EmployeeId: 12345}, demonstrating custom field name mapping. + copier.Copy(&target, &source) + fmt.Printf("Worker ID: %d\n", target.ID) + // Output: Worker ID: 1001 } ``` +## Other examples + ### Copy from Method to Field with Same Name Illustrates copying from a method to a field and vice versa. From 00c854b1d8904c9d79bc6c7ea293ee6d6c66ff81 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 15:20:44 +0200 Subject: [PATCH 07/11] chore: fixed formatting --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index a27e16e..c2b7e02 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,7 @@ type Employee struct { Name string Role string Age int32 - - DoubleAge int32 + DoubleAge int32 } func (employee *Employee) Role(role string) { From f2bf7a696b8b1e969153bc164bad63bb1d11fd46 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 15:45:22 +0200 Subject: [PATCH 08/11] chore: tested examples --- README.md | 80 +++++++++++++++++++++++++------------------------------ 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index c2b7e02..1e376f0 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ import "github.com/jinzhu/copier" ```go type User struct { - Name string - Role string - Age int32 + Name string + Role string + Age int32 } func (user *User) DoubleAge() int32 { @@ -48,10 +48,10 @@ func (user *User) DoubleAge() int32 { } type Employee struct { - Name string - Role string - Age int32 - DoubleAge int32 + Name string + Age int32 + DoubleAge int32 + SuperRole string } func (employee *Employee) Role(role string) { @@ -59,12 +59,12 @@ func (employee *Employee) Role(role string) { } func main() { - user := User{Name: "Jinzhu", Age: 18, Role: "Admin"} - employee := Employee{} + user := User{Name: "Jinzhu", Age: 18, Role: "Admin"} + employee := Employee{} - copier.Copy(&employee, &user) - fmt.Printf("%#v\n", employee) - // Output: Employee{Name: "Jinzhu", Role: "Admin", Age: 18} + copier.Copy(&employee, &user) + fmt.Printf("%#v\n", employee) + // Output: Employee{Name:"Jinzhu", Age:18, DoubleAge:36, SuperRole:"Super Admin"} } ``` @@ -101,22 +101,21 @@ The `copier:"must"` tag forces a field to be copied, resulting in a panic or an ```go type MandatorySource struct { - ID int + Identification int } type MandatoryTarget struct { - ID int `copier:"must"` // This field must be copied, or it will panic/error. + ID int `copier:"must"` // This field must be copied, or it will panic/error. } func main() { - source := MandatorySource{} - target := MandatoryTarget{} + source := MandatorySource{} + target := MandatoryTarget{ID: 10} - // This will result in a panic or an error since ID is a must field but is empty in source. - err := copier.Copy(&target, &source) - if err != nil { - log.Fatal(err) - } + // This will result in a panic or an error since ID is a must field but is empty in source. + if err := copier.Copy(&target, &source); err != nil { + log.Fatal(err) + } } ``` @@ -126,22 +125,21 @@ Similar to `copier:"must"`, but Copier returns an error instead of panicking if ```go type SafeSource struct { - Code string + ID string } type SafeTarget struct { - Code string `copier:"must,nopanic"` // Enforce copying without panic. + Code string `copier:"must,nopanic"` // Enforce copying without panic. } func main() { - source := SafeSource{} - target := SafeTarget{} + source := SafeSource{} + target := SafeTarget{Code: "200"} - err := copier.Copy(&target, &source) - if err != nil { - fmt.Println("Error:", err) - } - // This will not panic, but will return an error due to missing mandatory field. + if err := copier.Copy(&target, &source); err != nil { + log.Fatalln("Error:", err) + } + // This will not panic, but will return an error due to missing mandatory field. } ``` @@ -269,8 +267,8 @@ type Address struct { } type Contact struct { - Email string - Phones []string + Email string + Phones []string } type Employee struct { @@ -281,10 +279,10 @@ type Employee struct { } type Manager struct { - Name string `copier:"must"` - Age int32 `copier:"must,nopanic"` + Name string `copier:"must"` + Age int32 `copier:"must,nopanic"` ManagedCities []string - PrimaryContact *Contact `copier:"override"` + Contact *Contact `copier:"override"` SecondaryEmails []string } @@ -296,16 +294,12 @@ func main() { {City: "New York", Country: "USA"}, {City: "San Francisco", Country: "USA"}, }, - Contact: &Contact{ - Email: "john.doe@example.com", - Phones: []string{"123-456-7890", "098-765-4321"}, - }, - PrimaryContact: nil, // Intentionally empty to demonstrate `override` + Contact: nil, } manager := Manager{ - ManagedCities: []string{"Los Angeles", "Boston"}, - PrimaryContact: &Contact{ + ManagedCities: []string{"Los Angeles", "Boston"}, + Contact: &Contact{ Email: "john.doe@example.com", Phones: []string{"123-456-7890", "098-765-4321"}, }, // since override is set this should be overridden with nil @@ -316,7 +310,7 @@ func main() { fmt.Printf("Manager: %#v\n", manager) // Output: Manager struct showcasing copied fields from Employee, - // including overridden and deeply copied nested slices. + // including overridden and deeply copied nested slices. } ``` From 9e103f7a56c8f9f152c95f33567cd95620c1f076 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 16:02:59 +0200 Subject: [PATCH 09/11] feat: added tests for override tag --- copier_tags_test.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/copier_tags_test.go b/copier_tags_test.go index c8fe704..72fae84 100644 --- a/copier_tags_test.go +++ b/copier_tags_test.go @@ -16,13 +16,13 @@ type EmployeeTags struct { type User1 struct { Name string DOB string - Address string + Address string `copier:"override"` ID int } type User2 struct { DOB string - Address string + Address *string `copier:"override"` ID int } @@ -49,6 +49,29 @@ func TestCopyTagMust(t *testing.T) { copier.Copy(employee, user) } +func TestCopyTagOverrideZeroValue(t *testing.T) { + options := copier.Option{IgnoreEmpty: true} + employee := EmployeeTags{ID: 100, Address: ""} + user := User1{Name: "Dexter Ledesma", DOB: "1 November, 1970", Address: "21 Jump Street", ID: 12345} + + copier.CopyWithOption(&user, employee, options) + if user.Address != "" { + t.Error("Original Address was not overwritten") + } +} + +func TestCopyTagOverridePtr(t *testing.T) { + options := copier.Option{IgnoreEmpty: true} + address := "21 Jump Street" + user2 := User2{ID: 100, Address: nil} + user := User2{DOB: "1 November, 1970", Address: &address, ID: 12345} + + copier.CopyWithOption(&user, user2, options) + if user.Address != nil { + t.Error("Original Address was not overwritten") + } +} + func TestCopyTagFieldName(t *testing.T) { t.Run("another name field copy", func(t *testing.T) { type SrcTags struct { From d8f7ec91883de52d2a733afec986889408b9713c Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 18:19:48 +0200 Subject: [PATCH 10/11] feat: ptr -> zero value and zero value -> ptr overrides now work --- copier.go | 4 ++++ copier_tags_test.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/copier.go b/copier.go index afce2bc..77c64f0 100644 --- a/copier.go +++ b/copier.go @@ -593,6 +593,9 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ } // allocate new `to` variable with default value (eg. *string -> new(string)) to.Set(reflect.New(to.Type().Elem())) + } else if from.Kind() != reflect.Ptr && from.IsZero() { + to.Set(reflect.Zero(to.Type())) + return true, nil } // depointer `to` to = to.Elem() @@ -607,6 +610,7 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ } } if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) return true, nil } if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) { diff --git a/copier_tags_test.go b/copier_tags_test.go index 72fae84..61ee238 100644 --- a/copier_tags_test.go +++ b/copier_tags_test.go @@ -60,6 +60,29 @@ func TestCopyTagOverrideZeroValue(t *testing.T) { } } +func TestCopyTagOverridePtrToZeroValue(t *testing.T) { + options := copier.Option{IgnoreEmpty: true} + address := "21 Jump Street" + user1 := User1{ID: 100, Address: ""} + user2 := User2{DOB: "1 November, 1970", Address: &address, ID: 12345} + + copier.CopyWithOption(&user2, user1, options) + if user2.Address != nil { + t.Error("Original Address was not overwritten") + } +} + +func TestCopyTagOverrideZeroValueToPtr(t *testing.T) { + options := copier.Option{IgnoreEmpty: true} + user1 := User2{DOB: "1 November, 1970", Address: nil, ID: 12345} + user2 := User1{ID: 100, Address: "1 November, 1970"} + + copier.CopyWithOption(&user2, user1, options) + if user1.Address != nil { + t.Error("Original Address was not overwritten") + } +} + func TestCopyTagOverridePtr(t *testing.T) { options := copier.Option{IgnoreEmpty: true} address := "21 Jump Street" From c3f9f63c26f00be27b7732f1a6d8adda4f1420c2 Mon Sep 17 00:00:00 2001 From: Rocco Ciccone Date: Sun, 7 Apr 2024 18:31:00 +0200 Subject: [PATCH 11/11] feat: replaced deprecated method --- copier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copier.go b/copier.go index 77c64f0..2ca974d 100644 --- a/copier.go +++ b/copier.go @@ -206,7 +206,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error) to.SetMapIndex(toKey, toValue) break } - elemType = reflect.PtrTo(elemType) + elemType = reflect.PointerTo(elemType) toValue = toValue.Addr() } }