// 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 }