You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
4.1 KiB
Go
174 lines
4.1 KiB
Go
1 year ago
|
package nstruct
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"git.noahlan.cn/noahlan/ntool/nmap"
|
||
|
"git.noahlan.cn/noahlan/ntool/nreflect"
|
||
|
"reflect"
|
||
|
)
|
||
|
|
||
|
// ToMap quickly convert structs to map by reflect
|
||
|
func ToMap(st any, optFns ...MapOptFunc) map[string]any {
|
||
|
mp, _ := StructToMap(st, optFns...)
|
||
|
return mp
|
||
|
}
|
||
|
|
||
|
// MustToMap alis of TryToMap, but will panic on error
|
||
|
func MustToMap(st any, optFns ...MapOptFunc) map[string]any {
|
||
|
mp, err := StructToMap(st, optFns...)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return mp
|
||
|
}
|
||
|
|
||
|
// TryToMap simple convert structs to map by reflect
|
||
|
func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
|
||
|
return StructToMap(st, optFns...)
|
||
|
}
|
||
|
|
||
|
// ToSMap quickly and safe convert structs to map[string]string by reflect
|
||
|
func ToSMap(st any, optFns ...MapOptFunc) map[string]string {
|
||
|
mp, _ := StructToMap(st, optFns...)
|
||
|
return nmap.ToStringMap(mp)
|
||
|
}
|
||
|
|
||
|
// TryToSMap quickly convert structs to map[string]string by reflect
|
||
|
func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error) {
|
||
|
mp, err := StructToMap(st, optFns...)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return nmap.ToStringMap(mp), nil
|
||
|
}
|
||
|
|
||
|
// MustToSMap alias of ToStringMap(), but will panic on error
|
||
|
func MustToSMap(st any, optFns ...MapOptFunc) map[string]string {
|
||
|
mp, err := StructToMap(st, optFns...)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
return nmap.ToStringMap(mp)
|
||
|
}
|
||
|
|
||
|
// ToString quickly format struct to string
|
||
|
func ToString(st any, optFns ...MapOptFunc) string {
|
||
|
mp, err := StructToMap(st, optFns...)
|
||
|
if err == nil {
|
||
|
return nmap.ToString(mp)
|
||
|
}
|
||
|
return fmt.Sprint(st)
|
||
|
}
|
||
|
|
||
|
const defaultFieldTag = "json"
|
||
|
|
||
|
// MapOptions for convert struct to map
|
||
|
type MapOptions struct {
|
||
|
// TagName for map filed. default is "json"
|
||
|
TagName string
|
||
|
// ParseDepth for parse. TODO support depth
|
||
|
ParseDepth int
|
||
|
// MergeAnonymous struct fields to parent map. default is true
|
||
|
MergeAnonymous bool
|
||
|
// ExportPrivate export private fields. default is false
|
||
|
ExportPrivate bool
|
||
|
}
|
||
|
|
||
|
// MapOptFunc define
|
||
|
type MapOptFunc func(opt *MapOptions)
|
||
|
|
||
|
// WithMapTagName set tag name for map field
|
||
|
func WithMapTagName(tagName string) MapOptFunc {
|
||
|
return func(opt *MapOptions) {
|
||
|
opt.TagName = tagName
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MergeAnonymous merge anonymous struct fields to parent map
|
||
|
func MergeAnonymous(opt *MapOptions) {
|
||
|
opt.MergeAnonymous = true
|
||
|
}
|
||
|
|
||
|
// ExportPrivate merge anonymous struct fields to parent map
|
||
|
func ExportPrivate(opt *MapOptions) {
|
||
|
opt.ExportPrivate = true
|
||
|
}
|
||
|
|
||
|
// StructToMap quickly convert structs to map[string]any by reflect.
|
||
|
// Can custom export field name by tag `json` or custom tag
|
||
|
func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
|
||
|
mp := make(map[string]any)
|
||
|
if st == nil {
|
||
|
return mp, nil
|
||
|
}
|
||
|
|
||
|
obj := reflect.Indirect(reflect.ValueOf(st))
|
||
|
if obj.Kind() != reflect.Struct {
|
||
|
return mp, errors.New("must be an struct value")
|
||
|
}
|
||
|
|
||
|
opt := &MapOptions{TagName: defaultFieldTag}
|
||
|
for _, fn := range optFns {
|
||
|
fn(opt)
|
||
|
}
|
||
|
|
||
|
_, err := structToMap(obj, opt, mp)
|
||
|
return mp, err
|
||
|
}
|
||
|
|
||
|
func structToMap(obj reflect.Value, opt *MapOptions, mp map[string]any) (map[string]any, error) {
|
||
|
if mp == nil {
|
||
|
mp = make(map[string]any)
|
||
|
}
|
||
|
|
||
|
refType := obj.Type()
|
||
|
for i := 0; i < obj.NumField(); i++ {
|
||
|
ft := refType.Field(i)
|
||
|
name := ft.Name
|
||
|
// skip un-exported field
|
||
|
if !opt.ExportPrivate && IsUnexported(name) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
tagVal, ok := ft.Tag.Lookup(opt.TagName)
|
||
|
if ok && tagVal != "" {
|
||
|
sMap, err := ParseTagValueDefault(name, tagVal)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
name = sMap.Default("name", name)
|
||
|
if name == "" { // un-exported field
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
field := reflect.Indirect(obj.Field(i))
|
||
|
if field.Kind() == reflect.Struct {
|
||
|
// collect anonymous struct values to parent.
|
||
|
if ft.Anonymous && opt.MergeAnonymous {
|
||
|
_, err := structToMap(field, opt, mp)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
} else { // collect struct values to submap
|
||
|
sub, err := structToMap(field, opt, nil)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
mp[name] = sub
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if field.CanInterface() {
|
||
|
mp[name] = field.Interface()
|
||
|
} else if field.CanAddr() { // for unexported field
|
||
|
mp[name] = nreflect.UnexportedValue(field)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return mp, nil
|
||
|
}
|