一个功能强大的 Go 库,用于在不同数据结构之间进行双向数据转换,支持 map→struct、struct→map 和 struct→struct 转换。
- 双向数据转换:支持 map ↔ struct、struct ↔ struct 之间的无缝转换
- 类型安全转换:精确的类型检查和转换,确保数据完整性
- 弱类型输入支持:自动进行类型转换(如字符串 "30" → 整数 30)
- 灵活的标签配置:支持自定义标签名称和转换规则
- 嵌入结构体处理:自动处理嵌入结构体(包括指针嵌入)
- 元数据跟踪:记录转换过程中的关键信息(使用的键、未使用的键、未设置的字段)
- 字段名称转换:支持不同命名约定之间的转换(如驼峰式 → 蛇形命名)
- 错误处理:详细的错误信息,便于调试和排查问题
使用 Go 模块安装:
go get github.com/epkgs/copierpackage main
import (
"fmt"
"github.com/epkgs/copier"
)
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}
func main() {
input := map[string]any{
"name": "Edwin",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
},
}
var result Person
err := copier.Copy(&result, input)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// 输出:
// main.Person{Name:"Edwin", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
}package main
import (
"fmt"
"github.com/epkgs/copier"
)
type Person struct {
Name string
Age int
Emails []string
}
func main() {
input := map[string]any{
"name": 123, // 数字 → 字符串
"age": "42", // 字符串 → 数字
"emails": map[string]any{}, // 空 map → 空数组
}
var result Person
err := copier.Copy(&result, input, copier.WithWeaklyTypedInput(true))
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// 输出:main.Person{Name:"123", Age:42, Emails:[]string{}}
}package main
import (
"fmt"
"github.com/epkgs/copier"
)
// 源结构体
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Age int `json:"age"`
}
// 目标结构体
type Profile struct {
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
user := User{
FirstName: "John",
LastName: "Doe",
Email: "john.doe@example.com",
Age: 30,
}
var profile Profile
err := copier.Copy(&profile, user)
if err != nil {
panic(err)
}
fmt.Printf("%#v", profile)
// 输出:main.Profile{Email:"john.doe@example.com", Age:30}
}package main
import (
"fmt"
"github.com/epkgs/copier"
)
type User struct {
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Age int `json:"age"`
Active bool `json:"active"`
}
func main() {
user := User{
FirstName: "John",
LastName: "Doe",
Email: "john.doe@example.com",
Age: 30,
Active: true,
}
var result map[string]any
err := copier.Copy(&result, user)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// 输出:map[string]interface {}{"active":true, "age":30, "email":"john.doe@example.com", "first_name":"John", "last_name":"Doe"}
}将源数据转换并分配到目标数据结构中。
-
参数:
dst:目标数据结构(必须是指针)src:源数据结构(可以是 map、struct 等)opts:可选的配置选项函数,用于自定义转换行为
-
返回值:
error:转换过程中发生的错误,可能包含多个错误信息
库提供了一系列配置函数,用于自定义转换行为:
-
WithWeaklyTypedInput(enable bool)- 如果为 true,将执行弱类型转换(如字符串 "30" → 整数 30)
- 默认值:false
-
WithTagName(tagName string)- 用于字段映射的标签名称(默认:"json")
- 支持从 json / mapstructure 标签中获取映射名称和 omitempty 属性
- 示例:
copier.WithTagName("mapstructure")
-
WithIncludeIgnored(include bool)- 如果为 true,将包含被忽略的字段(通过 "-" 标签标记)
- 默认值:false
-
WithFieldNameConverter(structObj any, converter func(fieldName string) string)- 自定义 struct 字段名称转换函数,如未设定,默认为小驼峰模式
-
WithTypeConverter[Dst any, Src any](fn func(src Src) (dst Dst, err error))- 自定义类型转换器,用于在解码过程中转换特定类型的字段
-
WithMetadata(meta *Metadata)- 设置转换元数据,记录转换过程中的信息
-
WithDeepCopy(enable bool)- 如果为 true,将进行深层拷贝,否则仅拷贝第一层
- 默认值:false
type Metadata struct {
// 成功分配的键列表
Keys []string
// 源数据中未使用的键列表
Unused []string
// 目标结构中未设置的字段列表
Unset []string
}package main
import (
"fmt"
"github.com/epkgs/copier"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type User struct {
Name string
Age int
}
type UpdateUserRequest struct {
Name *string
Age *int
}
func UpdateUser(c *gin.Context){
var req UpdateUserRequest
_ = c.ShouldBindJSON(&req) // 简单展示,忽略绑定错误
var user User
var md copier.Metadata
_ = copier.Copy(&user, req, copier.WithMetadata(&md)) // 简单展示,忽略转换错误
gorm.G[User](db).Where("id = ?", 111).Select(md.Keys...).Updates(ctx, user) // 使用 md.Keys 过滤更新字段
}这是因为目标参数必须是一个指针,以便库可以修改其内容。请确保将目标参数的地址传递给 Copy 函数:
// 错误
var result Person
err := copier.Copy(result, input)
// 正确
var result Person
err := copier.Copy(&result, input)可以使用 WithFieldNameConverter 函数来自定义字段名称转换:
import (
"strings"
"github.com/epkgs/copier"
)
// 示例:将结构体字段转换为蛇形命名
func toSnakeCase(fieldName string) string {
var result strings.Builder
for i, r := range fieldName {
if i > 0 && r >= 'A' && r <= 'Z' {
result.WriteRune('_')
}
result.WriteRune(r)
}
return strings.ToLower(result.String())
}
// 使用示例
err := copier.Copy(&result, input,
copier.WithFieldNameConverter(resultType{}, toSnakeCase),
)可以在结构体标签中使用 -:
type User struct {
ID int `json:"id"`
Password string `json:"-"` // 这个字段将被忽略
}err := copier.Copy(
&result,
input,
copier.WithIncludeIgnored(User{}, []string{"Password"}),
)库会自动处理嵌入结构体(包括指针嵌入)。请确保嵌入结构体的字段是导出的(首字母大写):
type Base struct {
ID int
}
type User struct {
Base // 嵌入结构体,字段 ID 将被导出
Name string
}我们欢迎任何形式的贡献!如果您想为这个库做出贡献,请按照以下步骤进行:
- Fork 仓库:在 GitHub 上 fork 这个仓库
- 创建分支:为您的功能或修复创建一个新的分支
- 实现功能:编写代码并确保通过所有测试
- 提交代码:使用清晰的提交信息提交您的更改
- 创建 Pull Request:在 GitHub 上创建一个 Pull Request,描述您的更改
# 克隆仓库
git clone https://github.com/epkgs/copier.git
cd copier
# 安装依赖
go mod tidy
# 运行测试
go test ./...- 遵循 Go 语言的标准代码规范
- 确保所有新功能都有相应的测试
- 保持代码简洁、清晰和可维护
本库使用 MIT 许可证,详情请查看 LICENSE 文件。
如果您有任何问题或建议,请在 GitHub 上创建一个 Issue 或提交一个 Pull Request。
感谢您使用 copier 库!我们希望它能帮助您更轻松地处理数据转换任务。🎉