Golang Copier入门到入坑探究

目录
  • 正文
    • 安装
  • 快速入门
  • 入坑
  • 再探坑出坑
  • 再盘一盘坑
  • 结语

正文

github: https://github.com/jinzhu/copier

由于 golang 没有对复杂结构体的 clone 方法,所以,就需要有 copier 这样的工具库。

它看起来很简单,但实际使用中,有些“坑”还是要注意!

本文:

入门为辅,探“坑”为主,

看完再划走,CS我没有。

安装

go get github.com/jinzhu/copier

快速入门

好的,来一段代码快速了解 copier

package main
import (
    "fmt"
    "github.com/jinzhu/copier"
)
type SC struct {
    C uint8
}
type M1 struct {
    A int
    W string
    S *SC
}
func main() {
    var src = M1{12, "Hello", &SC{32}}
    var dst = M1{}
    fmt.Printf("before copy src %+v\tdst %+v\n", src, dst)
    copier.Copy(&dst, src)
    fmt.Printf("after  copy src %+v\tdst %+v\n", src, dst)
}

输出:

before copy src {A:12 W:Hello S:0xc00017f550}   dst {A:0 W: S:<nil>}
after  copy src {A:12 W:Hello S:0xc00017f550}   dst {A:12 W:Hello S:0xc00017f618}

好的,看到这,你就已掌握了 copier 80%的功能了。先别着急划走,接下来还是踩坑记录。

本文代码运行输出内容是基于 github.com/jinzhu/copier@v0.3.5 和 go1.16.1 darwin/amd64 环境演示的结果。

入坑

package main
import (
    "fmt"
    "github.com/davecgh/go-spew/spew"
    "github.com/jinzhu/copier"
)
type SC struct {
    C uint8
}
type Map1 struct {
    M map[string]int32
    A []int32
    C *SC
}
func main() {
    var src = Map1{map[string]int32{"C:": 3, "d": 4}, []int32{9, 8}, &SC{32}}
    var dst1 = Map1{}
    spew.Printf("before src %+v\t\tdst %+v\n", src, dst1)
    copier.Copy(&dst1, src)
    dst1.M["F"] = 5
    dst1.M["g"] = 6
    dst1.A[0] = 7
    dst1.C.C = 27
    spew.Printf("after  src %+v\tdst %+v\n", src, dst1)
}

以上代码运行后会输出:

before src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc00012a1e8){C:32}}          dst {M:<nil> A:<nil> C:<nil>}

befre 那一行代码如上️ , after 那一行会输出什么呢?

1. after  src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc00012a1e8){C:27}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
2. after  src {M:map[C::3 d:4] A:[9 8] C:<*>(0xc00012a1e8){C:32}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
3. after  src {M:map[C::3 d:4] A:[7 8] C:<*>(0xc00012a1e8){C:32}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
4. after  src {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a1e8){C:32}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}
5. after  src {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a1e8){C:27}}  dst {M:map[C::3 d:4 F:5 g:6] A:[7 8] C:<*>(0xc00012a348){C:27}}

答案是: var a = int(759 / 6 / 31.5)

为了避免不小心看了答案,请计算 759 / 6 / 31.5 得出的值四舍五入便是。

再探坑出坑

我看其他同学使用 copier 也是像上面那样——copier.Copy($dst, src), 当然啦,也不排除我!它仿佛就是一把小巧精悍的小刀。一个简单的函数调用,就完成了它的使命。

然而,它其实是把多功能刀,我都还没有打开它的—— option

上面的问题就是,我 Copy 后,对值的改动,影响了另一个值的 map,那么这个时候,就需要进行深 copy。接下来引入 copier 的 option

package main
import (
    "fmt"
    "github.com/davecgh/go-spew/spew"
    "github.com/jinzhu/copier"
)
type SC struct {
    C uint8
}
type Map1 struct {
    M map[string]int32
    A []int32
    C *SC
}
func main() {
    var src = Map1{map[string]int32{"C:": 3, "d": 4}, []int32{9, 8}, &SC{32}}
    var dst1 = Map1{}
    spew.Printf("before src %+v\t\tdst %+v\n", src, dst1)
    copier.CopyWithOption(&dst1, src, copier.Option{DeepCopy: true})   // 这里!
    dst1.M["F"] = 5
    dst1.M["g"] = 6
    dst1.A[0] = 7
    dst1.C.C = 27
    spew.Printf("after  src %+v\tdst %+v\n", src, dst1)
}

