diff --git a/README.md b/README.md index 2253d6e..8f760f5 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Development branch. Probably does not work. - [x] Unmarshal into pointers. - [ ] Support Date / times. - [ ] Support Unmarshaler interface. -- [ ] Support struct tags annotations. +- [x] Support struct tags annotations. - [ ] Original go-toml unmarshal tests pass. - [ ] Benchmark! - [ ] Abstract AST. diff --git a/targets.go b/targets.go index 333b756..79ef60c 100644 --- a/targets.go +++ b/targets.go @@ -348,25 +348,50 @@ func scopeMap(v reflect.Value, name string) (target, bool, error) { } func scopeStruct(v reflect.Value, name string) (target, bool, error) { - // TODO: cache this - t := v.Type() - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - if f.PkgPath != "" { - // only consider exported fields - continue - } - if f.Anonymous { - // TODO: handle embedded structs - } else { - fieldName, ok := f.Tag.Lookup("toml") - if !ok { - fieldName = f.Name + // TODO: cache this, and reduce allocations + + fieldPaths := map[string][]int{} + + path := make([]int, 0, 16) + var walk func(reflect.Value) + walk = func(v reflect.Value) { + t := v.Type() + for i := 0; i < t.NumField(); i++ { + l := len(path) + path = append(path, i) + f := t.Field(i) + if f.PkgPath != "" { + // only consider exported fields + continue } - if strings.EqualFold(fieldName, name) { - return valueTarget(v.Field(i)), true, nil + if f.Anonymous { + walk(v.Field(i)) + } else { + fieldName, ok := f.Tag.Lookup("toml") + if !ok { + fieldName = f.Name + } + + pathCopy := make([]int, len(path)) + copy(pathCopy, path) + + fieldPaths[fieldName] = pathCopy + // extra copy for the case-insensitive match + fieldPaths[strings.ToLower(fieldName)] = pathCopy } + path = path[:l] } } - return nil, false, nil + + walk(v) + + path, ok := fieldPaths[name] + if !ok { + path, ok = fieldPaths[strings.ToLower(name)] + } + if !ok { + return nil, false, nil + } + + return valueTarget(v.FieldByIndex(path)), true, nil }