Deep Copy in Go: Reflection, JSON, and GOB Techniques Explained
Performing a deep copy in Go involves creating a new instance of a data structure and copying all data from the original instance to the new one. This ensures that changes to the new instance do not affect the original instance and vice versa. Below is an example demonstrating how to achieve deep copying using reflection, manual copying, and serialization/deserialization methods.
Using Reflection (Generic Deep Copy)
To perform a deep copy using reflection, you can traverse each field of the struct and copy the values recursively:
package main
import (
"fmt"
"reflect"
)
// DeepCopy performs a deep copy of the provided object.
func DeepCopy(src interface{}) interface{} {
if src == nil {
return nil
}
dst := reflect.New(reflect.TypeOf(src)).Elem()
dst.Set(reflect.ValueOf(src))
return deepCopyValue(dst).Interface()
}
func deepCopyValue(src reflect.Value) reflect.Value {
if !src.IsValid() {
return src
}
switch src.Kind() {
case reflect.Ptr:
if src.IsNil() {
return reflect.Zero(src.Type())
}
dst := reflect.New(src.Elem().Type())
dst.Elem().Set(deepCopyValue(src.Elem()))
return dst
case reflect.Interface:
if src.IsNil() {
return reflect.Zero(src.Type())
}
dst := reflect.New(src.Elem().Type()).Elem()
dst.Set(deepCopyValue(src.Elem()))
return dst
case reflect.Struct:
dst := reflect.New(src.Type()).Elem()
for i := 0; i < src.NumField(); i++ {
dst.Field(i).Set(deepCopyValue(src.Field(i)))
}
return dst
case reflect.Slice:
if src.IsNil() {
return reflect.Zero(src.Type())
}
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
for i := 0; i < src.Len(); i++ {
dst.Index(i).Set(deepCopyValue(src.Index(i)))
}
return dst
case reflect.Map:
if src.IsNil() {
return reflect.Zero(src.Type())
}
dst := reflect.MakeMapWithSize(src.Type(), src.Len())
for _, key := range src.MapKeys() {
dst.SetMapIndex(key, deepCopyValue(src.MapIndex(key)))
}
return dst
default:
return src
}
}
type Inner struct {
Value int
}
type Outer struct {
Name string
Scores []int
Inner Inner
}
func main() {
original := Outer{
Name: "Alice",
Scores: []int{10, 20, 30},
Inner: Inner{Value: 42},
}
copy := DeepCopy(original).(Outer)
// Modify the copy
copy.Name = "Bob"
copy.Scores[0] = 100
copy.Inner.Value = 99
fmt.Println("Original:", original)
fmt.Println("Copy: ", copy)
}
Using Serialization/Deserialization (JSON)
Another method is to serialize the object into a format like JSON and then deserialize it back, which ensures a deep copy:
package main
import (
"bytes"
"encoding/gob"
"encoding/json"
"fmt"
)
// Using JSON
func DeepCopyJSON(src, dst interface{}) error {
data, err := json.Marshal(src)
if err != nil {
return err
}
return json.Unmarshal(data, dst)
}
// Using GOB
func DeepCopyGOB(src, dst interface{}) error {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
dec := gob.NewDecoder(&buf)
if err := enc.Encode(src); err != nil {
return err
}
return dec.Decode(dst)
}
type Inner struct {
Value int
}
type Outer struct {
Name string
Scores []int
Inner Inner
}
func main() {
original := Outer{
Name: "Alice",
Scores: []int{10, 20, 30},
Inner: Inner{Value: 42},
}
// Using JSON
var copyJSON Outer
err := DeepCopyJSON(original, ©JSON)
if err != nil {
fmt.Println("Error in JSON DeepCopy:", err)
return
}
// Modify the JSON copy
copyJSON.Name = "Bob"
copyJSON.Scores[0] = 100
copyJSON.Inner.Value = 99
fmt.Println("Original (JSON):", original)
fmt.Println("Copy (JSON): ", copyJSON)
// Using GOB
var copyGOB Outer
err = DeepCopyGOB(original, ©GOB)
if err != nil {
fmt.Println("Error in GOB DeepCopy:", err)
return
}
// Modify the GOB copy
copyGOB.Name = "Charlie"
copyGOB.Scores[0] = 200
copyGOB.Inner.Value = 88
fmt.Println("Original (GOB):", original)
fmt.Println("Copy (GOB): ", copyGOB)
}
Explanation
-
Reflection Method: The
DeepCopy
anddeepCopyValue
functions traverse the object using reflection to duplicate each field appropriately. This method is more complex but provides a generic way to deep copy different types of structures. -
Serialization/Deserialization:
- JSON:
DeepCopyJSON
uses JSON serialization and deserialization to create a deep copy. - GOB:
DeepCopyGOB
uses the GOB encoder and decoder for binary serialization, which can be more efficient than JSON but may require more setup.
- JSON:
Each method has its pros and cons. Reflection is flexible and works for many types, while JSON and GOB are simpler to use but may have performance overhead. Choose the method that best fits your needs.