修复嵌套结构体 + 嵌套数组的BUG #3

Merged
zhangdeman merged 3 commits from feature/unit_test into master 2025-03-23 18:00:56 +08:00
7 changed files with 355 additions and 111 deletions

48
abstract.go Normal file
View File

@ -0,0 +1,48 @@
// Package dynamicstruct ...
//
// Description : dynamicstruct ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-03-23 13:34
package dynamicstruct
// IBuilder 运行时动态生成结构体的接口约束
type IBuilder interface {
// AddField 添加结构体字段
AddField(name string, pkg string, typ any, tag string, anonymous bool) IBuilder
// RemoveField 移除指定名称的结构体字段
RemoveField(name string) IBuilder
// HasField 检测指定名称的结构体字段是否存在
HasField(name string) bool
// GetField 根据名称获取结构体字段定义
GetField(name string) IFieldConfig
// Build 返回动态定义的结构体.
Build() IDynamicStruct
}
// IFieldConfig 结构体字段的定义.
type IFieldConfig interface {
// SetType 设置字段类型.
SetType(typ any) IFieldConfig
// SetTag 设置字段 tag.
SetTag(tag string) IFieldConfig
// GetType 获取类型
GetType() any
// GetTag 获取tag
GetTag() string
}
// IDynamicStruct contains defined dynamic struct.
// This definition can't be changed anymore, once is built.
// It provides a method for creating new instances of same defintion.
type IDynamicStruct interface {
// New 获取结构体实例, 所有字段值均为对应类型的初始零值
New() any
// NewSliceOfStructs slice实例化
NewSliceOfStructs() any
// NewMapOfStructs map 或者 struct实例化
NewMapOfStructs(key any) any
}

View File

