golang高性能结构体映射与松散类型转换插件库set的使用

Golang高性能结构体映射与松散类型转换插件库set的使用

概述

set是一个高性能的reflect包装库,支持松散类型转换、结构体映射和填充,以及切片构建。

类型强制转换

类型强制转换允许从松散的数据源(例如传入的字符串数据)赋值给强类型的Go类型。

var t T           // T是目标数据类型,t是该类型的变量
var s S           // S是源数据类型,s是该类型的变量
set.V(&t).To(s)   // 使用"尽力而为"的方法将s设置到t中

使用Getter填充结构体

set.Value有两个方法FillFillByTag,它们使用set.Getter接口作为数据提供者来填充结构体及其层次结构。

为了方便:

  • set.GetterFunc允许普通函数作为set.Getter使用,类似于http.HandlerFunc
  • set.MapGetter允许map[string]Tmap[interface{}]T作为set.Getter使用

结构体映射

set.Mapper是一个强大且高度可配置的结构体映射工具。

映射器将遍历结构体层次结构并创建友好名称遍历信息1:1映射。然后可以使用这些友好名称来定位结构体值及其层次结构中的相关字段。

set.Mapper包含多个配置字段,可用于完全自定义生成的友好名称

  • 选择嵌套名称的组合方式:VendorNameVendor_NameVendor.Namevendor_name
  • 按优先级顺序指定多个标签:db标签值可以比json标签具有更高的优先级
  • 将类型提升到更高的命名空间
  • 指定被忽略且不映射的类型
  • 指定被视为标量的类型:对于sql.Null*类型或类似类型很有用

BoundMapping和PreparedMapping

创建set.Mapper后,它可以返回绑定到Go结构体的BoundMappingPreparedMapping类型。BoundMappingPreparedMapping通过映射器生成的友好名称提供对绑定数据的高性能访问。

BoundMapping提供对结构体字段的临时访问;每个方法都接受要访问的字段的映射名称。

PreparedMapping类似于预准备的SQL语句,访问计划必须通过调用其Plan方法来设置。

性能说明

reflect包总是比不使用reflect的代码慢。本包在设计实现上花费了大量精力来减少reflect开销:

  • reflect数据通常只在第一次遇到类型时收集一次(通过reflect.TypeOf,reflect.ValueOf)。这些数据被缓存并在以后遇到重复类型时从缓存中检索
  • 赋值通常首先尝试类型切换,然后回退到reflect。这种策略在类型强制转换中被大量使用
  • 本包中的适当类型有Rebind方法。Rebind将交换"绑定"的Go类型与新传入的实例,而无需进行额外的昂贵reflect调用

此外,本包尝试低分配以避免压倒垃圾收集器:

  • BoundMapping和PreparedMapping的某些方法允许预分配目标切片
  • BoundMapping、PreparedMapping和Value作为结构体而不是指针创建和返回

完整示例

package main

import (
	"fmt"
	"github.com/nofeaturesonlybugs/set"
)

type Person struct {
	Name    string
	Age     int
	Address struct {
		Street string
		City   string
	}
}

func main() {
	// 类型强制转换示例
	var age int
	set.V(&age).To("42") // 字符串转int
	fmt.Println("Age:", age) // 输出: Age: 42

	// 结构体映射示例
	p := Person{}
	mapper := set.Mapper{
		Join: ".", // 使用点号连接嵌套字段名
	}
	mapping := mapper.Map(&p)

	// 使用映射填充结构体
	data := map[string]interface{}{
		"Name":            "John Doe",
		"Age":             "30", // 字符串会自动转换
		"Address.Street": "123 Main St",
		"Address.City":   "Anytown",
	}

	bound := mapping.Bind(&p)
	for k, v := range data {
		if err := bound.Set(k, v); err != nil {
			panic(err)
		}
	}

	fmt.Printf("%+v\n", p)
	// 输出: {Name:John Doe Age:30 Address:{Street:123 Main St City:Anytown}}

	// 使用Getter填充结构体
	getter := set.MapGetter(data)
	if err := set.V(&p).FillByTag(getter, "set"); err != nil {
		panic(err)
	}
	fmt.Printf("%+v\n", p)
}

这个示例展示了set库的主要功能:

  1. 类型强制转换(字符串"42"转int)
  2. 结构体映射和通过映射名称设置值
  3. 使用MapGetter和FillByTag填充结构体

更多关于golang高性能结构体映射与松散类型转换插件库set的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang高性能结构体映射与松散类型转换插件库set的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang高性能结构体映射与松散类型转换插件库set的使用

