[{"data":1,"prerenderedAt":87},["ShallowReactive",2],{"content:\u002Ffiddling\u002Fgolang-deepcopy-between-different-type":3,"surround:\u002Ffiddling\u002Fgolang-deepcopy-between-different-type":76},{"id":4,"title":5,"body":6,"categories":51,"date":53,"description":54,"draft":55,"extension":56,"image":57,"meta":58,"navigation":60,"path":61,"permalink":57,"published":57,"readingTime":62,"recommend":57,"references":57,"seo":67,"sitemap":68,"stem":69,"tags":70,"type":74,"__hash__":75},"content\u002Fposts\u002Ffiddling\u002Fgolang-deepcopy-between-different-type.md","golang 支持不同类型结构体间的深拷贝",{"type":7,"value":8,"toc":48},"minimark",[9,13,16,19,22,33,36,39,42,45],[10,11,12],"p",{},"最近在做系统重构，忙得不可开交～博客荒废了好一阵子",[10,14,15],{},"在重构时，遇到了一个很恶心的事情：分层架构中不同层实体的互相转换。以商品为例，在视图层可能存在一个商品 VO，在领域层有个商品 entity 或者叫 DO（domain object），在存储层还可能有个商品 PO 对应着数据库实体……",[10,17,18],{},"这些实体结构大都很像，甚至好多基本一致或完全一样，或存在着细微的区别，例如某个字段在这个实体中是指针类型，而在另外的结构中则不是指针类型，因而导致无法直接强转，比如写很多 assembler 方法来对实体做转换，遇到复杂结构简直就是地狱。本质上就是深拷贝，就因为类型不一致而没法处理。",[10,20,21],{},"于是想着能不能用反射解决一下问题，对于这种特殊的场景做一个通用一点的转换方法，于是花了一个下午，以下代码应运而生：",[23,24,30],"pre",{"className":25,"code":27,"language":28,"meta":29},[26],"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","",[31,32,27],"code",{"__ignoreMap":29},[10,34,35],{},"核心在于 copyRecursive 方法，它支持处理结构体、Slice 和 Map 的深拷贝，并且支持从指针类型向非指针类型、非指针类型向指针类型的拷贝，唯一的要求就是拷贝结构体时，目标类型结构体中的所有字段都必须在源类型结构体中有同名且底层类型相同的字段，这样才能完成递归深拷贝。",[10,37,38],{},"具体代码原理不细细讲解，但是我还是要说一句：",[10,40,41],{},"反射确实很牛逼",[43,44],"hr",{},[10,46,47],{},"20220820 update：支持拷贝到源结构体中不存在同名字段的结构体，当源结构体不存在同名字段时，目标结构体的字段会被赋零值（指针为 nil，结构体为空结构体）",{"title":29,"searchDepth":49,"depth":49,"links":50},4,[],[52],"折腾","2022-08-15 01:05:01","在系统重构的过程中，面对分层架构中不同实体间的转换问题，深拷贝成为一项挑战。以商品为例，视图层的商品 VO、领域层的商品 entity 以及存储层的商品 PO 结构虽相似，但细微的差别如数据类型的不同，常常使得直接转换变得复杂且麻烦。为此，尝试利用反射实现一个通用的转换方法，旨在简化这一过程，减少繁琐的 assembler 方法，提升代码的可维护性与灵活性。",false,"md",null,{"slots":59},{},true,"\u002Ffiddling\u002Fgolang-deepcopy-between-different-type",{"text":63,"minutes":64,"time":65,"words":66},"4 min read",3.9,234000,780,{"title":5,"description":54},{"loc":61},"posts\u002Ffiddling\u002Fgolang-deepcopy-between-different-type",[52,71,72,73],"golang","反射","深拷贝","tech","gv5ytoJpivibmp4q6qKnoByzQC0vZa1w-hDbeWX77Ck",[77,82],{"title":78,"path":79,"stem":80,"date":81,"type":74,"children":-1},"Java 中 this 关键字导致编译期常量传播优化失效的问题","\u002Ffiddling\u002Fthis-in-javac-string-concat","posts\u002Ffiddling\u002Fthis-in-javac-string-concat","2022-04-16 00:01:28",{"title":83,"path":84,"stem":85,"date":86,"type":74,"children":-1},"一次失败的项目实践——春节七天乐（不起来）","\u002Ffiddling\u002Fgo-os","posts\u002Ffiddling\u002Fgo-os","2023-02-02 23:24:55",1787554445188]