@ -7,73 +7,22 @@ import (
"strings"
)
type (
// Builder 运行时动态生成结构体的接口约束
Builder interface {
// AddField 添加结构体字段
AddField(name string, pkg string, typ any, tag string, anonymous bool) Builder
// RemoveField 移除指定名称的结构体字段
RemoveField(name string) Builder
// HasField 检测指定名称的结构体字段是否存在
HasField(name string) bool
// GetField 根据名称获取结构体字段定义
GetField(name string) FieldConfig
// Build 返回动态定义的结构体.
Build() DynamicStruct
}
type nestedStruct struct {
Field string
Builder IBuilder
Tag string
}
// FieldConfig 结构体字段的定义.
FieldConfig interface {
// SetType 设置字段类型.
SetType(typ any) FieldConfig
// SetTag 设置字段 tag.
SetTag(tag string) FieldConfig
}
// DynamicStruct contains defined dynamic struct.
// This definition can't be changed anymore, once is built.
// It provides a method for creating new instances of same defintion.
DynamicStruct interface {
// New 获取结构体实例, 所有字段值均为对应类型的初始零值
New() any
// NewSliceOfStructs slice实例化
NewSliceOfStructs() any
// NewMapOfStructs map 或者 struct实例化
NewMapOfStructs(key any) any
}
nestedStruct struct {
Field string
Builder Builder
Tag string
}
builderImpl struct {
fields []*fieldConfigImpl
nestedStructTable map[string]nestedStruct // 嵌套结构体的定义, 父级路径 => 父级路径下挂接的子路径
maxFieldDeep int // 字段嵌套最大深度
structTagTable map[string]string // 字段路径 => json tag最高优先级, 没传的时候会使用AddField的tag, 也为空使用手搓json tag
}
fieldConfigImpl struct {
name string
pkg string
typ any
tag string
anonymous bool
}
dynamicStructImpl struct {
structFields []reflect.StructField
definition reflect.Type
}
)
type builderImpl struct {
fields []*fieldConfigImpl
nestedStructTable map[string]nestedStruct // 嵌套结构体的定义, 父级路径 => 父级路径下挂接的子路径
maxFieldDeep int // 字段嵌套最大深度
structTagTable map[string]string // 字段路径 => json tag最高优先级, 没传的时候会使用AddField的tag, 也为空使用手搓json tag
}
// NewStruct 获取builder实例
// 传入的tag映射表: 字段路径 => json tag最高优先级, 没传的时候会使用AddField的tag, 也为空使用手搓json tag
func NewStruct(structTagTable map[string]string) Builder {
func NewStruct(structTagTable map[string]string) IBuilder {
return &builderImpl{
fields: []*fieldConfigImpl{},
nestedStructTable: make(map[string]nestedStruct),
@ -82,12 +31,12 @@ func NewStruct(structTagTable map[string]string) Builder {
}
// ExtendStruct 基于已有结构体, 生成动态结构体(相当于继承指定的结构体属性)
func ExtendStruct(value ...any) Builder {
func ExtendStruct(value ...any) IBuilder {
return MergeStructs(value...)
}
// MergeStructs 多个结构体合并成一个动态结构体
func MergeStructs(values ...any) Builder {
func MergeStructs(values ...any) IBuilder {
builder := NewStruct(map[string]string{})
for _, value := range values {
@ -111,8 +60,8 @@ func (b *builderImpl) parseNestedField(fieldName string) ([]string, bool) {
}
// AddField 添加结构体字段
func (b *builderImpl) AddField(name string, pkg string, typ any, tag string, anonymous bool) Builder {
// 瞎话线转驼峰, 传入的name实际对应序列化时的json tag
func (b *builderImpl) AddField(name string, pkg string, typ any, tag string, anonymous bool) IBuilder {
// 下划线转驼峰, 传入的name实际对应序列化时的json tag
// name = wrapper.String(name).SnakeCaseToCamel()
if cfgTag, exist := b.structTagTable[name]; exist && len(cfgTag) > 0 {
tag = cfgTag
@ -191,7 +140,7 @@ func (b *builderImpl) addNestedField(nameArr []string, pkg string, typ any, tag
}
// RemoveField 根据名称移除结构体字段
func (b *builderImpl) RemoveField(name string) Builder {
func (b *builderImpl) RemoveField(name string) IBuilder {
newFieldList := make([]*fieldConfigImpl, 0)
for i := range b.fields {
if b.fields[i].name == name {
@ -214,7 +163,7 @@ func (b *builderImpl) HasField(name string) bool {
}
// GetField 根据名称获取字段配置, 不存在, 返回nil
func (b *builderImpl) GetField(name string) FieldConfig {
func (b *builderImpl) GetField(name string) IFieldConfig {
for i := range b.fields {
if b.fields[i].name == name {
return b.fields[i]
@ -224,7 +173,7 @@ func (b *builderImpl) GetField(name string) FieldConfig {
}
// Build 构建动态结构体
func (b *builderImpl) Build() DynamicStruct {
func (b *builderImpl) Build() IDynamicStruct {
// 按照嵌套深度, 进行一次回溯处理
for deep := b.maxFieldDeep - 1; deep > 0; deep-- {
// 嵌套数据结构
@ -240,7 +189,7 @@ func (b *builderImpl) Build() DynamicStruct {
} else {
// 处理数组
arrDeep := 0
newParentNameArr := []string{}
var newParentNameArr []string
for i := len(parentNameArr) - 1; i >= 0; i-- {
if parentNameArr[i] != ArraySplit {
newParentNameArr = parentNameArr[:i+1]
@ -257,18 +206,19 @@ func (b *builderImpl) Build() DynamicStruct {
val = reflect.New(reflect.SliceOf(reflect.TypeOf(val))).Interface()
}
// 解除指针引用
val = reflect.ValueOf(val).Elem().Interface()
// val = reflect.ValueOf(val).Elem().Interface()
newParamIndex := strings.Join(newParentNameArr, ".")
isTopIndex := len(newParentNameArr) == 1
if isTopIndex {
// 顶层结构, 数组类型不存在还有其他属性情况, 直接追加字段, 并移除嵌套表里的定义
b.AddField(b.nestedStructTable[newParamIndex].Field, "", val, b.nestedStructTable[newParamIndex].Tag, false)
delete(b.nestedStructTable, newParamIndex)
// b.nestedStructTable[newParamIndex].Builder.AddField(b.nestedStructTable[newParamIndex].Field, "", val, b.nestedStructTable[newParamIndex].Tag, false)
} else {
// 非顶层结构, 再上探一级
b.nestedStructTable[strings.Join(newParentNameArr[:len(newParentNameArr)-1], ".")].Builder.AddField(b.nestedStructTable[newParamIndex].Field, "", val, b.nestedStructTable[newParamIndex].Tag, false)
}
// 嵌套结构中删除数组字段
delete(b.nestedStructTable, newParamIndex)
} else {
// 非数组
// (非顶层) 父级结构存在, 将其追加到父级结构中即可, 向前看一步即为父级结构
@ -281,7 +231,7 @@ func (b *builderImpl) Build() DynamicStruct {
var structFields []reflect.StructField
for _, field := range b.fields {
if strings.Contains(field.name, ArraySplit) {
// TODO : 临时过滤, 后续需要在正确逻辑处正确移除
// 去除数组路径
continue
}
structFields = append(structFields, reflect.StructField{
@ -297,31 +247,3 @@ func (b *builderImpl) Build() DynamicStruct {
definition: reflect.StructOf(structFields),
}
}
// SetType 设置字段类型
func (f *fieldConfigImpl) SetType(typ any) FieldConfig {
f.typ = typ
return f
}
// SetTag 设置字段标签
func (f *fieldConfigImpl) SetTag(tag string) FieldConfig {
f.tag = tag
return f
}
// New 创建动态结构体实例
func (ds *dynamicStructImpl) New() any {
// 不要指针,全部解引用
return reflect.New(ds.definition).Interface()
}
// NewSliceOfStructs 创建动态结构体切片实例
func (ds *dynamicStructImpl) NewSliceOfStructs() any {
return reflect.New(reflect.SliceOf(ds.definition)).Interface()
}
// NewMapOfStructs 创建动态结构体map实例
func (ds *dynamicStructImpl) NewMapOfStructs(key any) any {
return reflect.New(reflect.MapOf(reflect.Indirect(reflect.ValueOf(key)).Type(), ds.definition)).Interface()
}

View File

@ -9,19 +9,14 @@ package dynamicstruct
import (
"encoding/json"
"fmt"
"reflect"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func Test_dynamicStructImpl_New(t *testing.T) {
/*func Test_dynamicStructImpl_New(t *testing.T) {
instance := NewStruct(map[string]string{}).
/*AddField("Integer", "", 0, `json:"int"`, false).
AddField("Text", "", "", `json:"someText"`, false).
AddField("Float", "", 0.0, `json:"double"`, false).
AddField("Boolean", "", false, "", false).
AddField("Slice", "", []int{}, "", false).
AddField("Anonymous", "", "", `json:"-"`, false).*/
AddField("user.base.age", "", 20, `json:"age"`, false).
AddField("user.base.name", "", "", `json:"name"`, false).
AddField("user.job.address", "", "", `json:"address"`, false).
@ -52,3 +47,179 @@ func Test_dynamicStructImpl_New(t *testing.T) {
fmt.Println(string(valByte))
// fmt.Println(reflect.ValueOf(val).Elem().FieldByName("Integer").Interface())
}
*/
// TestStruct : 测试结构体
func TestStruct(t *testing.T) {
Convey("普通结构体,无嵌套", t, func() {
data := []byte(`
{
"age": 123,
"name": "example",
"double": 123.45,
"tag_map": "ttt",
"bool": true,
"slice": [1, 2, 3]
}
`)
instance := NewStruct(map[string]string{
"test_tag_map": `json:"tag_map"`,
}).
AddField("age", "", 0, `json:"age"`, false).
AddField("name", "", "", `json:"name"`, false).
AddField("test_tag_map", "", "", "", false).
AddField("double", "", 0.0, `json:"double"`, false).
AddField("bool", "", false, "", false).
AddField("slice", "", []int{}, `json:"slice"`, false).Build()
val := instance.New()
err := json.Unmarshal(data, &val)
So(err, ShouldBeNil)
reflectType := reflect.TypeOf(val)
// 需要是结构体类型
So(reflectType.Elem().Kind(), ShouldEqual, reflect.Struct)
// 验证数据
// 数据序列化成功
targetByte, targetErr := json.Marshal(val)
So(targetErr, ShouldBeNil)
// 数据转map成功
var res map[string]any
targetUnmarshalErr := json.Unmarshal(targetByte, &res)
So(targetUnmarshalErr, ShouldBeNil)
// 验证数据
So(len(res), ShouldEqual, 6)
So(res["age"], ShouldEqual, float64(123))
So(res["name"], ShouldEqual, "example")
So(res["tag_map"], ShouldEqual, "ttt")
So(res["double"], ShouldEqual, 123.45)
So(res["bool"], ShouldEqual, true)
So(res["slice"], ShouldEqual, []any{float64(1), float64(2), float64(3)})
})
}
// TestNestedStruct : 测试嵌套结构体
func TestNestedStruct(t *testing.T) {
Convey("嵌套结构体", t, func() {
data := []byte(`
{
"user": {"info": {
"age": 123,
"name": "example",
"double": 123.45,
"bool": true,
"slice": [1, 2, 3]
}}}
`)
instance := NewStruct(map[string]string{
"test_tag_map": `json:"tag_map"`,
}).
AddField("user.info.age", "", 0, `json:"age"`, false).
AddField("user.info.name", "", "", `json:"name"`, false).
Build()
val := instance.New()
err := json.Unmarshal(data, &val)
So(err, ShouldBeNil)
reflectType := reflect.TypeOf(val)
// 需要是结构体类型
So(reflectType.Elem().Kind(), ShouldEqual, reflect.Struct)
// 验证数据
// 数据序列化成功
targetByte, targetErr := json.Marshal(val)
So(targetErr, ShouldBeNil)
// 数据转map成功
var res map[string]any
targetUnmarshalErr := json.Unmarshal(targetByte, &res)
So(targetUnmarshalErr, ShouldBeNil)
So(len(res), ShouldEqual, 1)
So(res["user"], ShouldNotBeEmpty)
userMap, ok := res["user"].(map[string]any)
So(ok, ShouldBeTrue)
So(len(userMap), ShouldEqual, 1)
userInfoMap, ok := userMap["info"].(map[string]any)
So(ok, ShouldBeTrue)
So(userInfoMap["age"], ShouldEqual, float64(123))
So(userInfoMap["name"], ShouldEqual, "example")
})
}
// TestStruct : 测试数组
func TestArray(t *testing.T) {
Convey("嵌套结构体带多级数组", t, func() {
data := []byte(`
{
"user": {"list":[[[{
"age": 123,
"name": "example",
"double": 123.45,
"bool": true,
"slice": [1, 2, 3]
}]]]}
}
`)
instance := NewStruct(map[string]string{
"test_tag_map": `json:"tag_map"`,
}).
AddField("user.list.[].[].[].age", "", 0, `json:"age"`, false).
AddField("user.list.[].[].[].name", "", "", `json:"name"`, false).
Build()
val := instance.New()
err := json.Unmarshal(data, &val)
So(err, ShouldBeNil)
reflectType := reflect.TypeOf(val)
// 需要是结构体类型
So(reflectType.Elem().Kind(), ShouldEqual, reflect.Struct)
// 验证数据
// 数据序列化成功
targetByte, targetErr := json.Marshal(val)
So(targetErr, ShouldBeNil)
// 数据转map成功
var res map[string]map[string][][][]any
targetUnmarshalErr := json.Unmarshal(targetByte, &res)
So(targetUnmarshalErr, ShouldBeNil)
So(len(res), ShouldEqual, 1)
So(res["user"], ShouldNotBeEmpty)
So(len(res["user"]), ShouldEqual, 1)
So(len(res["user"]["list"][0]), ShouldEqual, 1)
So(len(res["user"]["list"][0][0]), ShouldEqual, 1)
userMap, ok := res["user"]["list"][0][0][0].(map[string]any)
So(ok, ShouldBeTrue)
So(len(userMap), ShouldEqual, 2)
So(userMap["age"], ShouldEqual, float64(123))
So(userMap["name"], ShouldEqual, "example")
So(userMap["double"], ShouldBeNil)
})
}
func TestFieldManage(t *testing.T) {
Convey("结构体字段管理", t, func() {
instance := NewStruct(map[string]string{
"test_tag_map": `json:"tag_map"`,
}).AddField("age", "", 0, `json:"age"`, false).
AddField("name", "", "", `json:"name"`, false)
So(instance.HasField("Name"), ShouldBeTrue)
So(instance.GetField("Name"), ShouldNotBeNil)
instance.RemoveField("Name")
So(instance.HasField("Name"), ShouldBeFalse)
So(instance.GetField("Name"), ShouldBeNil)
So(instance.HasField("Age"), ShouldBeTrue)
})
}
func TestExtendStruct(t *testing.T) {
Convey("结构体字段继承", t, func() {
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
type Company struct {
Address string `json:"address"`
Logo string `json:"logo"`
Age float64 `json:"company_age"` // 应该覆盖掉 User.Age
}
instance := ExtendStruct(User{}, Company{})
So(instance.HasField("Name"), ShouldBeTrue)
So(instance.HasField("Age"), ShouldBeTrue)
So(instance.HasField("Address"), ShouldBeTrue)
So(instance.HasField("Logo"), ShouldBeTrue)
So(reflect.TypeOf(instance.GetField("Age").GetType()).Kind(), ShouldEqual, reflect.Float64)
So(instance.GetField("Age").GetTag(), ShouldEqual, `json:"company_age"`)
})
}

31
dynamic_struct.go Normal file
View File

@ -0,0 +1,31 @@
// Package dynamicstruct ...
//
// Description : dynamicstruct ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-03-23 14:14
package dynamicstruct
import "reflect"
type dynamicStructImpl struct {
structFields []reflect.StructField
definition reflect.Type
}
// New 创建动态结构体实例
func (ds *dynamicStructImpl) New() any {
// 不要指针,全部解引用
return reflect.New(ds.definition).Interface()
}
// NewSliceOfStructs 创建动态结构体切片实例
func (ds *dynamicStructImpl) NewSliceOfStructs() any {
return reflect.New(reflect.SliceOf(ds.definition)).Interface()
}
// NewMapOfStructs 创建动态结构体map实例
func (ds *dynamicStructImpl) NewMapOfStructs(key any) any {
return reflect.New(reflect.MapOf(reflect.Indirect(reflect.ValueOf(key)).Type(), ds.definition)).Interface()
}

36
field_config.go Normal file
View File

@ -0,0 +1,36 @@
// Package dynamicstruct ...
//
// Description : dynamicstruct ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2025-03-23 14:07
package dynamicstruct
type fieldConfigImpl struct {
name string
pkg string
typ any
tag string
anonymous bool
}
// SetType 设置字段类型
func (f *fieldConfigImpl) SetType(typ any) IFieldConfig {
f.typ = typ
return f
}
func (f *fieldConfigImpl) GetType() any {
return f.typ
}
// SetTag 设置字段标签
func (f *fieldConfigImpl) SetTag(tag string) IFieldConfig {
f.tag = tag
return f
}
func (f *fieldConfigImpl) GetTag() string {
return f.tag
}

13
go.mod
View File

@ -2,18 +2,29 @@ module git.zhangdeman.cn/zhangdeman/dynamic-struct
go 1.24.1
require (
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436
github.com/smartystreets/goconvey v1.8.1
)
require (
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8 // indirect
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 // indirect
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd // indirect
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e // indirect
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/smarty/assertions v1.15.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/tools v0.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

25
go.sum
View File

@ -12,10 +12,26 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@ -23,6 +39,15 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=