在Go语言开发中,我们经常需要在不同类型之间进行转换和映射,特别是在处理JSON数据、数据库记录或API响应时。set是一个高性能的Go语言库,专门用于简化结构体映射和松散类型转换。下面我将详细介绍这个库的使用方法。

1. set库简介

set库提供了以下主要功能:

  • 在不同类型之间进行值转换
  • 结构体之间的字段映射
  • 松散类型转换(如string到int)
  • 支持自定义转换规则

2. 安装set库

go get github.com/elliotchance/orderedmap
go get github.com/fatih/set

3. 基本使用示例

3.1 简单类型转换

package main

import (
	"fmt"
	"github.com/fatih/set"
)

func main() {
	// 创建一个新的set
	s := set.New(set.ThreadSafe)
	
	// 添加元素
	s.Add("hello")
	s.Add(123)
	s.Add(45.67)
	s.Add(true)
	
	// 检查元素是否存在
	if s.Has("hello") {
		fmt.Println("Set contains 'hello'")
	}
	
	// 遍历所有元素
	s.Each(func(item interface{}) bool {
		fmt.Printf("Item: %v, Type: %T\n", item, item)
		return true // 返回false会停止遍历
	})
	
	// 移除元素
	s.Remove(123)
}

3.2 结构体映射

package main

import (
	"fmt"
	"github.com/fatih/structs"
)

type User struct {
	Name string
	Age  int
}

type UserDTO struct {
	Name     string
	Age      int
	IsActive bool `structs:"-"`
}

func main() {
	user := User{Name: "Alice", Age: 25}
	
	// 将User结构体转换为map
	m := structs.Map(user)
	fmt.Println(m) // map[Age:25 Name:Alice]
	
	// 将map转换为UserDTO结构体
	var dto UserDTO
	structs.FillMap(m, &dto)
	fmt.Printf("%+v\n", dto) // {Name:Alice Age:25 IsActive:false}
}

4. 高级特性

4.1 自定义转换规则

package main

import (
	"fmt"
	"github.com/fatih/structs"
	"reflect"
	"strconv"
)

type Product struct {
	ID   string
	Name string
}

type ProductDTO struct {
	ID   int
	Name string
}

func stringToIntHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
	if f.Kind() == reflect.String && t.Kind() == reflect.Int {
		return strconv.Atoi(data.(string))
	}
	return data, nil
}

func main() {
	product := Product{ID: "123", Name: "Laptop"}
	var dto ProductDTO
	
	// 注册自定义hook
	structs.DefaultTagName = "json"
	structs.AddHook(stringToIntHook)
	
	// 执行转换
	structs.FillMap(product, &dto)
	fmt.Printf("%+v\n", dto) // {ID:123 Name:Laptop}
}

4.2 松散类型转换

package main

import (
	"fmt"
	"github.com/fatih/set"
)

func main() {
	s := set.New(set.NonThreadSafe)
	
	// 添加不同类型元素
	s.Add("123") // string
	s.Add(456)   // int
	
	// 获取所有元素为int
	ints := set.IntSlice(s)
	fmt.Println(ints) // [123 456]
	
	// 获取所有元素为string
	strings := set.StringSlice(s)
	fmt.Println(strings) // ["123" "456"]
}

5. 性能优化建议

  1. 选择合适的set类型

    • set.ThreadSafe:线程安全但性能较低
    • set.NonThreadSafe:非线程安全但性能高
  2. 批量操作

    s.Add("a", "b", "c") // 比多次调用Add性能更好
    
  3. 预分配大小

    s := set.New(set.ThreadSafe)
    s.SetSize(1000) // 预分配空间
    

6. 实际应用场景

6.1 API请求/响应处理

func handleRequest(data map[string]interface{}) (User, error) {
	var user User
	if err := structs.FillMap(data, &user); err != nil {
		return User{}, err
	}
	return user, nil
}

6.2 数据库记录映射

func mapDBRowToStruct(row map[string]interface{}, dest interface{}) error {
	return structs.FillMap(row, dest)
}

7. 注意事项

  1. 类型转换失败时不会panic,而是返回默认值
  2. 结构体字段需要是导出的(首字母大写)
  3. 复杂嵌套结构可能需要自定义hook处理

set库为Go开发者提供了灵活高效的类型转换和结构体映射解决方案,特别适合处理动态数据和不同数据源之间的交互。通过合理使用,可以显著减少样板代码,提高开发效率。

回到顶部