支持嵌套结构体 #1
							
								
								
									
										83
									
								
								builder.go
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								builder.go
									
									
									
									
									
								
							| @ -1,6 +1,8 @@ | |||||||
| package dynamicstruct | package dynamicstruct | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"git.zhangdeman.cn/zhangdeman/wrapper" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| @ -42,8 +44,16 @@ type ( | |||||||
| 		NewMapOfStructs(key any) any | 		NewMapOfStructs(key any) any | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	nestedStruct struct { | ||||||
|  | 		Field   string | ||||||
|  | 		Builder Builder | ||||||
|  | 		JsonTag string | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	builderImpl struct { | 	builderImpl struct { | ||||||
| 		fields []*fieldConfigImpl | 		fields            []*fieldConfigImpl | ||||||
|  | 		nestedStructTable map[string]nestedStruct // 嵌套结构体的定义, 父级路径 => 父级路径下挂接的子路径 | ||||||
|  | 		maxFieldDeep      int                     // 字段嵌套最大深度 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fieldConfigImpl struct { | 	fieldConfigImpl struct { | ||||||
| @ -55,14 +65,16 @@ type ( | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dynamicStructImpl struct { | 	dynamicStructImpl struct { | ||||||
| 		definition reflect.Type | 		structFields []reflect.StructField | ||||||
|  | 		definition   reflect.Type | ||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // NewStruct 获取builder实例 | // NewStruct 获取builder实例 | ||||||
| func NewStruct() Builder { | func NewStruct() Builder { | ||||||
| 	return &builderImpl{ | 	return &builderImpl{ | ||||||
| 		fields: []*fieldConfigImpl{}, | 		fields:            []*fieldConfigImpl{}, | ||||||
|  | 		nestedStructTable: make(map[string]nestedStruct), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -97,6 +109,8 @@ func (b *builderImpl) parseNestedField(fieldName string) ([]string, bool) { | |||||||
|  |  | ||||||
| // AddField 添加结构体字段 | // AddField 添加结构体字段 | ||||||
| func (b *builderImpl) AddField(name string, pkg string, typ any, tag string, anonymous bool) Builder { | 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) | 	fieldNameArr, isNestedField := b.parseNestedField(name) | ||||||
| 	if !isNestedField { | 	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) | 		b.addNormalField(name, pkg, typ, tag, anonymous) | ||||||
| 		return b | 		return b | ||||||
| 	} | 	} | ||||||
| 	// TODO : 添加嵌套的结构体 | 	// 添加嵌套的结构体 | ||||||
| 	b.addNestedField(fieldNameArr, pkg, typ, tag, anonymous) | 	b.addNestedField(fieldNameArr, pkg, typ, tag, anonymous) | ||||||
| 	return b | 	return b | ||||||
| } | } | ||||||
| @ -129,6 +143,36 @@ func (b *builderImpl) addNormalField(name string, pkg string, typ any, tag strin | |||||||
|  |  | ||||||
| // addNestedField 添加嵌套字段 | // addNestedField 添加嵌套字段 | ||||||
| func (b *builderImpl) addNestedField(nameArr []string, pkg string, typ any, tag string, anonymous bool) { | 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 根据名称移除结构体字段 | // RemoveField 根据名称移除结构体字段 | ||||||
| @ -166,8 +210,33 @@ func (b *builderImpl) GetField(name string) FieldConfig { | |||||||
|  |  | ||||||
| // Build 构建动态结构体 | // Build 构建动态结构体 | ||||||
| func (b *builderImpl) Build() DynamicStruct { | 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 | 	var structFields []reflect.StructField | ||||||
|  |  | ||||||
| 	for _, field := range b.fields { | 	for _, field := range b.fields { | ||||||
| 		structFields = append(structFields, reflect.StructField{ | 		structFields = append(structFields, reflect.StructField{ | ||||||
| 			Name:      field.name, | 			Name:      field.name, | ||||||
| @ -177,9 +246,9 @@ func (b *builderImpl) Build() DynamicStruct { | |||||||
| 			Anonymous: field.anonymous, | 			Anonymous: field.anonymous, | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &dynamicStructImpl{ | 	return &dynamicStructImpl{ | ||||||
| 		definition: reflect.StructOf(structFields), | 		structFields: structFields, | ||||||
|  | 		definition:   reflect.StructOf(structFields), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| // Package dynamicstruct ... | // Package dynamicstruct ... | ||||||
| // | // | ||||||
| // Description : dynamicstruct ... | // Description : dynamic struct ... | ||||||
| // | // | ||||||
| // Author : go_developer@163.com<白茶清欢> | // Author : go_developer@163.com<白茶清欢> | ||||||
| // | // | ||||||
| @ -10,20 +10,24 @@ package dynamicstruct | |||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Test_dynamicStructImpl_New(t *testing.T) { | func Test_dynamicStructImpl_New(t *testing.T) { | ||||||
| 	instance := NewStruct(). | 	instance := NewStruct(). | ||||||
| 		AddField("Integer", "", 0, `json:"int"`, false). | 		/*AddField("Integer", "", 0, `json:"int"`, false). | ||||||
| 		AddField("Text", "", "", `json:"someText"`, false). | 		AddField("Text", "", "", `json:"someText"`, false). | ||||||
| 		AddField("Float", "", 0.0, `json:"double"`, false). | 		AddField("Float", "", 0.0, `json:"double"`, false). | ||||||
| 		AddField("Boolean", "", false, "", false). | 		AddField("Boolean", "", false, "", false). | ||||||
| 		AddField("Slice", "", []int{}, "", false). | 		AddField("Slice", "", []int{}, "", false). | ||||||
| 		AddField("Anonymous", "", "", `json:"-"`, false). | 		AddField("Anonymous", "", "", `json:"-"`, false).*/ | ||||||
| 		Build(). | 		AddField("user.base.age", "", 20, `json:"age"`, false). | ||||||
| 		New() | 		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(` | 	data := []byte(` | ||||||
| { | { | ||||||
| @ -32,10 +36,13 @@ func Test_dynamicStructImpl_New(t *testing.T) { | |||||||
|     "double": 123.45, |     "double": 123.45, | ||||||
|     "Boolean": true, |     "Boolean": true, | ||||||
|     "Slice": [1, 2, 3], |     "Slice": [1, 2, 3], | ||||||
|  |     "user": {"job":{"address":"beijing","company":{"name":"unknown"}}, "base":{"age": 1800, "name":"baicha"}}, | ||||||
|     "Anonymous": "avoid to read" |     "Anonymous": "avoid to read" | ||||||
| } | } | ||||||
| `) | `) | ||||||
| 	err := json.Unmarshal(data, &instance) | 	err := json.Unmarshal(data, &val) | ||||||
| 	fmt.Println(err) | 	fmt.Println(err, val) | ||||||
| 	fmt.Println(reflect.ValueOf(instance).Elem().FieldByName("Integer").Interface()) | 	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 | module git.zhangdeman.cn/zhangdeman/dynamic-struct | ||||||
|  |  | ||||||
| go 1.24.1 | 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= | ||||||
		Reference in New Issue
	
	Block a user