Go library to help traverse a complex nest of struct/map/array/slice.
package main
import (
"fmt"
"github.com/pwnedgod/traveller"
)
type swipe struct {
Plain string
Meaning string
Peace int
}
type facade struct {
Party string
Barrel any
Retirement float64
Tiger string
unexported string
}
type offroad struct {
Surprise []int
Social []float64
Bake string
Deprive string
}
type philosophy struct {
Deliver string
Thirsty string
Job any
Peace map[string]string
Tire offroad
}
func main() {
x := philosophy{
Deliver: "Something",
Thirsty: "Juice",
Job: facade{
Party: "Night",
Barrel: "Hidden",
Retirement: 90,
Tiger: "Claws",
unexported: "Inconspicuous",
},
Peace: map[string]string{
"Unfathomable": "Indeed",
"Joke": "Batman",
},
Tire: offroad{
Surprise: []int{1, 2, 3, 4},
Social: []float64{50.1, 60.2, 70.3},
Bake: "Cake",
Deprive: "Sleep",
},
}
allStrings := traveller.GetAll[string](x, traveller.P("**"))
fmt.Println(allStrings) // [Something Juice Night Hidden Claws Indeed Batman Cake Sleep]
}
traveller.GetAll[T]
will retrieve all value matching the given path and type.
allPasswords := traveller.GetAll[string](val, traveller.P("**.password"))
traveller.Get[T]
can be used along with the type that is desired. Will only return the first value matching the given path and type.
password, ok := traveller.Get[string](val, traveller.P("**.password"))
traveller.MustGet[T]
can also be used to obtain the desired value, but will panic if the value is not found.
password := traveller.MustGet[string](val, traveller.P("**.password"))
traveller.SetAll
and traveller.SetAllBy[T]
will attempt to set all matching values.
If the type is unassignable to that type, then the attempt will be ignored.
changeCount := traveller.SetAll(val, traveller.P("**.password"), "<hidden>")
changeCount := traveller.SetAllBy(val, traveller.P("**.password"), func(oldVal any) {
return MyHash(oldVal.(string))
})
traveller.Set
and traveller.SetBy[T]
will attempt to set the first successful matching value.
If the assignment was unsuccessful, it will continue searching.
hasChanged := traveller.Set(val, traveller.P("**.password"), "<hidden>")
hasChanged := traveller.SetBy(val, traveller.P("**.password"), func(oldVal string) {
return MyHash(oldVal)
})
Due to the nature of Go and some inaddressable values, if a value is deemed inaddressable, the traversed value will be reassigned as a copy on its parent. The resulting edit should still be the same, but please be aware of this little detail/hack.
Also be aware of pointers, especially if the same pointer to a value is unexpectedly used somewhere else.
This is what determines the matching behaviour. You can make your own Matcher by satisfying the following interface:
type Matcher interface {
// Return true to continue traversing.
Match(reflect.Value, MatcherSegment) (keepSearching bool)
}
The included matchers are:
MatchExact
: Exact match along with its type for key (string for field name, int for array/slice index, etc.).MatchPattern
: Match by wildcard pattern. Matching provided by github.com/gertd/wild.MatchMulti
: Recursive matching. Allows free deep traversal.
Path
and MustPath
(along with its shorthand P
and PCI
) return a []traveller.Matcher
and it is the direct type to be used. You can also make your own []traveller.Matcher
.
traveller.GetAll[string](val, []traveller.Matcher{traveller.MatchExact{Value: "something"}, traveller.MatchMulti{}})
There are several options that allows manipulation of the traversal behaviour.
traveller.GetAll[string](val, traveller.P("something.**.some*"),
traveller.WithIgnoreMaps(true),
traveller.WithNoFlatEmbeds(true),
// ...
)
WithNoFlatEmbeds
: If true, disallows "flattening" of embedded values. Like the following:
// Cannot get by "Value" on this struct because NoFlatEmbeds is set.
// Only "Inner.Value" is allowed.
type Outer struct {
Inner
}
type Inner struct {
Value string
}
WithIgnoreStructs
: Ignores structs on traversal. If the main value is a struct, then it will not search anything.WithIgnoreMaps
: Ignores maps on traversal. If the main value is a map, then it will not search anything.WithIgnoreArrays
: Ignore arrays and slices on traversal. If the main value is an array or a slice, then it will not search anything.