2025-04-01 15:35:34 +08:00
2025-03-23 17:51:13 +08:00
2025-03-23 20:54:35 +08:00
2025-03-23 17:51:13 +08:00
2025-04-01 14:41:13 +08:00
2025-04-01 14:41:13 +08:00
2025-03-19 15:27:14 +08:00
2025-03-19 15:27:14 +08:00
2025-04-01 15:35:34 +08:00

Golang 运行时动态结构体

简介

主要提供在运行时动态生成结构体的能力, 同时支持合并多个已有结构体,生成一个新的结构体

主要功能如下:

  • 运行时动态生成结构体, 更灵活
  • 运行时继承已有结构体的结构
  • 运行时合并多个结构体
  • 向结构体中新增字段
  • 移除结构体中指定字段
  • 修改已存在字段的类型以及Tag标签
  • 读取动态结构体字段的Helper
  • 动态结构体的值, 解析到一个已定义的结构体中
  • 动态结构体生成slice或者map实例
  • 支持嵌套的结构体
  • 支持N维数组
  • 支持嵌套的结构体数组

开发背景

开发个人网关项目时, 面临一个问题: 如何验证网关请求参数的合法性? 在Golang生态中,比较知名的参数验证库是 https://github.com/go-playground/validator。 其支持的参数验证方式如下:

  • 单个值按照指定规则进行验证, 无上下文信息
  • map数据验证, 无上下文信息, 可以看做是单个值的验证的批量版本
  • 结构体数据验证, 支持上下文信息, 可以跨结构体字段进行关联验证

网关参数验证的需求背景如下:

  • 基础的参数验证, 如取值范围、长度等
  • 数据格式校验IP地址、URL、邮箱、手机号等
  • 数据关联校验两个字段的值必须相等、A字段不存在,则B字段必须存在等
  • ......

综合数据验证库提供的验证能力, 以及业务场景的复杂性, 需要使用结构体数据进行参数验证。那么, 此时则面临另外一个问题: 不同的网关接口是有不同的参数结构的, 如何在运行时动态生成结构体呢? 这就需要用到本文所介绍的动态结构体生成能力。

快速开始

# 安装
go get -v -u github.com/liuxiaobopro/golang-dynamic-struct
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这一机制, 动态实例化结构体

具体实现

核心就是一句代码:

structFields := []reflect.StructField
reflect.New(reflect.StructOf(structFields))

其中, structFields 是一个 一个结构体字段配置列表,其可配置的属性具体查看即可, reflect.StructOf 则是根据结构体字段配置反射出结构体类型, 利用 reflect.New 这一机制, 可以动态实例化一个 结构体

核心逻辑就是这么简单, 但是, 动态生成结构体的过程中, 还需要考虑到很多其他的问题, 如:

  • 如何动态生成嵌套的结构体
  • 如何动态生成嵌套的结构体数组
  • 如何动态生成N维数组
  • 如何动态生成结构体数组

其实上述逻辑无非是基于最原始的基础逻辑, 递归进行实现 , 所以, 具体实现细节, 可以查看源码。

Description
Golang运行时动态生成结构体
Readme MIT 98 KiB
Languages
Go 100%