支持嵌套结构体 #1
83
builder.go
83
builder.go
@ -1,6 +1,8 @@
|
||||
package dynamicstruct
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.zhangdeman.cn/zhangdeman/wrapper"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
@ -42,8 +44,16 @@ type (
|
||||
NewMapOfStructs(key any) any
|
||||
}
|
||||
|
||||
nestedStruct struct {
|
||||
Field string
|
||||
Builder Builder
|
||||
JsonTag string
|
||||
}
|
||||
|
||||
builderImpl struct {
|
||||
fields []*fieldConfigImpl
|
||||
fields []*fieldConfigImpl
|
||||
nestedStructTable map[string]nestedStruct // 嵌套结构体的定义, 父级路径 => 父级路径下挂接的子路径
|
||||
maxFieldDeep int // 字段嵌套最大深度
|
||||
}
|
||||
|
||||
fieldConfigImpl struct {
|
||||
@ -55,14 +65,16 @@ type (
|
||||
}
|
||||
|
||||
dynamicStructImpl struct {
|
||||
definition reflect.Type
|
||||
structFields []reflect.StructField
|
||||
definition reflect.Type
|
||||
}
|
||||
)
|
||||
|
||||
// NewStruct 获取builder实例
|
||||
func NewStruct() Builder {
|
||||
return &builderImpl{
|
||||
fields: []*fieldConfigImpl{},
|
||||
fields: []*fieldConfigImpl{},
|
||||
nestedStructTable: make(map[string]nestedStruct),
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,6 +109,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
|
||||
// name = wrapper.String(name).SnakeCaseToCamel()
|
||||
// 判断是否嵌套结构体
|
||||
fieldNameArr, isNestedField := b.parseNestedField(name)
|
||||
if !isNestedField {
|
||||
@ -104,7 +118,7 @@ func (b *builderImpl) AddField(name string, pkg string, typ any, tag string, ano
|
||||
b.addNormalField(name, pkg, typ, tag, anonymous)
|
||||
return b
|
||||
}
|
||||
// TODO : 添加嵌套的结构体
|
||||
// 添加嵌套的结构体
|
||||
b.addNestedField(fieldNameArr, pkg, typ, tag, anonymous)
|
||||
return b
|
||||
}
|
||||
@ -129,6 +143,36 @@ func (b *builderImpl) addNormalField(name string, pkg string, typ any, tag strin
|
||||
|
||||
// addNestedField 添加嵌套字段
|
||||
func (b *builderImpl) addNestedField(nameArr []string, pkg string, typ any, tag string, anonymous bool) {
|
||||
if len(nameArr) == 1 {
|
||||
// 说明已经是最顶层结构了
|
||||
b.addNormalField(nameArr[0], pkg, typ, tag, anonymous)
|
||||
return
|
||||
}
|
||||
if len(nameArr) > b.maxFieldDeep {
|
||||
// 设置字段嵌套的最大深度, 由于生成结构体确认深度使用
|
||||
b.maxFieldDeep = len(nameArr)
|
||||
}
|
||||
for i := len(nameArr) - 1; i > 0; i-- {
|
||||
jsonTag := nameArr[i]
|
||||
fieldName := wrapper.String(jsonTag).SnakeCaseToCamel()
|
||||
parentName := strings.Join(nameArr[:i], ".")
|
||||
parentJsonTag := nameArr[i-1]
|
||||
parentFieldName := wrapper.String(parentJsonTag).SnakeCaseToCamel()
|
||||
if len(parentName) > 0 {
|
||||
if _, exist := b.nestedStructTable[parentName]; !exist {
|
||||
b.nestedStructTable[parentName] = nestedStruct{
|
||||
Field: parentFieldName,
|
||||
Builder: NewStruct(),
|
||||
JsonTag: parentJsonTag,
|
||||
}
|
||||
}
|
||||
}
|
||||
if i == len(nameArr)-1 {
|
||||
// 最深层此字段, 直接追加到他的父级结构体中即可
|
||||
b.nestedStructTable[parentName].Builder.AddField(fieldName, pkg, typ, tag, anonymous)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveField 根据名称移除结构体字段
|
||||
@ -166,8 +210,33 @@ func (b *builderImpl) GetField(name string) FieldConfig {
|
||||
|
||||
// Build 构建动态结构体
|
||||
func (b *builderImpl) Build() DynamicStruct {
|
||||
// 按照嵌套深度, 进行一次回溯处理
|
||||
for deep := b.maxFieldDeep - 1; deep > 0; deep-- {
|
||||
// 嵌套数据结构
|
||||
for parentIndex, builderCfg := range b.nestedStructTable {
|
||||
parentNameArr := strings.Split(parentIndex, ".")
|
||||
if len(parentNameArr) != deep {
|
||||
// 从深度最深处向上处理
|
||||
continue
|
||||
}
|
||||
/*if deep == b.maxFieldDeep-1 {
|
||||
// 说明最深层嵌套结构
|
||||
parentJsonTag := parentNameArr[deep-1]
|
||||
parentField := wrapper.String(parentJsonTag).SnakeCaseToCamel()
|
||||
b.AddField(parentField, "", builderCfg.Builder.Build().New(), fmt.Sprintf(`json:"%v"`, parentJsonTag), false)
|
||||
continue
|
||||
}*/
|
||||
if deep == 1 {
|
||||
// 说明是顶层了
|
||||
b.AddField(builderCfg.Field, "", builderCfg.Builder.Build().New(), fmt.Sprintf(`json:"%v"`, builderCfg.JsonTag), false)
|
||||
} else {
|
||||
// (非顶层) 父级结构存在, 将其追加到父级结构中即可, 向前看一步即为父级结构
|
||||
b.nestedStructTable[strings.Join(parentNameArr[:len(parentNameArr)-1], ".")].Builder.AddField(builderCfg.Field, "", builderCfg.Builder.Build().New(), fmt.Sprintf(`json:"%v"`, builderCfg.JsonTag), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 一级字段属性
|
||||
var structFields []reflect.StructField
|
||||
|
||||
for _, field := range b.fields {
|
||||
structFields = append(structFields, reflect.StructField{
|
||||
Name: field.name,
|
||||
@ -177,9 +246,9 @@ func (b *builderImpl) Build() DynamicStruct {
|
||||
Anonymous: field.anonymous,
|
||||
})
|
||||
}
|
||||
|
||||
return &dynamicStructImpl{
|
||||
definition: reflect.StructOf(structFields),
|
||||
structFields: structFields,
|
||||
definition: reflect.StructOf(structFields),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Package dynamicstruct ...
|
||||
//
|
||||
// Description : dynamicstruct ...
|
||||
// Description : dynamic struct ...
|
||||
//
|
||||
// Author : go_developer@163.com<白茶清欢>
|
||||
//
|
||||
@ -10,20 +10,24 @@ package dynamicstruct
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_dynamicStructImpl_New(t *testing.T) {
|
||||
instance := NewStruct().
|
||||
AddField("Integer", "", 0, `json:"int"`, false).
|
||||
/*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).
|
||||
Build().
|
||||
New()
|
||||
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).
|
||||
AddField("user.job.company.name", "", "", `json:"name"`, false).
|
||||
Build()
|
||||
|
||||
val := instance.New()
|
||||
|
||||
data := []byte(`
|
||||
{
|
||||
@ -32,10 +36,13 @@ func Test_dynamicStructImpl_New(t *testing.T) {
|
||||
"double": 123.45,
|
||||
"Boolean": true,
|
||||
"Slice": [1, 2, 3],
|
||||
"user": {"job":{"address":"beijing","company":{"name":"unknown"}}, "base":{"age": 1800, "name":"baicha"}},
|
||||
"Anonymous": "avoid to read"
|
||||
}
|
||||
`)
|
||||
err := json.Unmarshal(data, &instance)
|
||||
fmt.Println(err)
|
||||
fmt.Println(reflect.ValueOf(instance).Elem().FieldByName("Integer").Interface())
|
||||
err := json.Unmarshal(data, &val)
|
||||
fmt.Println(err, val)
|
||||
valByte, _ := json.Marshal(val)
|
||||
fmt.Println(string(valByte))
|
||||
// fmt.Println(reflect.ValueOf(val).Elem().FieldByName("Integer").Interface())
|
||||
}
|
||||
|
16
go.mod
16
go.mod
@ -1,3 +1,19 @@
|
||||
module git.zhangdeman.cn/zhangdeman/dynamic-struct
|
||||
|
||||
go 1.24.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/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
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
28
go.sum
Normal file
28
go.sum
Normal file
@ -0,0 +1,28 @@
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8 h1:VEifPc+vkpEQoX9rj7zxmT1m+IA81XjOxe7+Z1aqWNM=
|
||||
git.zhangdeman.cn/zhangdeman/consts v0.0.0-20250227040546-863c03f34bb8/go.mod h1:IXXaZkb7vGzGnGM5RRWrASAuwrVSNxuoe0DmeXx5g6k=
|
||||
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0 h1:gUDlQMuJ4xNfP2Abl1Msmpa3fASLWYkNlqDFF/6GN0Y=
|
||||
git.zhangdeman.cn/zhangdeman/op_type v0.0.0-20240122104027-4928421213c0/go.mod h1:VHb9qmhaPDAQDcS6vUiDCamYjZ4R5lD1XtVsh55KsMI=
|
||||
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd h1:q7GG14qgXKB4MEXQFOe7/UYebsqMfPaSX80TcPdOosI=
|
||||
git.zhangdeman.cn/zhangdeman/serialize v0.0.0-20241223084948-de2e49144fcd/go.mod h1:+D6uPSljwHywjVY5WSBY4TRVMj26TN5f5cFGEYMldjs=
|
||||
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e h1:Q973S6CcWr1ICZhFI1STFOJ+KUImCl2BaIXm6YppBqI=
|
||||
git.zhangdeman.cn/zhangdeman/util v0.0.0-20240618042405-6ee2c904644e/go.mod h1:VpPjBlwz8U+OxZuxzHQBv1aEEZ3pStH6bZvT21ADEbI=
|
||||
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436 h1:SM4zc54W2wmM72+4pMNQ8iS371H6lj4J8rj8KJKf7pw=
|
||||
git.zhangdeman.cn/zhangdeman/wrapper v0.0.0-20250302133417-c1588abcb436/go.mod h1:YJ1FlvFgkfAHkxkt3l5rKKUqEpQkNMbCFDzDmgteEU8=
|
||||
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/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
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=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
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=
|
||||
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=
|
Loading…
x
Reference in New Issue
Block a user