251 lines
6.6 KiB
Go
251 lines
6.6 KiB
Go
|
// Package reflect implements extensions to the standard reflect lib suitable
|
||
|
// for implementing marshaling and unmarshaling packages. The main Mapper type
|
||
|
// allows for Go-compatible named atribute access, including accessing embedded
|
||
|
// struct attributes and the ability to use functions and struct tags to
|
||
|
// customize field names.
|
||
|
//
|
||
|
package reflectx
|
||
|
|
||
|
import "sync"
|
||
|
|
||
|
import (
|
||
|
"reflect"
|
||
|
"runtime"
|
||
|
)
|
||
|
|
||
|
type fieldMap map[string][]int
|
||
|
|
||
|
// Mapper is a general purpose mapper of names to struct fields. A Mapper
|
||
|
// behaves like most marshallers, optionally obeying a field tag for name
|
||
|
// mapping and a function to provide a basic mapping of fields to names.
|
||
|
type Mapper struct {
|
||
|
cache map[reflect.Type]fieldMap
|
||
|
tagName string
|
||
|
mapFunc func(string) string
|
||
|
mutex sync.Mutex
|
||
|
}
|
||
|
|
||
|
// NewMapper returns a new mapper which optionally obeys the field tag given
|
||
|
// by tagName. If tagName is the empty string, it is ignored.
|
||
|
func NewMapper(tagName string) *Mapper {
|
||
|
return &Mapper{
|
||
|
cache: make(map[reflect.Type]fieldMap),
|
||
|
tagName: tagName,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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]fieldMap),
|
||
|
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) fieldMap {
|
||
|
m.mutex.Lock()
|
||
|
mapping, ok := m.cache[t]
|
||
|
if !ok {
|
||
|
mapping = getMapping(t, m.tagName, m.mapFunc)
|
||
|
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{}
|
||
|
nm := m.TypeMap(v.Type())
|
||
|
for tagName, indexes := range nm {
|
||
|
r[tagName] = FieldByIndexes(v, indexes)
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// FieldByName returns a field by the 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)
|
||
|
|
||
|
nm := m.TypeMap(v.Type())
|
||
|
traversal, ok := nm[name]
|
||
|
if !ok {
|
||
|
return *new(reflect.Value)
|
||
|
}
|
||
|
return FieldByIndexes(v, traversal)
|
||
|
}
|
||
|
|
||
|
// 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)
|
||
|
|
||
|
nm := m.TypeMap(v.Type())
|
||
|
|
||
|
vals := make([]reflect.Value, 0, len(names))
|
||
|
for _, name := range names {
|
||
|
traversal, ok := nm[name]
|
||
|
if !ok {
|
||
|
vals = append(vals, *new(reflect.Value))
|
||
|
} else {
|
||
|
vals = append(vals, FieldByIndexes(v, traversal))
|
||
|
}
|
||
|
}
|
||
|
return vals
|
||
|
}
|
||
|
|
||
|
// Traversals by name 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 {
|
||
|
t = Deref(t)
|
||
|
mustBe(t, reflect.Struct)
|
||
|
nm := m.TypeMap(t)
|
||
|
|
||
|
r := make([][]int, 0, len(names))
|
||
|
for _, name := range names {
|
||
|
traversal, ok := nm[name]
|
||
|
if !ok {
|
||
|
r = append(r, []int{})
|
||
|
} else {
|
||
|
r = append(r, traversal)
|
||
|
}
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// FieldByIndexes returns a value for a particular struct traversal.
|
||
|
func FieldByIndexes(v reflect.Value, indexes []int) reflect.Value {
|
||
|
for _, i := range indexes {
|
||
|
v = reflect.Indirect(v).Field(i)
|
||
|
// if this is a pointer, it's possible it is nil
|
||
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||
|
alloc := reflect.New(Deref(v.Type()))
|
||
|
v.Set(alloc)
|
||
|
}
|
||
|
}
|
||
|
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) {
|
||
|
k := v.Kind()
|
||
|
if k != expected {
|
||
|
panic(&reflect.ValueError{Method: methodName(), Kind: k})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// methodName is 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
|
||
|
p []int
|
||
|
}
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
|
||
|
// getMapping returns a mapping for the t type, using the tagName and the mapFunc
|
||
|
// to determine the canonical names of fields.
|
||
|
func getMapping(t reflect.Type, tagName string, mapFunc func(string) string) fieldMap {
|
||
|
queue := []typeQueue{}
|
||
|
queue = append(queue, typeQueue{Deref(t), []int{}})
|
||
|
m := fieldMap{}
|
||
|
for len(queue) != 0 {
|
||
|
// pop the first item off of the queue
|
||
|
tq := queue[0]
|
||
|
queue = queue[1:]
|
||
|
// iterate through all of its fields
|
||
|
for fieldPos := 0; fieldPos < tq.t.NumField(); fieldPos++ {
|
||
|
f := tq.t.Field(fieldPos)
|
||
|
|
||
|
name := f.Tag.Get(tagName)
|
||
|
if len(name) == 0 {
|
||
|
if mapFunc != nil {
|
||
|
name = mapFunc(f.Name)
|
||
|
} else {
|
||
|
name = f.Name
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if the name is "-", disabled via a tag, skip it
|
||
|
if name == "-" {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// skip unexported fields
|
||
|
if len(f.PkgPath) != 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// bfs search of anonymous embedded structs
|
||
|
if f.Anonymous {
|
||
|
queue = append(queue, typeQueue{Deref(f.Type), apnd(tq.p, fieldPos)})
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// if the name is shadowed by an earlier identical name in the search, skip it
|
||
|
if _, ok := m[name]; ok {
|
||
|
continue
|
||
|
}
|
||
|
// add it to the map at the current position
|
||
|
m[name] = apnd(tq.p, fieldPos)
|
||
|
}
|
||
|
}
|
||
|
return m
|
||
|
}
|