Depending on sqlx makes users pull SQL related code that is usually not needed. Tools like dep make it easier by explicitly listing relevant subpackages but the code needs to be downloaded anyway.
442 lines
12 KiB
Go
442 lines
12 KiB
Go
// Package reflectx implements extensions to the standard reflect lib suitable
|
|
// for implementing marshalling and unmarshalling packages. The main Mapper type
|
|
// allows for Go-compatible named attribute access, including accessing embedded
|
|
// struct attributes and the ability to use functions and struct tags to
|
|
// customize field names.
|
|
//
|
|
package reflectx
|
|
|
|
import (
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// A FieldInfo is metadata for a struct field.
|
|
type FieldInfo struct {
|
|
Index []int
|
|
Path string
|
|
Field reflect.StructField
|
|
Zero reflect.Value
|
|
Name string
|
|
Options map[string]string
|
|
Embedded bool
|
|
Children []*FieldInfo
|
|
Parent *FieldInfo
|
|
}
|
|
|
|
// A StructMap is an index of field metadata for a struct.
|
|
type StructMap struct {
|
|
Tree *FieldInfo
|
|
Index []*FieldInfo
|
|
Paths map[string]*FieldInfo
|
|
Names map[string]*FieldInfo
|
|
}
|
|
|
|
// GetByPath returns a *FieldInfo for a given string path.
|
|
func (f StructMap) GetByPath(path string) *FieldInfo {
|
|
return f.Paths[path]
|
|
}
|
|
|
|
// GetByTraversal returns a *FieldInfo for a given integer path. It is
|
|
// analogous to reflect.FieldByIndex, but using the cached traversal
|
|
// rather than re-executing the reflect machinery each time.
|
|
func (f StructMap) GetByTraversal(index []int) *FieldInfo {
|
|
if len(index) == 0 {
|
|
return nil
|
|
}
|
|
|
|
tree := f.Tree
|
|
for _, i := range index {
|
|
if i >= len(tree.Children) || tree.Children[i] == nil {
|
|
return nil
|
|
}
|
|
tree = tree.Children[i]
|
|
}
|
|
return tree
|
|
}
|
|
|
|
// Mapper is a general purpose mapper of names to struct fields. A Mapper
|
|
// behaves like most marshallers in the standard library, obeying a field tag
|
|
// for name mapping but also providing a basic transform function.
|
|
type Mapper struct {
|
|
cache map[reflect.Type]*StructMap
|
|
tagName string
|
|
tagMapFunc func(string) string
|
|
mapFunc func(string) string
|
|
mutex sync.Mutex
|
|
}
|
|
|
|
// NewMapper returns a new mapper using the tagName as its struct field tag.
|
|
// If tagName is the empty string, it is ignored.
|
|
func NewMapper(tagName string) *Mapper {
|
|
return &Mapper{
|
|
cache: make(map[reflect.Type]*StructMap),
|
|
tagName: tagName,
|
|
}
|
|
}
|
|
|
|
// NewMapperTagFunc returns a new mapper which contains a mapper for field names
|
|
// AND a mapper for tag values. This is useful for tags like json which can
|
|
// have values like "name,omitempty".
|
|
func NewMapperTagFunc(tagName string, mapFunc, tagMapFunc func(string) string) *Mapper {
|
|
return &Mapper{
|
|
cache: make(map[reflect.Type]*StructMap),
|
|
tagName: tagName,
|
|
mapFunc: mapFunc,
|
|
tagMapFunc: tagMapFunc,
|
|
}
|
|
}
|
|
|
|
// NewMapperFunc returns a new mapper which optionally obeys a field tag and
|
|
// a struct field name mapper func given by f. Tags will take precedence, but
|
|
// for any other field, the mapped name will be f(field.Name)
|
|
func NewMapperFunc(tagName string, f func(string) string) *Mapper {
|
|
return &Mapper{
|
|
cache: make(map[reflect.Type]*StructMap),
|
|
tagName: tagName,
|
|
mapFunc: f,
|
|
}
|
|
}
|
|
|
|
// TypeMap returns a mapping of field strings to int slices representing
|
|
// the traversal down the struct to reach the field.
|
|
func (m *Mapper) TypeMap(t reflect.Type) *StructMap {
|
|
m.mutex.Lock()
|
|
mapping, ok := m.cache[t]
|
|
if !ok {
|
|
mapping = getMapping(t, m.tagName, m.mapFunc, m.tagMapFunc)
|
|
m.cache[t] = mapping
|
|
}
|
|
m.mutex.Unlock()
|
|
return mapping
|
|
}
|
|
|
|
// FieldMap returns the mapper's mapping of field names to reflect values. Panics
|
|
// if v's Kind is not Struct, or v is not Indirectable to a struct kind.
|
|
func (m *Mapper) FieldMap(v reflect.Value) map[string]reflect.Value {
|
|
v = reflect.Indirect(v)
|
|
mustBe(v, reflect.Struct)
|
|
|
|
r := map[string]reflect.Value{}
|
|
tm := m.TypeMap(v.Type())
|
|
for tagName, fi := range tm.Names {
|
|
r[tagName] = FieldByIndexes(v, fi.Index)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// FieldByName returns a field by its mapped name as a reflect.Value.
|
|
// Panics if v's Kind is not Struct or v is not Indirectable to a struct Kind.
|
|
// Returns zero Value if the name is not found.
|
|
func (m *Mapper) FieldByName(v reflect.Value, name string) reflect.Value {
|
|
v = reflect.Indirect(v)
|
|
mustBe(v, reflect.Struct)
|
|
|
|
tm := m.TypeMap(v.Type())
|
|
fi, ok := tm.Names[name]
|
|
if !ok {
|
|
return v
|
|
}
|
|
return FieldByIndexes(v, fi.Index)
|
|
}
|
|
|
|
// FieldsByName returns a slice of values corresponding to the slice of names
|
|
// for the value. Panics if v's Kind is not Struct or v is not Indirectable
|
|
// to a struct Kind. Returns zero Value for each name not found.
|
|
func (m *Mapper) FieldsByName(v reflect.Value, names []string) []reflect.Value {
|
|
v = reflect.Indirect(v)
|
|
mustBe(v, reflect.Struct)
|
|
|
|
tm := m.TypeMap(v.Type())
|
|
vals := make([]reflect.Value, 0, len(names))
|
|
for _, name := range names {
|
|
fi, ok := tm.Names[name]
|
|
if !ok {
|
|
vals = append(vals, *new(reflect.Value))
|
|
} else {
|
|
vals = append(vals, FieldByIndexes(v, fi.Index))
|
|
}
|
|
}
|
|
return vals
|
|
}
|
|
|
|
// TraversalsByName returns a slice of int slices which represent the struct
|
|
// traversals for each mapped name. Panics if t is not a struct or Indirectable
|
|
// to a struct. Returns empty int slice for each name not found.
|
|
func (m *Mapper) TraversalsByName(t reflect.Type, names []string) [][]int {
|
|
r := make([][]int, 0, len(names))
|
|
m.TraversalsByNameFunc(t, names, func(_ int, i []int) error {
|
|
if i == nil {
|
|
r = append(r, []int{})
|
|
} else {
|
|
r = append(r, i)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
return r
|
|
}
|
|
|
|
// TraversalsByNameFunc traverses the mapped names and calls fn with the index of
|
|
// each name and the struct traversal represented by that name. Panics if t is not
|
|
// a struct or Indirectable to a struct. Returns the first error returned by fn or nil.
|
|
func (m *Mapper) TraversalsByNameFunc(t reflect.Type, names []string, fn func(int, []int) error) error {
|
|
t = Deref(t)
|
|
mustBe(t, reflect.Struct)
|
|
tm := m.TypeMap(t)
|
|
for i, name := range names {
|
|
fi, ok := tm.Names[name]
|
|
if !ok {
|
|
if err := fn(i, nil); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := fn(i, fi.Index); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FieldByIndexes returns a value for the field given by the struct traversal
|
|
// for the given value.
|
|
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
|
|
for _, i := range indexes {
|
|
v = reflect.Indirect(v).Field(i)
|
|
// if this is a pointer and it's nil, allocate a new value and set it
|
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
|
alloc := reflect.New(Deref(v.Type()))
|
|
v.Set(alloc)
|
|
}
|
|
if v.Kind() == reflect.Map && v.IsNil() {
|
|
v.Set(reflect.MakeMap(v.Type()))
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// FieldByIndexesReadOnly returns a value for a particular struct traversal,
|
|
// but is not concerned with allocating nil pointers because the value is
|
|
// going to be used for reading and not setting.
|
|
func FieldByIndexesReadOnly(v reflect.Value, indexes []int) reflect.Value {
|
|
for _, i := range indexes {
|
|
v = reflect.Indirect(v).Field(i)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// Deref is Indirect for reflect.Types
|
|
func Deref(t reflect.Type) reflect.Type {
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
return t
|
|
}
|
|
|
|
// -- helpers & utilities --
|
|
|
|
type kinder interface {
|
|
Kind() reflect.Kind
|
|
}
|
|
|
|
// mustBe checks a value against a kind, panicing with a reflect.ValueError
|
|
// if the kind isn't that which is required.
|
|
func mustBe(v kinder, expected reflect.Kind) {
|
|
if k := v.Kind(); k != expected {
|
|
panic(&reflect.ValueError{Method: methodName(), Kind: k})
|
|
}
|
|
}
|
|
|
|
// methodName returns the caller of the function calling methodName
|
|
func methodName() string {
|
|
pc, _, _, _ := runtime.Caller(2)
|
|
f := runtime.FuncForPC(pc)
|
|
if f == nil {
|
|
return "unknown method"
|
|
}
|
|
return f.Name()
|
|
}
|
|
|
|
type typeQueue struct {
|
|
t reflect.Type
|
|
fi *FieldInfo
|
|
pp string // Parent path
|
|
}
|
|
|
|
// A copying append that creates a new slice each time.
|
|
func apnd(is []int, i int) []int {
|
|
x := make([]int, len(is)+1)
|
|
for p, n := range is {
|
|
x[p] = n
|
|
}
|
|
x[len(x)-1] = i
|
|
return x
|
|
}
|
|
|
|
type mapf func(string) string
|
|
|
|
// parseName parses the tag and the target name for the given field using
|
|
// the tagName (eg 'json' for `json:"foo"` tags), mapFunc for mapping the
|
|
// field's name to a target name, and tagMapFunc for mapping the tag to
|
|
// a target name.
|
|
func parseName(field reflect.StructField, tagName string, mapFunc, tagMapFunc mapf) (tag, fieldName string) {
|
|
// first, set the fieldName to the field's name
|
|
fieldName = field.Name
|
|
// if a mapFunc is set, use that to override the fieldName
|
|
if mapFunc != nil {
|
|
fieldName = mapFunc(fieldName)
|
|
}
|
|
|
|
// if there's no tag to look for, return the field name
|
|
if tagName == "" {
|
|
return "", fieldName
|
|
}
|
|
|
|
// if this tag is not set using the normal convention in the tag,
|
|
// then return the fieldname.. this check is done because according
|
|
// to the reflect documentation:
|
|
// If the tag does not have the conventional format,
|
|
// the value returned by Get is unspecified.
|
|
// which doesn't sound great.
|
|
if !strings.Contains(string(field.Tag), tagName+":") {
|
|
return "", fieldName
|
|
}
|
|
|
|
// at this point we're fairly sure that we have a tag, so lets pull it out
|
|
tag = field.Tag.Get(tagName)
|
|
|
|
// if we have a mapper function, call it on the whole tag
|
|
// XXX: this is a change from the old version, which pulled out the name
|
|
// before the tagMapFunc could be run, but I think this is the right way
|
|
if tagMapFunc != nil {
|
|
tag = tagMapFunc(tag)
|
|
}
|
|
|
|
// finally, split the options from the name
|
|
parts := strings.Split(tag, ",")
|
|
fieldName = parts[0]
|
|
|
|
return tag, fieldName
|
|
}
|
|
|
|
// parseOptions parses options out of a tag string, skipping the name
|
|
func parseOptions(tag string) map[string]string {
|
|
parts := strings.Split(tag, ",")
|
|
options := make(map[string]string, len(parts))
|
|
if len(parts) > 1 {
|
|
for _, opt := range parts[1:] {
|
|
// short circuit potentially expensive split op
|
|
if strings.Contains(opt, "=") {
|
|
kv := strings.Split(opt, "=")
|
|
options[kv[0]] = kv[1]
|
|
continue
|
|
}
|
|
options[opt] = ""
|
|
}
|
|
}
|
|
return options
|
|
}
|
|
|
|
// getMapping returns a mapping for the t type, using the tagName, mapFunc and
|
|
// tagMapFunc to determine the canonical names of fields.
|
|
func getMapping(t reflect.Type, tagName string, mapFunc, tagMapFunc mapf) *StructMap {
|
|
m := []*FieldInfo{}
|
|
|
|
root := &FieldInfo{}
|
|
queue := []typeQueue{}
|
|
queue = append(queue, typeQueue{Deref(t), root, ""})
|
|
|
|
QueueLoop:
|
|
for len(queue) != 0 {
|
|
// pop the first item off of the queue
|
|
tq := queue[0]
|
|
queue = queue[1:]
|
|
|
|
// ignore recursive field
|
|
for p := tq.fi.Parent; p != nil; p = p.Parent {
|
|
if tq.fi.Field.Type == p.Field.Type {
|
|
continue QueueLoop
|
|
}
|
|
}
|
|
|
|
nChildren := 0
|
|
if tq.t.Kind() == reflect.Struct {
|
|
nChildren = tq.t.NumField()
|
|
}
|
|
tq.fi.Children = make([]*FieldInfo, nChildren)
|
|
|
|
// iterate through all of its fields
|
|
for fieldPos := 0; fieldPos < nChildren; fieldPos++ {
|
|
|
|
f := tq.t.Field(fieldPos)
|
|
|
|
// parse the tag and the target name using the mapping options for this field
|
|
tag, name := parseName(f, tagName, mapFunc, tagMapFunc)
|
|
|
|
// if the name is "-", disabled via a tag, skip it
|
|
if name == "-" {
|
|
continue
|
|
}
|
|
|
|
fi := FieldInfo{
|
|
Field: f,
|
|
Name: name,
|
|
Zero: reflect.New(f.Type).Elem(),
|
|
Options: parseOptions(tag),
|
|
}
|
|
|
|
// if the path is empty this path is just the name
|
|
if tq.pp == "" {
|
|
fi.Path = fi.Name
|
|
} else {
|
|
fi.Path = tq.pp + "." + fi.Name
|
|
}
|
|
|
|
// skip unexported fields
|
|
if len(f.PkgPath) != 0 && !f.Anonymous {
|
|
continue
|
|
}
|
|
|
|
// bfs search of anonymous embedded structs
|
|
if f.Anonymous {
|
|
pp := tq.pp
|
|
if tag != "" {
|
|
pp = fi.Path
|
|
}
|
|
|
|
fi.Embedded = true
|
|
fi.Index = apnd(tq.fi.Index, fieldPos)
|
|
nChildren := 0
|
|
ft := Deref(f.Type)
|
|
if ft.Kind() == reflect.Struct {
|
|
nChildren = ft.NumField()
|
|
}
|
|
fi.Children = make([]*FieldInfo, nChildren)
|
|
queue = append(queue, typeQueue{Deref(f.Type), &fi, pp})
|
|
} else if fi.Zero.Kind() == reflect.Struct || (fi.Zero.Kind() == reflect.Ptr && fi.Zero.Type().Elem().Kind() == reflect.Struct) {
|
|
fi.Index = apnd(tq.fi.Index, fieldPos)
|
|
fi.Children = make([]*FieldInfo, Deref(f.Type).NumField())
|
|
queue = append(queue, typeQueue{Deref(f.Type), &fi, fi.Path})
|
|
}
|
|
|
|
fi.Index = apnd(tq.fi.Index, fieldPos)
|
|
fi.Parent = tq.fi
|
|
tq.fi.Children[fieldPos] = &fi
|
|
m = append(m, &fi)
|
|
}
|
|
}
|
|
|
|
flds := &StructMap{Index: m, Tree: root, Paths: map[string]*FieldInfo{}, Names: map[string]*FieldInfo{}}
|
|
for _, fi := range flds.Index {
|
|
flds.Paths[fi.Path] = fi
|
|
if fi.Name != "" && !fi.Embedded {
|
|
flds.Names[fi.Path] = fi
|
|
}
|
|
}
|
|
|
|
return flds
|
|
}
|