In my code I need a function to traverse an arbitrary JSON-like data structure with a path definition. E.g., given the structure
{"root": {"inner": [{"name": "obj1"}, {"name": "obj2"}]}}
and the path ("root", "inner", "0", "name"}
I would obtain "obj1"
. I just started learning Go and to be honest I found it quite hard to write it given the constraints imposed by Go's type system. Now it compiles and it seems to be working. To use it in my code, I needed additional helper functions with specific return types. All in all it seems to be a ugly hack with a lot of repetition. I'd like to see if this is the best one can do in Go.
Note: especially terrible is the distinction between map[string]string
and map[string]interface{}
. I though that interface{}
would represent every type, but apparently not in this case. If I remove that part from the switch
statement, the program does not work because map[string]string
is not handled by the map[string]interface{}
branch.
package main
import (
"fmt"
"strconv"
)
func main() {
test := map[string]interface{}{
"root": map[string]interface{}{
"inner": []interface{}{
map[string]string{"name": "obj1"},
map[string]string{"name": "obj2"},
},
},
}
fmt.Println(TraverseToStr(test, []string{"root", "inner", "0", "name"}))
}
func TraverseToUint64(data interface{}, path []string) uint64 {
val, err := Traverse(data, path)
if err != nil {
return 0
} else {
return val.(uint64)
}
}
func TraverseToStr(data interface{}, path []string) string {
val, err := Traverse(data, path)
if err != nil {
return ""
} else {
return val.(string)
}
}
func TraverseToMap(data interface{}, path []string) map[string]interface{} {
val, err := Traverse(data, path)
if err != nil {
return make(map[string]interface{})
} else {
return val.(map[string]interface{})
}
}
func TraverseToArray(data interface{}, path []string) []interface{} {
val, err := Traverse(data, path)
if err != nil {
return make([]interface{}, 0)
} else {
return val.([]interface{})
}
}
func Traverse(data interface{}, path []string) (interface{}, error) {
var ok bool
current := data
for _, p := range path {
switch current.(type) {
case map[string]string:
if current, ok = current.(map[string]string)[p]; !ok {
return nil, fmt.Errorf("key not found in map: %s", p)
}
case map[string]interface{}:
if current, ok = current.(map[string]interface{})[p]; !ok {
return nil, fmt.Errorf("key not found in map: %s", p)
}
case []interface{}:
if i, err := strconv.ParseInt(p, 10, 64); err == nil {
array := current.([]interface{})
if int(i) < len(array) {
current = array[i]
} else {
return nil, fmt.Errorf("index %d out of bounds for %v", i, array)
}
} else {
return nil, fmt.Errorf("integer required, got: %s", p)
}
default:
return nil, fmt.Errorf("cannot traverse %T\n", current)
}
}
return current, nil
}
```