walk.go (105 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package lookslike
import (
"fmt"
"reflect"
"github.com/elastic/go-lookslike/llpath"
)
type walkObserverInfo struct {
key llpath.PathComponent
value reflect.Value
rootVal reflect.Value
path llpath.Path
}
// walkObserver functions run once per object in the tree.
type walkObserver func(info walkObserverInfo) error
// walk determine if in is a `map[string]interface{}` or a `Slice` and traverse it if so, otherwise will
// treat it as a scalar and invoke the walk observer on the input value directly.
func walk(inVal reflect.Value, expandPaths bool, wo walkObserver) error {
switch inVal.Kind() {
case reflect.Map:
return walkMap(inVal, expandPaths, wo)
case reflect.Slice:
return walkSlice(inVal, expandPaths, wo)
default:
return walkInterface(inVal, expandPaths, wo)
}
}
// walkmap[string]interface{} is a shorthand way to walk a tree with a map as the root.
func walkMap(mVal reflect.Value, expandPaths bool, wo walkObserver) error {
return walkFullMap(mVal, mVal, llpath.Path{}, expandPaths, wo)
}
// walkSlice walks the provided root slice.
func walkSlice(sVal reflect.Value, expandPaths bool, wo walkObserver) error {
return walkFullSlice(sVal, reflect.ValueOf(map[string]interface{}{}), llpath.Path{}, expandPaths, wo)
}
func walkInterface(s reflect.Value, expandPaths bool, wo walkObserver) error {
return wo(walkObserverInfo{
value: s,
key: llpath.PathComponent{},
rootVal: reflect.ValueOf(map[string]interface{}{}),
path: llpath.Path{},
})
}
func walkFull(oVal, rootVal reflect.Value, path llpath.Path, expandPaths bool, wo walkObserver) (err error) {
// Unpack any wrapped interfaces
for oVal.Kind() == reflect.Interface {
oVal = reflect.ValueOf(oVal.Interface())
}
lastPathComponent := path.Last()
if lastPathComponent == nil {
// In the case of a slice we can have an empty path
if oVal.Kind() == reflect.Slice || oVal.Kind() == reflect.Array {
lastPathComponent = &llpath.PathComponent{}
} else {
panic("Attempted to traverse an empty Path on non array/slice in lookslike.walkFull, this should never happen.")
}
}
err = wo(walkObserverInfo{*lastPathComponent, oVal, rootVal, path})
if err != nil {
return err
}
switch oVal.Kind() {
case reflect.Map:
err := walkFullMap(oVal, rootVal, path, expandPaths, wo)
if err != nil {
return err
}
case reflect.Slice:
for i := 0; i < oVal.Len(); i++ {
newPath := path.ExtendSlice(i)
err := walkFull(oVal.Index(i), rootVal, newPath, expandPaths, wo)
if err != nil {
return err
}
}
}
return nil
}
// walkFull walks the given map[string]interface{} tree.
func walkFullMap(mVal, rootVal reflect.Value, p llpath.Path, expandPaths bool, wo walkObserver) (err error) {
if mVal.Kind() != reflect.Map {
return fmt.Errorf("could not walk not map type for %s", mVal)
}
for _, kVal := range mVal.MapKeys() {
vVal := mVal.MapIndex(kVal)
k := kVal.String()
var newPath llpath.Path
if !expandPaths {
newPath = p.ExtendMap(k)
} else {
additionalPath, err := llpath.ParsePath(k)
if err != nil {
return err
}
newPath = p.Concat(additionalPath)
}
err = walkFull(vVal, rootVal, newPath, expandPaths, wo)
if err != nil {
return err
}
}
return nil
}
func walkFullSlice(sVal reflect.Value, rootVal reflect.Value, p llpath.Path, expandPaths bool, wo walkObserver) (err error) {
for i := 0; i < sVal.Len(); i++ {
var newPath llpath.Path
newPath = p.ExtendSlice(i)
err = walkFull(sVal.Index(i), rootVal, newPath, expandPaths, wo)
if err != nil {
return err
}
}
return nil
}