Skip to content

πŸƒ Go library to help traverse a complex nest of struct/map/array/slice

License

Notifications You must be signed in to change notification settings

ezraisw/traveller

Folders and files

NameName
Last commit message
Last commit date

Latest commit

eac9e50 Β· Jul 14, 2024

History

12 Commits
Jul 14, 2024
Dec 8, 2022
Jan 19, 2023
Dec 12, 2022
Jan 19, 2023
Jul 14, 2024
Jul 14, 2024
Dec 8, 2022
Dec 9, 2022
Jan 19, 2023
Dec 13, 2022
Jan 19, 2023
Jul 14, 2024
Dec 9, 2022
Dec 12, 2022
Dec 12, 2022

Repository files navigation

Traveller

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

Getting

Multiple Values

traveller.GetAll[T] will retrieve all value matching the given path and type.

allPasswords := traveller.GetAll[string](val, traveller.P("**.password"))

Single Value

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"))

Setting

Multiple Values

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

Single Value

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

Caveat of Setting Values

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.

Matcher

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{}})

Options

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.

About

πŸƒ Go library to help traverse a complex nest of struct/map/array/slice

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages