在Golang开发中,序列化是将内存中的数据结构转换为可存储或传输格式的重要操作,手动为每一个结构体编写序列化逻辑会消耗大量精力,利用反射机制可以实现通用的序列化工具,适配各类自定义结构体。

反射基础回顾
Golang的反射能力由reflect包提供,核心类型包括reflect.Type和reflect.Value,前者用于获取类型信息,后者用于获取值信息。要处理任意类型的输入,首先需要将输入转换为reflect.Value:
package main
import (
"fmt"
"reflect"
)
func main() {
type User struct {
Name string
Age int
}
u := User{Name: "张三", Age: 20}
val := reflect.ValueOf(u)
fmt.Println(val.Type()) // 输出main.User
}
基础序列化实现
我们先实现一个处理基础结构体字段的序列化函数,将结构体转换为键值对格式的字符串:
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
// Encode 通用序列化函数
func Encode(v interface{}) (string, error) {
val := reflect.ValueOf(v)
// 处理指针类型,获取指针指向的值
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// 只处理结构体类型
if val.Kind() != reflect.Struct {
return "", fmt.Errorf("不支持的类型: %v", val.Kind())
}
typ := val.Type()
var parts []string
// 遍历所有字段
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
// 拼接字段名和值
fieldName := field.Name
fieldValue := formatValue(fieldVal)
parts = append(parts, fmt.Sprintf("%s=%s", fieldName, fieldValue))
}
return strings.Join(parts, "&"), nil
}
// formatValue 格式化字段值为字符串
func formatValue(val reflect.Value) string {
switch val.Kind() {
case reflect.String:
return val.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(val.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(val.Uint(), 10)
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(val.Float(), 'f', -1, 64)
case reflect.Bool:
return strconv.FormatBool(val.Bool())
default:
return fmt.Sprintf("%v", val.Interface())
}
}
func main() {
type User struct {
Name string
Age int
Score float64
}
u := User{Name: "李四", Age: 25, Score: 95.5}
result, err := Encode(u)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result) // 输出Name=李四&Age=25&Score=95.5
}
支持结构体标签
实际使用中我们通常需要自定义序列化时的字段名,Golang结构体标签可以很好地支持这个需求,我们修改代码解析encode标签:
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func Encode(v interface{}) (string, error) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return "", fmt.Errorf("不支持的类型: %v", val.Kind())
}
typ := val.Type()
var parts []string
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
// 解析encode标签,优先使用标签值作为字段名
fieldName := field.Name
if tag := field.Tag.Get("encode"); tag != "" && tag != "-" {
fieldName = tag
} else if tag == "-" {
// 标签为-时跳过该字段
continue
}
fieldValue := formatValue(fieldVal)
parts = append(parts, fmt.Sprintf("%s=%s", fieldName, fieldValue))
}
return strings.Join(parts, "&"), nil
}
func formatValue(val reflect.Value) string {
switch val.Kind() {
case reflect.String:
return val.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(val.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(val.Uint(), 10)
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(val.Float(), 'f', -1, 64)
case reflect.Bool:
return strconv.FormatBool(val.Bool())
default:
return fmt.Sprintf("%v", val.Interface())
}
}
func main() {
type User struct {
Name string `encode:"username"`
Age int `encode:"user_age"`
Email string `encode:"-"`
Score float64
}
u := User{Name: "王五", Age: 30, Email: "test@ipipp.com", Score: 88.0}
result, err := Encode(u)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result) // 输出username=王五&user_age=30&Score=88.0
}
处理嵌套结构体
很多场景下结构体包含嵌套结构体的字段,我们需要递归处理嵌套结构,修改formatValue函数支持结构体类型:
package main
import (
"fmt"
"reflect"
"strconv"
"strings"
)
func Encode(v interface{}) (string, error) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return "", fmt.Errorf("不支持的类型: %v", val.Kind())
}
return encodeStruct(val)
}
// encodeStruct 处理结构体序列化
func encodeStruct(val reflect.Value) (string, error) {
typ := val.Type()
var parts []string
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldVal := val.Field(i)
fieldName := field.Name
if tag := field.Tag.Get("encode"); tag != "" && tag != "-" {
fieldName = tag
} else if tag == "-" {
continue
}
fieldValue, err := formatValue(fieldVal)
if err != nil {
return "", err
}
parts = append(parts, fmt.Sprintf("%s=%s", fieldName, fieldValue))
}
return strings.Join(parts, "&"), nil
}
// formatValue 格式化字段值,支持嵌套结构体
func formatValue(val reflect.Value) (string, error) {
switch val.Kind() {
case reflect.String:
return val.String(), nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(val.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return strconv.FormatUint(val.Uint(), 10), nil
case reflect.Float32, reflect.Float64:
return strconv.FormatFloat(val.Float(), 'f', -1, 64), nil
case reflect.Bool:
return strconv.FormatBool(val.Bool()), nil
case reflect.Struct:
// 递归处理嵌套结构体
return encodeStruct(val)
default:
return fmt.Sprintf("%v", val.Interface()), nil
}
}
func main() {
type Address struct {
City string `encode:"city"`
}
type User struct {
Name string `encode:"username"`
Age int `encode:"user_age"`
Addr Address `encode:"address"`
}
u := User{
Name: "赵六",
Age: 28,
Addr: Address{City: "北京"},
}
result, err := Encode(u)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(result) // 输出username=赵六&user_age=28&address=北京
}
注意事项与优化方向
使用反射构建序列化工具时需要注意几个问题:
- 反射操作相比直接代码有性能损耗,如果对性能要求极高的场景需要谨慎使用,或者增加缓存机制缓存类型信息减少重复反射开销。
- 需要处理结构体指针字段,避免空指针导致的panic,可以在格式化值时判断值是否可寻址。
- 当前实现仅支持基础类型和嵌套结构体,还可以扩展支持切片、映射等复合类型,进一步提升工具的通用性。
通过以上步骤,我们就实现了一个基础的Golang通用序列化Encoder,能够适配大部分常见的结构体序列化需求,开发者可以根据实际场景继续扩展功能。