golang在struct间复制数据并忽略字段大小写与类型差异

2 阅读3分钟

一、背景和意义

在golang开发中,表现层、业务层、数据层往往定义了各自的struct,经常需要在这些struct间复制数据,复制数据时可能需要忽略字段大小写差异、以及字段类型差异等。本文给出用copier框架实现数据复制的示例。

二、copier框架入门示例

copier框架使用示例如下:

package main

import (
   "fmt"
   "github.com/jinzhu/copier"
   "strconv"
)

type UserReq struct {   // 表现层或API层中的用户数据
   UserId   string
   NickName string
   Age      int32
   Salary   int
}

type UserEntity struct { // 数据库层中的用户数据
   UserID   int
   Nickname string
   Age      int32
   Salary   int
}

func main() {
   userReq := UserReq{
      UserId:   "123",
      NickName: "张三",
      Age:      18,
      Salary:   200000,
   }

   var userEntity = UserEntity{}
   err := copier.Copy(&userEntity, userReq)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Printf("默认模式下的复制结果: %+v \n", userEntity)
   }
}

上述代码的输出结果如下:

默认模式下的复制结果: {UserID:0 Nickname:张三 Age:18 Salary:200000} 

三、代码解读

在前面的例子中定义了两个存储用户数据的struct,一个是表现层或API层的,另一个是数据库层的。在这个struct在定义时可能由于开发人员的习惯不一样,导致字段上有一些差异。

首先是字段名不一致,UserId和UserID存在大小写差异,Nickname和NickName也一样。

其次就是字段类型存在差异,例如UserReq中的用户ID是字符串,而UserEntity中的用户ID是数字。

从前面的例子看出,copier框架默认情况下会忽略大小写差异,NickName到Nickname之间会正常地复制数据。但字段类型存在差异时没有复制,所以用户ID没有被复制。

四、忽略数字与字符串类型的差异

copier在复制数据时,可以增加Converters,自动地从数字转到字符串或者从字符串转到数字。代码优化为:

package main

import (
	"fmt"
	"github.com/jinzhu/copier"
	"strconv"
)

type UserReq struct { // 表现层或API层中的用户数据
	UserId   string
	NickName string
	Age      int32
	Salary   int
}

type UserEntity struct { // 数据库层中的用户数据
	UserID   int
	Nickname string
	Age      int32
	Salary   int
}

func main() {
	userReq := UserReq{
		UserId:   "123",
		NickName: "张三",
		Age:      18,
		Salary:   200000,
	}

	var userEntity = UserEntity{}
	err := copier.Copy(&userEntity, userReq)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("默认模式下的复制结果: %+v \n", userEntity)
	}

	userEntity = UserEntity{}
	option := copier.Option{Converters: []copier.TypeConverter{
		{
			// 源struct是字符串,目标struct是int时,自动转换
			SrcType: "",
			DstType: 0,
			Fn: func(src interface{}) (interface{}, error) {
				srcStr, ok := src.(string)
				if !ok {
					return 0, nil
				}
				return strconv.Atoi(srcStr)
			},
		},
		{
			// 源struct是int,目标struct是字符串时,自动转换
			SrcType: 0,
			DstType: "",
			Fn: func(src interface{}) (interface{}, error) {
				srcInt, ok := src.(int)
				if !ok {
					return 0, nil
				}
				return strconv.Itoa(srcInt), nil
			},
		},
	}}
	err = copier.CopyWithOption(&userEntity, userReq, option)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("忽略字符串与数字差异的复制结果:%+v \n", userEntity)
	}
}

程序运行结果为:

默认模式下的复制结果: {UserID:0 Nickname:张三 Age:18 Salary:200000} 
忽略字符串与数字差异的复制结果:{UserID:123 Nickname:张三 Age:18 Salary:200000}

虽然用户ID类型不同,但也被复制了。