mirror of
https://github.com/duke-git/lancet.git
synced 2025-09-26 19:41:20 +08:00
125 lines
2.9 KiB
Go
125 lines
2.9 KiB
Go
// Package structs provide several high level functions to manipulate struct, tag, and field.
|
|
package structs
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/duke-git/lancet/v2/pointer"
|
|
)
|
|
|
|
// defaultTagName is the default tag for struct fields to lookup.
|
|
var defaultTagName = "json"
|
|
|
|
// Struct is abstract struct for provide several high level functions
|
|
type Struct struct {
|
|
raw any
|
|
rtype reflect.Type
|
|
rvalue reflect.Value
|
|
TagName string
|
|
}
|
|
|
|
// New returns a new *Struct
|
|
func New(value any, tagName ...string) *Struct {
|
|
value = pointer.ExtractPointer(value)
|
|
v := reflect.ValueOf(value)
|
|
t := reflect.TypeOf(value)
|
|
|
|
tn := defaultTagName
|
|
|
|
if len(tagName) > 0 {
|
|
tn = tagName[0]
|
|
}
|
|
|
|
// if need: can also set defaultTagName to tn across structs package level
|
|
// defaultTagName = tn
|
|
|
|
return &Struct{
|
|
raw: value,
|
|
rtype: t,
|
|
rvalue: v,
|
|
TagName: tn,
|
|
}
|
|
}
|
|
|
|
// ToMap converts the given struct to a map[string]any, where the keys
|
|
// of the keys are the field names and the values of the map are the values
|
|
// of the fields. The default map key is the struct field name, but you can
|
|
// change it. The `json` key is the default tag key. Example:
|
|
//
|
|
// // default
|
|
// Name string `json:"name"`
|
|
//
|
|
// // ignore the field
|
|
// Name string // no tag
|
|
// Age string `json:"-"` // json ignore tag
|
|
// sex string // unexported field
|
|
// Goal int `json:"goal,omitempty"` // omitempty if the field is zero value
|
|
//
|
|
// // custom map key
|
|
// Name string `json:"myName"`
|
|
//
|
|
// ToMap convert the exported fields of a struct to map.
|
|
func (s *Struct) ToMap() (map[string]any, error) {
|
|
if !s.IsStruct() {
|
|
return nil, fmt.Errorf("invalid struct %v", s)
|
|
}
|
|
|
|
result := make(map[string]any)
|
|
fields := s.Fields()
|
|
for _, f := range fields {
|
|
if !f.IsExported() || f.tag.IsEmpty() || f.tag.Name == "-" {
|
|
continue
|
|
}
|
|
|
|
if f.IsZero() && f.tag.HasOption("omitempty") {
|
|
continue
|
|
}
|
|
|
|
if f.IsNil() {
|
|
continue
|
|
}
|
|
|
|
result[f.tag.Name] = f.mapValue(f.Value())
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Fields returns all the struct fields within a slice
|
|
func (s *Struct) Fields() []*Field {
|
|
fieldNum := s.rvalue.NumField()
|
|
fields := make([]*Field, 0, fieldNum)
|
|
for i := 0; i < fieldNum; i++ {
|
|
v := s.rvalue.Field(i)
|
|
sf := s.rtype.Field(i)
|
|
field := newField(v, sf, s.TagName)
|
|
fields = append(fields, field)
|
|
}
|
|
return fields
|
|
}
|
|
|
|
// Field returns a Field if the given field name was found
|
|
func (s *Struct) Field(name string) (*Field, bool) {
|
|
f, ok := s.rtype.FieldByName(name)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return newField(s.rvalue.FieldByName(name), f, s.TagName), true
|
|
}
|
|
|
|
// IsStruct returns true if the given rvalue is a struct
|
|
func (s *Struct) IsStruct() bool {
|
|
k := s.rvalue.Kind()
|
|
if k == reflect.Invalid {
|
|
return false
|
|
}
|
|
return k == reflect.Struct
|
|
}
|
|
|
|
// ToMap convert struct to map, only convert exported struct field
|
|
// map key is specified same as struct field tag `json` value.
|
|
func ToMap(v any) (map[string]any, error) {
|
|
return New(v).ToMap()
|
|
}
|