本文主要介绍 jinzhu/copier 的使用和常用场景

简介

在go后端项目开发中,内部rpc服务返回的字段跟api服务相差无几,一个个赋值比较费事儿。那么就需要Object、List、HashMap 等进行值拷贝。jinzhu/copier即提供了这些场景的支持。

copier 特性

  • 从方法复制到具有相同名称的字段
  • 从字段复制到具有相同名称的方法
  • 从一个切片复制到另一个切片
  • 从结构体复制到切片
  • 从map复制到map
  • 强制复制带有标记的字段
  • 忽略带有标记的字段
  • 深拷贝

Usage

Copy from struct

package main

import (
	"github.com/jinzhu/copier"
	"testing"
)

type User struct {
	Name         string
	Role         string
	Age          int32
	EmployeeCode int64 `copier:"EmployeeNum"` // specify field name

	// Explicitly ignored in the destination struct.
	Salary int
}

//目标结构体中的标签提供了copy指令。复制忽略
//或强制复制,如果字段没有被复制则惊慌或返回错误。
type Employee struct {
	//告诉copier。如果没有复制此字段,则复制到panic。
	Name string `copier:"must"`

	//告诉copier。 如果没有复制此字段,则返回错误。
	Age int32 `copier:"must,nopanic"`

	// 告诉copier。 显式忽略复制此字段。
	Salary int `copier:"-"`

	DoubleAge  int32
	EmployeeId int64 `copier:"EmployeeNum"` // 指定字段名
	SuperRole  string
}

func TestCopyStruct(t *testing.T) {
	var (
		user     = User{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 200000}
		employee = Employee{Salary: 150000}
	)
	copier.Copy(&employee, &user)
	t.Logf("%#v \n", employee)
}

output:

    copier_test.go:47: main.Employee{Name:"Jinzhu", Age:18, Salary:150000, DoubleAge:36, EmployeeId:0, SuperRole:"Super Admin"} 

Copy from slice to slice

func TestCopySlice(t *testing.T) {
	var (
		users     = []User{{Name: "Jinzhu", Age: 18, Role: "Admin", Salary: 100000}, {Name: "jinzhu 2", Age: 30, Role: "Dev", Salary: 60000}}
		employees = []Employee{}
	)
	employees = []Employee{}
	copier.Copy(&employees, &users)

	t.Logf("%#v \n", employees)
}`

output :

    copier_test.go:57: []main.Employee{main.Employee{Name:"Jinzhu", Age:18, Salary:0, DoubleAge:36, EmployeeId:0, SuperRole:"Super Admin"}, main.Employee{Name:"jinzhu 2", Age:30, Salary:0, DoubleAge:60, EmployeeId:0, SuperRole:"Super Dev"}} 

Copy from Map to Map

func TestCopyMap(t *testing.T) {
	// Copy map to map
	map1 := map[int]int{3: 6, 4: 8}
	map2 := map[int32]int8{}
	copier.Copy(&map2, map1)

	t.Logf("%#v \n", map2)
}

output :

    copier_test.go:66: map[int32]int8{3:6, 4:8} 

场景 1(rpc&api)

实际开发中,免不了服务间通讯,比较前文所说的场景,一个内部的rpc服务返回的参数和api服务差不多,那么就可以使用copier。

//伪代码如下
func ApiLogin(ctx context.Context,request *api.LoginRequest)(reply *api.LogingReply,err error)  {
	grpcClient := v1.NewGameGrpcClient(ctx)
	reply, err := client.Login(ctx, &grpc.api.LoginRequest{ 
					})
	user := api.LogingReply.User{}
 	copier.Copy(&user, reply.User())
return &api.LoginReply{
	User:user,
},err

场景 2 (model-object/aggregate)

实际开发中,不管是mvc\ddd 都会有从model到object/aggreate的repository,那么就可以使用copier。

func (r *UserRepo) Get(ctx context.Context, uid int64) (u User,err error) {
	model, err := db.User.Get(ctx, uid)
	if err != nil {
		return
	}
	obj:= User{}
	copy(&obj,model)
	return obj,nil
}

小结

copier提供不同类型之间相同的字段名,使用tag或者方法支持不同的字段名的赋值。减少一些重复的工作量,小巧实用。

参考