123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- // 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)
- copy(x, is)
- 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 {
- // check if nothing has already been pushed with the same path
- // sometimes you can choose to override a type using embedded struct
- fld, ok := flds.Paths[fi.Path]
- if !ok || fld.Embedded {
- flds.Paths[fi.Path] = fi
- if fi.Name != "" && !fi.Embedded {
- flds.Names[fi.Path] = fi
- }
- }
- }
- return flds
- }
|