好的,这样copy之后,对新变量的改动,不会传递会原变量改动了。

再盘一盘坑

package main
import (
    "fmt"
    "github.com/davecgh/go-spew/spew"
    "github.com/jinzhu/copier"
)
type ArrTC struct {
    Name [2]string
    C    *ArrTC
}
type ArrT struct {
    A  [3]int32
    S  []int32
    E  []int32
    C  string
    V  string
    M map[string]int32
    AC ArrTC
    s bool
}
func main() {
    var src = ArrT{
        [3]int32{9, 10, 0},
        []int32{12, 0},
        []int32{},
        "",
        "val",
        map[string]int32{"A:": 1, "b": 0},
        ArrTC{},
        true,
    }
    var dst = ArrT{
        [3]int32{1, 2, 3},
        []int32{4, 5, 6, 7},
        []int32{9, 10},
        "char",
        "ha",
        map[string]int32{"C:": 3, "b": 4, ".": 0},
        ArrTC{[2]string{"Y", "Z"}, nil},
        false,
    }
    spew.Printf("before src %+v\tdst %+v\n", src, dst)
    copier.CopyWithOption(&dst, src, copier.Option{IgnoreEmpty: true, DeepCopy: true})
    spew.Printf("after  src %+v\tdst %+v\n", src, dst)
    src.M["b"] = 99
    src.S[1] = 1
    dst.S[0] = 2
    spew.Printf("last  src %+v\tdst %+v\n\n", src, dst)
}

输出:

before src {A:[9 10 0] S:[12 0] E:[] C: V:val M:map[A::1 b:0] AC:{Name:[ ] C:<nil>} s:true}    dst {A:[1 2 3] S:[4 5 6 7] E:[9 10] C:char V:ha M:map[C::3 b:4 .:0] AC:{Name:[Y Z] C:<nil>} s:false}
after  src {A:[9 10 0] S:[12 0] E:[] C: V:val M:map[A::1 b:0] AC:{Name:[ ] C:<nil>} s:true}    dst {A:[9 10 0] S:[12 0 6 7] E:[9 10] C:char V:val M:map[A::1 C::3 b:0 .:0] AC:{Name:[Y Z] C:<nil>} s:true}
last  src {A:[9 10 0] S:[12 1] E:[] C: V:val M:map[A::1 b:99] AC:{Name:[ ] C:<nil>} s:true}    dst {A:[9 10 0] S:[2 0 6 7] E:[9 10] C:char V:val M:map[C::3 b:0 .:0 A::1] AC:{Name:[Y Z] C:<nil>} s:true}

这次的代码我加上了 IgnoreEmpty: true, 也就是复制时忽略空的值。 也就说可以当作值 merge 用。

然后,又测试了一下变量独立性。复制之后,srcdst 两个变量再无瓜葛,对其中一个值的任意改动都不会同步到另一个值。

但是,这个 merge 的表现,可能不是你想的那样,

src.S = []int32{12, 0}
dst.S = []int32{4, 5, 6, 7}
## 调用 copy 后, 你预期的结果是什么?[6/7]
6. dst.S = []int32{12, 0}
7. dst.S = []int32{12, 0, 6, 7}
  • 选项6: 嗯,原来是 {12, 0} 复制给 dst 就是 {12, 0}
  • 选项7: 这个是切片,你只给我 0,1 位的值,copier把 0,1 位置的值 copy 了,dst后面2,3位的值,src没给出,那就不管。所以就是 {12, 0, 6, 7}

这块的表现,我觉得是有争议的,大佬们在评论区留下你预期选项,看看大家是不是都这样想的。

实际运行结果,见上面的代码输出就能找到答案。

结语

copier 本来是一个短小精悍的工具库,也没想要水一篇,最近使用时,突然踩坑,就特开一篇,和大家分享一下踩坑经验。

在使用外部库的时候,还是建议去 github 上看看详细说明, 或者上 pkg.go.dev 看看它暴露出来出的接口以及说明。更或者进行完整的测试,充分了解它之后,再使用。

以上就是Golang Copier入门到入坑探究的详细内容,更多关于Golang Copier入门的资料请关注我们其它相关文章!

(0)

