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.
ntool/nstruct/mapconv.go

174 lines
4.1 KiB
Go

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
}