dynamic-struct/README.md
2025-04-01 15:35:34 +08:00

95 lines
3.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Golang 运行时动态结构体
## 简介
主要提供在运行时动态生成结构体的能力, 同时支持合并多个已有结构体,生成一个新的结构体
主要功能如下:
* 运行时动态生成结构体, 更灵活
* 运行时继承已有结构体的结构
* 运行时合并多个结构体
* 向结构体中新增字段
* 移除结构体中指定字段
* 修改已存在字段的类型以及Tag标签
* 读取动态结构体字段的Helper
* 动态结构体的值, 解析到一个已定义的结构体中
* 动态结构体生成slice或者map实例
* 支持嵌套的结构体
* 支持N维数组
* 支持嵌套的结构体数组
## 开发背景
开发个人网关项目时, 面临一个问题: **`如何验证网关请求参数的合法性?`** 在Golang生态中,比较知名的参数验证库是 [https://github.com/go-playground/validator](go-playground/validator/v10)。 其支持的参数验证方式如下:
- 单个值按照指定规则进行验证, 无上下文信息
- map数据验证, 无上下文信息, 可以看做是单个值的验证的批量版本
- 结构体数据验证, 支持上下文信息, 可以跨结构体字段进行关联验证
网关参数验证的需求背景如下:
- 基础的参数验证, 如取值范围、长度等
- 数据格式校验IP地址、URL、邮箱、手机号等
- 数据关联校验两个字段的值必须相等、A字段不存在,则B字段必须存在等
- ......
综合数据验证库提供的验证能力, 以及业务场景的复杂性, 需要使用结构体数据进行参数验证。那么, 此时则面临另外一个问题: 不同的网关接口是有不同的参数结构的, 如何在运行时动态生成结构体呢? 这就需要用到本文所介绍的动态结构体生成能力。
## 快速开始
```bash
# 安装
go get -v -u github.com/liuxiaobopro/golang-dynamic-struct
```
```go
package main
import (
"fmt"
dynamicstruct "git.zhangdeman.cn/zhangdeman/dynamic-struct"
"github.com/go-playground/validator/v10"
"fmt"
)
func main() {
// 动态生成一个结构体
ds := dynamicstruct.NewStruct()
ds = ds.AddField("name", "", `json:"name" validate:"required"`)
ds = ds.AddField("age", 0, `json:"age" validate:"gte=0,lte=130"`)
ds = ds.AddField("email", "", `json:"email" validate:"required,email"`)
// 生成结构体实例
user := ds.Build().New()
userTestData := `{
"name": "张三",
"age": 180,
"email": "test@qq.com",
}`
validatorInstance = validator.New()
validatorInstance.SetTagName("validate")
err := validatorInstance.Struct(user)
fmt.Println(err)
}
```
## 实现原理
无论任何语言, 但凡出现 **`动态`** 这一类字眼, 实现原理一定脱离不了 **`反射`** , 且 **`反射是核心实现技术`** , 本库中的实现原理也不例外, 也是基于反射进行实现的。核心逻辑是利用 **`reflect.New`** 这一泛实例化数据的机制进行实现. 确定原理方向, **`抽象的需求运行时动态生成不定结构的结构体`** 已经转化为具体功能诉求, **`如何利用reflect.New这一机制, 动态实例化结构体`**
## 具体实现
核心就是一句代码:
```go
structFields := []reflect.StructField
reflect.New(reflect.StructOf(structFields))
```
其中, **`structFields`** 是一个 **`一个结构体字段配置列表,其可配置的属性具体查看即可`**, **`reflect.StructOf`** 则是根据结构体字段配置反射出结构体类型, 利用 **`reflect.New`** 这一机制, 可以动态实例化一个 **`结构体`**。
核心逻辑就是这么简单, 但是, 动态生成结构体的过程中, 还需要考虑到很多其他的问题, 如:
- 如何动态生成嵌套的结构体
- 如何动态生成嵌套的结构体数组
- 如何动态生成N维数组
- 如何动态生成结构体数组
其实上述逻辑无非是基于最原始的基础逻辑, **`递归进行实现`** , 所以, 具体实现细节, 可以查看源码。