相关推荐

  • Dragonfly P2P 传输协议优化代码解析

    目录 优化背景 相关代码分析 优化方案 优化实现 优化结果 优化背景 此前 Dragonfly 的 P2P 下载采用静态限流策略,相关配置项在 dfget.yaml 配置文件中: # 下载服务选项. download: # 总下载限速. totalRateLimit: 1024Mi # 单个任务下载限速. perPeerRateLimit: 512Mi 其中 perPeerRateLimit 为单个任务设置流量上限, totalRateLimit 为单个节点的所有任务设置流量上限. 静态限流策略

  • Android Studio gradle配置packagingOptions打包so库重复

    目录 正文 pickFirst 匹配 doNotStrip 设置 merge 将匹配的文件都添加到APK中 exclude 过滤 正文 在安卓开发中,通常会使用到gradle来编译,在安卓项目的app目录下的build.gradle中是用来对编译进行配置的,packagingOptions 是其中的一个打包配置,常见的设置项有exclude.pickFirst.doNotStrip.merge. 在日常代码开发中,我们需要知其然,而知其所以然,本文章知识也是Android日常瘦身的的必备知识.

  • Go语言defer的一些神奇规则示例详解

    目录 测试题 分析 规则一当defer被声明时,其参数就会被实时解析 规则二 defer可能操作主函数的具名返回值 规则三 延迟函数执行按后进先出顺序执行 坑实例 测试题 defer有一些规则,如果不了解,代码实现的最终结果会与预期不一致.对于这些规则,你了解吗? 这是关于defer使用的代码,可以先考虑一下返回值. package main import ( "fmt" ) /** * @Author: Jason Pang * @Description: 快照 */ func de

  • go variant底层原理深入解析

    目录 varint benchmarks struct variant 为什么 variant 要比 plainstruct 快 variant 可能的优化? varint 今天本来在研究 OpenTelemetry 的基准性能测试 github.com/zdyj3170101…,测试不同网络协议:grpc, grpc-stream, http, websocket 在发送不同大小数据包时消耗 cpu,吞吐 的区别,由 tigrannajaryan 这位大神所写. 好奇翻了翻该大神的 githu

  • Go语言编程通过dwarf获取内联函数

    目录 dwarf组成 如何将 addr 转换为行号 内联函数 如何展开内联函数 使用 parca 展开内联函数 parca 输出有以下问题 dwarf组成 dwarf 由 The Debugging Information Entry . type Entry struct { Offset Offset Tag Tag // 描述其类型 Children bool Field []Field // 包含的字段 } 不同的 entry 有不同的类型: tag compile unit, 在 go

  • Golang Copier入门到入坑探究

    目录 正文 安装 快速入门 入坑 再探坑出坑 再盘一盘坑 结语 正文 github: https://github.com/jinzhu/copier 由于 golang 没有对复杂结构体的 clone 方法,所以,就需要有 copier 这样的工具库. 它看起来很简单,但实际使用中,有些“坑”还是要注意! 本文: 入门为辅,探“坑”为主, 看完再划走,CS我没有. 安装 go get github.com/jinzhu/copier 快速入门 好的,来一段代码快速了解 copier packa

  • 详解超简单的react服务器渲染(ssr)入坑指南

    前言 本文是基于react ssr的入门教程,在实际项目中使用还需要做更多的配置和优化,比较适合第一次尝试react ssr的小伙伴们.技术涉及到 koa2 + react,案例使用create-react-app创建 SSR 介绍 Server Slide Rendering,缩写为 ssr 即服务器端渲染,这个要从SEO说起,目前react单页应用HTML代码是下面这样的 <!DOCTYPE html> <html lang="en"> <head&g

  • Golang的strings.Split()踩坑记录

    目录 背景 场景 前置 排查 验证 打印底层信息 追源码 类似情况 总结 背景 工作中,当我们需要对字符串按照某个字符串切分成字符串数组数时,常用到strings.Split() 最近在使用过程中踩到了个坑,后对踩坑原因做了分析,并总结了使用string.Split可能踩到的坑.最后写本篇文章做复盘总结与分享 场景 当时是需要取某个结构体的某个属性,并将其按,切分 整体逻辑类似这样的 type Info struct{ Ids string // Ids: 123,456 } func test

  • SpringBoot入坑笔记之spring-boot-starter-web 配置文件的使用

    经过上一篇的介绍,相信小伙伴们已经按奈不住内心对springboot的向往,本篇我将继续向小伙伴介绍springboot配置文件的配置,已经全局配置参数如何使用,好了下面开始我们今天的内容介绍. 我们知道Spring Boot支持容器的自动配置,默认是Tomcat,当然我们也是可以进行修改的: 1.首先我们排除spring-boot-starter-web依赖中的Tomcat:在pom文件中排除tomcat的starter <dependency> <groupId>org.spr

  • 浅谈Vue3.0新版API之composition-api入坑指南

    关于VUE3.0 由于vue3.0语法跟vue2.x的语法几乎是完全兼容的,本文主要介绍了如何使用composition-api,主要分以下几个方面来讲 使用vite体验vue3.0 composition-api解决了什么问题 语法糖介绍 vite的安装使用 vite仓库地址 https://github.com/vuejs/vite 上面有详细的安装使用教程,按照步骤安装即可 composition-api解决了什么问题 使用传统的option配置方法写组件的时候问题,随着业务复杂度越来越高

  • datagrip入坑指南(如何找到数据库和表)

    用习惯了navicat,一直喜欢如下界面,可以方便查看数据库及表的信息. 但是不知道为什么navicat总是无法连接oracle,因此转战datagrip. 第一次使用datagrip,用户体验可谓是相当差了(大佬莫喷,对于我的使用习惯来说,这款软件用户体验真的极差.) 琢磨了半天,都不知道怎么弄成navicat那种界面. 以下是入坑教程:包括新建项目,连接数据库,已经查看数据库及表的内容. 1.新建项目: 2.连接数据库 点击 File-->Data Sources 点击左上角绿色小加号,选择

  • Python带你从浅入深探究Tuple(基础篇)

    元组 Python中的元组容器序列(tuple)与列表容器序列(list)具有极大的相似之处,因此也常被称为不可变的列表. 但是两者之间也有很多的差距,元组侧重于数据的展示,而列表侧重于数据的存储与操作. 它们非常相似,虽然都可以存储任意类型的数据,但是一个元组定义好之后就不能够再进行修改. 元组特性 元组的特点: 元组属于容器序列 元组属于不可变类型 元组底层由顺序存储组成,而顺序存储是线性结构的一种 基本声明 以下是使用类实例化的形式进行对象声明: tup = tuple((1, 2, 3,

  • Java十道入门易踩坑题分析前篇

    目录 1,java基本类型 2,java包装类 3,Java编译 4,JDK,JVM,JRE 5,类型转换 6,转义字符 7,标识符 8,类型转换 9,赋值符号 10,打印一个字符串 1,java基本类型 下面属于java基本数据类型的有( ) A.String B.byte C.char D.Array Java基本数据类型分为三种,数值型,字符型,布尔型 数值型: 整型:byte.short.int.long 浮点型:double.float 字符型:char 布尔型:boolean Arr

  • Java十道入门易踩坑题分析后篇

    目录 一,写在前面 二,代码分析 代码分析① 代码分析② 代码分析③ 代码分析④ 代码分析⑤ 代码分析⑥ 代码分析⑦ 代码分析⑧ 代码分析⑨ 代码分析⑩ 一,写在前面 本篇主要讲类和对象这一章节的踩坑题,这一章也相对复杂和难理解,面试也是常考的一类题,我分析的都是比较经典的,读者觉得自己学习的不够扎实,可以收藏,如果觉得写发不错的话,求个免费的赞,谢谢! 二,代码分析 代码分析① 阅读如下代码. 请问,对语句行 test.hello(). 描述正确的有() package NowCoder; c

  • Golang实现可重入锁的示例代码

    目录 什么是可重入锁 具体实现 项目中遇到了可重入锁的需求和实现,具体记录下. 什么是可重入锁 我们平时说的分布式锁,一般指的是在不同服务器上的多个线程中,只有一个线程能抢到一个锁,从而执行一个任务.而我们使用锁就是保证一个任务只能由一个线程来完成.所以我们一般是使用这样的三段式逻辑: Lock();DoJob();Unlock(); 但是由于我们的系统都是分布式的,这个锁一般不会只放在某个进程中,我们会借用第三方存储,比如 Redis 来做这种分布式锁.但是一旦借助了第三方存储,我们就必须面对

随机推荐