Documentation
¶
Overview ¶
Example ¶
package main import ( "fmt" "reflect" "github.com/muir/reflectutils" ) type S struct { I1 int D T2 S string M T } type T struct { I2 int } type T2 struct { I3 int I4 int } func makeIntDoubler(t reflect.Type) func(v reflect.Value) { if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { panic("makeIntDoubler only supports pointers to structs") } var ints []reflect.StructField reflectutils.WalkStructElements(t, func(f reflect.StructField) bool { if f.Type.Kind() == reflect.Int { ints = append(ints, f) } return true }) return func(v reflect.Value) { v = v.Elem() for _, f := range ints { i := v.FieldByIndex(f.Index) i.SetInt(int64(i.Interface().(int)) * 2) } } } func makeIntDoublerNoRecursive(t reflect.Type) (func(v reflect.Value), error) { if t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { panic("makeIntDoublerNoRecursive only supports pointers to structs") } var ints []reflect.StructField err := reflectutils.WalkStructElementsWithError(t, func(f reflect.StructField) error { if f.Type.Kind() == reflect.Int { ints = append(ints, f) } return reflectutils.DoNotRecurseSignalErr }) return func(v reflect.Value) { v = v.Elem() for _, f := range ints { i := v.FieldByIndex(f.Index) i.SetInt(int64(i.Interface().(int)) * 2) } }, err } func main() { s := S{ I1: 3, D: T2{ I3: 2, I4: 6, }, S: "string", M: T{ I2: 5, }, } v := reflect.ValueOf(&s) doubler := makeIntDoubler(v.Type()) doubler(v) fmt.Printf("%v\n", v.Interface()) doubler, err := makeIntDoublerNoRecursive(v.Type()) doubler(v) fmt.Printf("%v\n", v.Interface()) fmt.Printf("%v", err) }
Output: &{6 {4 12} string {10}} &{12 {4 12} string {10}} <nil>
Index ¶
- Variables
- func FillInDefaultValues(pointerToStruct any) error
- func MakeStringSetter(t reflect.Type, optArgs ...StringSetterArg) (func(target reflect.Value, value string) error, error)
- func NonElement(t reflect.Type) reflect.Type
- func NonPointer(t reflect.Type) reflect.Type
- func RegisterStringSetter(fn interface{})
- func TypeName(t reflect.Type) string
- func WalkStructElements(t reflect.Type, f func(reflect.StructField) bool)
- func WalkStructElementsWithError(t reflect.Type, f func(reflect.StructField) error) error
- type FillOptArg
- type StringSetterArg
- type Tag
- type TagSet
- type Tags
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DoNotRecurseSignalErr = errors.New("walkstruct: do not recurse signal")
Functions ¶
func FillInDefaultValues ¶ added in v0.10.0
FillInDefaultValues will look at struct tags, looking for a "default" tag. If it finds one and the value in the struct is not set, then it will try to turn the string into a value. A pointer that is not nil is considered set and will not be overrridden.
It can handle fields with supported types. The supported types are: any type that implements encoding.TextUnmarshaler; time.Duration; and any types preregistered with RegisterStringSetter(); pointers to any of the above types.
The argument must be a pointer to a struct. Anything else will return error. A nil pointer is not allowed.
func MakeStringSetter ¶ added in v0.0.2
func MakeStringSetter(t reflect.Type, optArgs ...StringSetterArg) (func(target reflect.Value, value string) error, error)
MakeStringSetter handles setting a reflect.Value from a string. Based on type, it returns a function to do the work. It is assumed that the reflect.Type matches the reflect.Value. If not, panic is likely.
For arrays and slices, strings are split on comma to create the values for the elements.
Any type that matches a type registered with RegisterStringSetter will be unpacked with the corresponding function. A string setter is pre-registered for time.Duration Anything that implements encoding.TextUnmarshaler will be unpacked that way. Anything that implements flag.Value will be unpacked that way.
Maps, structs, channels, interfaces, channels, and funcs are not supported unless they happen to implent encoding.TextUnmarshaler.
func NonElement ¶ added in v0.6.0
NonElement unwraps pointers, slices, arrays, and maps until it finds a type that doesn't support Elem. It returns that type.
func NonPointer ¶ added in v0.3.1
NonPointer unwraps pointer types until a type that isn't a pointer is found.
func RegisterStringSetter ¶ added in v0.4.0
func RegisterStringSetter(fn interface{})
RegisterStringSetter registers functions that can be used to transform strings into specific types. The fn argument must be a function that takes a string and returns an arbitrary type and an error. An example of such a function is time.ParseDuration. Any call to RegisterStringSetter with a value that is not a function of that sort will panic.
RegisterStringSetter is not thread safe and should probably only be used during init().
These functions are used by MakeStringSetter() when there is an opportunity to do so.
func TypeName ¶ added in v0.7.0
TypeName is an alternative to reflect.Type's .String() method. The only expected difference is that if there is a package that is versioned, the version will appear in the package name.
For example, if there is a foo/v2 package with a Bar type, and you ask for for the TypeName, you'll get "foo/v2.Bar" instead of the "foo.Bar" that reflect returns.
func WalkStructElements ¶
func WalkStructElements(t reflect.Type, f func(reflect.StructField) bool)
WalkStructElements recursively visits the fields in a structure calling a callback for each field. It modifies the reflect.StructField object so that Index is relative to the root object originally passed to WalkStructElements. This allows the FieldByIndex method on a reflect.Value matching the original struct to fetch that field.
WalkStructElements should be called with a reflect.Type whose Kind() is reflect.Struct or whose Kind() is reflect.Ptr and Elem.Type() is reflect.Struct. All other types will simply be ignored.
The return value from f only matters when the type of the field is a struct. In that case, a false value prevents recursion.
func WalkStructElementsWithError ¶ added in v0.11.0
WalkStructElementsWithError recursively visits the fields in a structure calling a callback for each field. It modifies the reflect.StructField object so that Index is relative to the root object originally passed to WalkStructElementsWithError. This allows the FieldByIndex method on a reflect.Value matching the original struct to fetch that field.
WalkStructElementsWithError should be called with a reflect.Type whose Kind() is reflect.Struct or whose Kind() is reflect.Ptr and Elem.Type() is reflect.Struct. All other types will simply be ignored.
A non-nil return value from the called function stops iteration and recursion and becomes the return value.
A special error return value, DoNotRecurseSignalErr is not considered an error (it will not become the return value, and it does not stop iteration) but it will prevent recursion if returned on a field that is itself a struct.
Types ¶
type FillOptArg ¶ added in v0.2.0
type FillOptArg func(*fillOpt)
func WithTag ¶ added in v0.2.0
func WithTag(tag string) FillOptArg
WithTag overrides the tag used by Tag.Fill. The default is "pt".
type StringSetterArg ¶ added in v0.2.1
type StringSetterArg func(*stringSetterOpts)
func ForceJSON ¶ added in v0.8.0
func ForceJSON(b bool) StringSetterArg
ForceJSON controls if types will be decoded with JSON unmarshal. This overrides normal decoding patterns. The default is false.
func SliceAppend ¶ added in v0.3.0
func SliceAppend(b bool) StringSetterArg
Controls the behavior for setting existing existing slices. If this is true (the default) then additional setting to a slice appends to the existing slice. If false, slices are replaced.
func WithSplitOn ¶ added in v0.2.1
func WithSplitOn(s string) StringSetterArg
WithSplitOn specifies how to split strings into slices or arrays. An empty string indicates that input strings should not be split. That's different than the behavior of strings.Split(). If unspecified, strings will be split on comma (,).
type Tag ¶ added in v0.1.0
Tag represents a single element of a struct tag. For example for the field S in the struct below, there would be to Tags: one for json and one for xml. The value for the json one would be "s,omitempty".
type Foo struct { S string `json:"s,omitempty" xml:"s_thing"` }
func (Tag) Fill ¶ added in v0.2.0
func (tag Tag) Fill(model interface{}, opts ...FillOptArg) error
Fill unpacks struct tags into a struct based on tags of the desitnation struct. This is very meta. It is using struct tags to control parsing of struct tags. The tag being parsed is the receiver (tag). The model that controls the parsing is the function parameter (model). The parsing may be adjusted based on the opts.
type MyTags struct { Name string `pt:"0"` // selecting by position excludes other kinds of matching Flag bool `pt:"flag"` Int int `pt:"intValue"` }
The above will fill the Name field by grabbing the first element of the tag. It will fill Flag by noticing if any of the following are present in the comma-separated list of tag elements: "flag", "!flag" (sets to false), "flag=true", "flag=false", "flag=0", "flag=1", "flag=t", "flag=f", "flag=T", "flag=F". It will set Int by looking for "intValue=280" (set to 280).
When filling an array value, the default character to split upon is comma, but other values can be set with "split=X" to split on X. Special values of X are "quote", "space", and "none"
For bool values (and *bool, etc) an antonym can be specified:
MyBool bool `pt:"mybool,!other"`
So, then "mybool" maps to true, "!mybool" maps to false, "other" maps to false and "!other" maps to true.
Example ¶
Fill is a helper for when you're working with tags.
package main import ( "fmt" "reflect" "github.com/muir/reflectutils" ) type ExampleStruct struct { String string `foo:"something,N=9,square,!jump"` Bar string `foo:"different,!square,jump,ignore=xyz"` } type TagExtractorType struct { Name string `pt:"0"` // selecting by position excludes other kinds of matching NameAgain bool `pt:"something,different"` Square bool `pt:"square"` Jump bool `pt:"jump"` Ignore string `pt:"-"` N int } // Fill is a helper for when you're working with tags. func main() { var es ExampleStruct t := reflect.TypeOf(es) reflectutils.WalkStructElements(t, func(f reflect.StructField) bool { var tet TagExtractorType err := reflectutils.SplitTag(f.Tag).Set().Get("foo").Fill(&tet) if err != nil { fmt.Println(err) } fmt.Printf("%s: %+v\n", f.Name, tet) return true }) }
Output: String: {Name:something NameAgain:false Square:true Jump:false Ignore: N:9} Bar: {Name:different NameAgain:false Square:false Jump:true Ignore: N:0}
type TagSet ¶ added in v0.2.0
type TagSet struct { // Tags is a read-only value Tags Tags // contains filtered or unexported fields }
TagSet is a simple transformation of an array of Tag into an indexted structure so that lookup is efficient.
type Tags ¶ added in v0.2.0
type Tags []Tag
func SplitTag ¶ added in v0.1.0
SplitTag breaks apart a reflect.StructTag into an array of annotated key/value pairs. Tags are expected to be in the conventional format. What does "contentional" mean? `name:"values,value=value" name2:"value"`. See https://flaviocopes.com/go-tags/ a light introduction.