[{"data":1,"prerenderedAt":91},["ShallowReactive",2],{"content:\u002Fen\u002Ffiddling\u002Fgolang-deepcopy-between-different-type":3,"surround:\u002Fen\u002Ffiddling\u002Fgolang-deepcopy-between-different-type":80},{"id":4,"title":5,"authorship":6,"body":7,"categories":52,"date":54,"description":55,"draft":56,"extension":57,"image":58,"meta":59,"navigation":61,"path":62,"permalink":63,"published":63,"readingTime":64,"recommend":63,"references":63,"seo":69,"seoDescription":70,"seoTitle":63,"sitemap":71,"stem":72,"tags":73,"type":78,"__hash__":79},"content_en\u002Fposts\u002Ffiddling\u002Fgolang-deepcopy-between-different-type.md","Deep Copying Between Different Struct Types in Go","human-only",{"type":8,"value":9,"toc":49},"minimark",[10,14,17,20,23,34,37,40,43,46],[11,12,13],"p",{},"I have been swamped with a system refactor lately. The blog has been gathering dust for a while.",[11,15,16],{},"One particularly annoying part of the refactor was converting entities between layers of a layered architecture. Take a product: the view layer may have a product VO, the domain layer a product entity or DO (domain object), and the persistence layer a product PO corresponding to a database entity...",[11,18,19],{},"Most of these structures are similar; many are almost or entirely identical. Others have tiny differences, such as a field being a pointer in one struct but not in another, which prevents a direct cast. That means writing lots of assembler methods to convert entities, and complicated structures make this sheer hell. Fundamentally, it is just a deep copy that cannot be handled because the types differ.",[11,21,22],{},"I wondered whether reflection could solve this with a reasonably general conversion method for this particular situation. An afternoon later, the following code was born:",[24,25,31],"pre",{"className":26,"code":28,"language":29,"meta":30},[27],"language-go","func Copy(src interface{}, dstType interface{}) interface{} {\n    if src == nil {\n        return nil\n    }\n    cpy := reflect.New(reflect.TypeOf(dstType)).Elem()\n    copyRecursive(reflect.ValueOf(src), cpy)\n    return cpy.Interface()\n}\n \nfunc copyRecursive(src, dst reflect.Value) {\n    switch src.Kind() {\n    case reflect.Ptr:\n        originValue := src.Elem()\n        if !originValue.IsValid() {\n            return\n        }\n        \u002F\u002F 允许 src 为 ptr 而 dst 为非 ptr\n        if dst.Kind() == reflect.Ptr {\n            dst.Set(reflect.New(dst.Type().Elem()))\n            copyRecursive(originValue, dst.Elem())\n        } else {\n            dst.Set(reflect.New(dst.Type()).Elem())\n            copyRecursive(originValue, dst)\n        }\n    case reflect.Interface:\n        if src.IsNil() {\n            return\n        }\n        originValue := src.Elem()\n        copyValue := reflect.New(dst.Type().Elem()).Elem()\n        copyRecursive(originValue, copyValue)\n        dst.Set(copyValue)\n    case reflect.Struct:\n        \u002F\u002F time.Time 需要特殊处理\n        t, ok := src.Interface().(time.Time)\n        if ok {\n            dst.Set(reflect.ValueOf(t))\n            return\n        }\n        if dst.Kind() == reflect.Ptr {\n            \u002F\u002F 目标类型是指针而源类型不是指针\n            copyValue := reflect.New(dst.Type().Elem()).Elem()\n            copyRecursive(src, copyValue)\n            dst.Set(copyValue.Addr())\n            return\n        }\n        for i := 0; i \u003C dst.NumField(); i++ {\n            if dst.Type().Field(i).PkgPath != \"\" {\n                \u002F\u002F 不可导出的字段不拷贝\n                continue\n            }\n            field := src.FieldByName(dst.Type().Field(i).Name)\n            if !field.IsValid() {\n                \u002F\u002F 源字段不存在，忽略（目标自动零值）\n                continue\n            }\n            copyRecursive(field, dst.Field(i))\n        }\n    case reflect.Slice:\n        if src.IsNil() {\n            return\n        }\n        dst.Set(reflect.MakeSlice(dst.Type(), src.Len(), src.Cap()))\n        for i := 0; i \u003C src.Len(); i++ {\n            copyRecursive(src.Index(i), dst.Index(i))\n        }\n    case reflect.Map:\n        if src.IsNil() {\n            return\n        }\n        dst.Set(reflect.MakeMap(dst.Type()))\n        for _, key := range src.MapKeys() {\n            value := src.MapIndex(key)\n            copyValue := reflect.New(dst.Type().Elem()).Elem()\n            copyRecursive(value, copyValue)\n            copyKey := Copy(key.Interface(), reflect.New(dst.Type().Key()).Elem().Interface())\n            dst.SetMapIndex(reflect.ValueOf(copyKey), copyValue)\n        }\n    default:\n        \u002F\u002F 源类型是基础类型\n        \u002F\u002F 类型不一致但底层类型一致的基本类型，需要强转\n        if dst.Kind() == reflect.Ptr {\n            \u002F\u002F 目标类型是指针而源类型不是指针\n            copyValue := reflect.New(dst.Type().Elem()).Elem()\n            copyRecursive(src, copyValue)\n            dst.Set(copyValue.Addr())\n            return\n        }\n        dst.Set(src.Convert(dst.Type()))\n    }\n}\n","go","",[32,33,28],"code",{"__ignoreMap":30},[11,35,36],{},"The core is copyRecursive. It handles deep copies of structs, slices, and maps, including copies from pointer to non-pointer types and vice versa. The only requirement when copying structs is that every field in the destination struct have a field with the same name and underlying type in the source struct, allowing the recursive deep copy to proceed.",[11,38,39],{},"I will not explain the code in detail, but I do have to say:",[11,41,42],{},"Reflection is fucking awesome.",[44,45],"hr",{},[11,47,48],{},"20220820 update: Copying into structs whose fields do not all exist under the same names in the source is now supported. When there is no matching source field, the destination field receives its zero value: nil for pointers and an empty struct for structs.",{"title":30,"searchDepth":50,"depth":50,"links":51},4,[],[53],"fiddling","2022-08-15 01:05:01","While refactoring a system, converting entities between layers made deep copying surprisingly awkward. A product VO in the view layer, an entity in the domain layer, and a PO in the persistence layer can look nearly identical, yet small type differences complicate direct conversion. I used reflection to build a general conversion method, reducing repetitive assembler methods and making the code more maintainable and flexible.",false,"md","https:\u002F\u002Fblog-img.774352199.xyz\u002FBlhm0I.webp",{"slots":60},{},true,"\u002Ffiddling\u002Fgolang-deepcopy-between-different-type",null,{"text":65,"minutes":66,"time":67,"words":68},"3 min read",2.855,171300,571,{"title":5,"description":55},"Use Go reflection to deep-copy structs across entity types, handling slices, maps, pointer conversions, and zero values for missing source fields.",{"loc":62},"posts\u002Ffiddling\u002Fgolang-deepcopy-between-different-type",[74,75,76,77],"Go","Reflection","Deep copy","Struct conversion","tech","c1l4362gTF7Sba7QGowJOxOve1h8JXXg9vJfZbQpO2I",[81,86],{"title":82,"path":83,"stem":84,"date":85,"type":78,"children":-1},"How Java’s this Keyword Can Prevent Compile-Time Constant Propagation","\u002Ffiddling\u002Fthis-in-javac-string-concat","posts\u002Ffiddling\u002Fthis-in-javac-string-concat","2022-04-16 00:01:28",{"title":87,"path":88,"stem":89,"date":90,"type":78,"children":-1},"A Project That Failed: Seven Not-So-Happy Days over Chinese New Year","\u002Ffiddling\u002Fgo-os","posts\u002Ffiddling\u002Fgo-os","2023-02-02 23:24:55",1789914050291]