如何用Golang反射实现通用拷贝函数

来源:IPIPP.com作者:头衔:全栈工程师
导读:本期聚焦于小伙伴创作的《如何用Golang反射实现通用拷贝函数》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何用Golang反射实现通用拷贝函数》有用,将其分享出去将是对创作者最好的鼓励。

在Golang日常开发中,结构体数据拷贝是非常常见的需求,如果是同类型结构体,手动赋值还比较方便,但如果是不同类型但字段相似的结构体,逐个字段赋值就非常繁琐。这时可以利用反射机制实现通用拷贝函数,适配多种结构体拷贝场景。

如何用Golang反射实现通用拷贝函数

核心实现思路

通用拷贝函数的核心逻辑是通过反射获取源数据和目标数据的类型、值信息,然后遍历源数据的字段,匹配目标数据中同名字段,将源字段的值拷贝到目标字段。主要步骤分为以下几步:

  • 校验源数据和目标数据的类型,确保两者都是结构体或者结构体指针
  • 通过反射获取源数据和目标数据的实际值,处理指针类型的入参
  • 遍历源结构体的所有字段,获取字段名和可导出状态
  • 在目标结构体中查找同名字段,判断字段类型是否可赋值
  • 将源字段的值拷贝到目标字段,跳过不可导出字段和类型不匹配的字段

完整代码实现

下面是使用Golang反射实现的通用拷贝函数完整代码,包含基础的类型校验和字段拷贝逻辑:

package main

import (
	"fmt"
	"reflect"
)

// CopyStruct 通用结构体拷贝函数,src为源数据,dst为目标数据指针
func CopyStruct(src, dst interface{}) error {
	// 获取src的反射值
	srcValue := reflect.ValueOf(src)
	// 如果是指针,获取指向的值
	if srcValue.Kind() == reflect.Ptr {
		srcValue = srcValue.Elem()
	}
	// 校验src必须是结构体
	if srcValue.Kind() != reflect.Struct {
		return fmt.Errorf("src must be struct or struct pointer")
	}

	// 获取dst的反射值
	dstValue := reflect.ValueOf(dst)
	// dst必须是指针
	if dstValue.Kind() != reflect.Ptr {
		return fmt.Errorf("dst must be struct pointer")
	}
	// 获取指针指向的值
	dstElem := dstValue.Elem()
	// 校验dst指向的必须是结构体
	if dstElem.Kind() != reflect.Struct {
		return fmt.Errorf("dst must point to struct")
	}

	// 获取src的类型信息
	srcType := srcValue.Type()

	// 遍历src的所有字段
	for i := 0; i < srcValue.NumField(); i++ {
		srcField := srcValue.Field(i)
		srcFieldType := srcType.Field(i)
		// 跳过不可导出字段
		if !srcFieldType.IsExported() {
			continue
		}
		// 在dst中查找同名字段
		dstField := dstElem.FieldByName(srcFieldType.Name)
		// 如果dst中不存在该字段,跳过
		if !dstField.IsValid() {
			continue
		}
		// 如果dst字段不可导出,跳过
		if !dstField.CanSet() {
			continue
		}
		// 判断src字段的值是否可以赋值给dst字段
		if srcField.Type().AssignableTo(dstField.Type()) {
			dstField.Set(srcField)
		}
	}
	return nil
}

// 源结构体
type UserSrc struct {
	Name  string
	Age   int
	Email string
}

// 目标结构体
type UserDst struct {
	Name  string
	Age   int
	Email string
}

// 字段不同的目标结构体
type UserDst2 struct {
	Name     string
	Age      int
	Email    string
	Address  string // 源结构体中不存在的字段
	phone    string // 不可导出字段,不会被拷贝
}

func main() {
	src := UserSrc{
		Name:  "张三",
		Age:   25,
		Email: "test@ipipp.com",
	}
	var dst UserDst
	err := CopyStruct(src, &dst)
	if err != nil {
		fmt.Println("拷贝失败:", err)
		return
	}
	fmt.Printf("拷贝结果: %+v\n", dst)

	var dst2 UserDst2
	err = CopyStruct(src, &dst2)
	if err != nil {
		fmt.Println("拷贝失败:", err)
		return
	}
	fmt.Printf("拷贝到不同结构体结果: %+v\n", dst2)
}

代码说明

上述代码中的CopyStruct函数首先会对入参类型做校验,确保源数据是结构体或结构体指针,目标数据是结构体指针。接着通过反射遍历源结构体的所有可导出字段,在目标结构体中查找同名字段,仅当字段类型可直接赋值时才执行拷贝操作。

需要注意,如果源结构体和目标结构体的对应字段类型不同,比如一个是int一个是int64,这种实现不会做类型转换,会直接跳过该字段。如果需要支持类型转换,可以在判断赋值逻辑处添加类型转换的相关处理。

注意事项

  • 反射会有一定的性能损耗,如果对性能要求极高的场景,不建议频繁使用通用拷贝函数,可手动编写专用拷贝逻辑
  • 该实现仅支持浅拷贝,如果结构体包含切片、映射、指针等引用类型字段,拷贝后源和目标会共享这些引用类型的数据
  • 不可导出字段不会被拷贝,这是Golang反射的机制限制,无法修改不可导出字段的值
  • 如果目标结构体中不存在源结构体的某个字段,该字段会被自动跳过,不会报错

Golang反射通用拷贝函数struct拷贝修改时间:2026-05-26 00:21:56

免责声明:已尽一切努力确保本网站所含信息的准确性。网站部分内容来源于网络或由用户自行发表,内容观点不代表本站立场。本站是个人网站免费分享,内容仅供个人学习、研究或参考使用,如内容中引用了第三方作品,其版权归原作者所有。若内容触犯了您的权益,请联系我们进行处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。前端、网络、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握网站开发与运维所需的核心技术栈。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端逻辑,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。