reflectutils

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2024 License: MIT Imports: 10 Imported by: 5

README

reflectutils - utility functions for working with Golang's reflect package

GoDoc unit tests report card codecov

Install:

go get github.com/muir/reflectutils

Reflectutils is simply a repository for functions useful for working with Golang's reflect package.

Here's the highlights:

Walking structures

func WalkStructElements(t reflect.Type, f func(reflect.StructField) bool)

Recursively walking a struct with reflect has a pitfalls:

  1. It isn't recurse with respect to embeded structs
  2. The Index field of reflect.Structfield of embedded structs is not relative to your starting point.

WalkStructElements() walks embedded elements and it updates StructField.Index so that it is relative to the root struct that was passed in.

Setting elements

func MakeStringSetter(t reflect.Type, optArgs ...StringSetterArg) (func(target reflect.Value, value string) error, error)

MakeStringSetter() returns a function that can be used to assing to reflect.Value given a string value. It can handle arrays and slices (splits strings on commas).

Parsing struct tags

Use SplitTag() to break a struct tag into it's elements and then use Tag.Fill() to parse it into a struct.

For example:

type TagInfo struct {
	Name	string	`pt:"0"`     // positional, first argument
	Train	bool	`pt:"train"` // boolean: true: "train", "train=true"; false: "!train", "train=false"
	Count	int	`pt:"count"` // integer value will be parsed
}

st := reflect.StructTag(`foo:"bar,!train,count=9"`)
var tagInfo TagInfo
err := GetTag(st, "foo").Fill(&tagInfo)

// tagInfo.Name will be "bar"
// tagInfo.Train will be false
// tagInfo.Count will be 9

Type names

The TypeName() function exists to disambiguate between type names that are versioned. reflect.Type.String() will hides package versions. This doesn't matter unless you've, unfortunately, imported multiple versions of the same package.

Development status

Reflectutils is used by several packages. Backwards compatability is expected.

Documentation

Overview

Example
package main

import (
	"fmt"
	"reflect"

	"github.com/muir/reflectutils"
)

type S struct {
	I1 int
	S  string
	M  T
}

type T struct {
	I2 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 main() {
	s := S{
		I1: 3,
		S:  "string",
		M: T{
			I2: 5,
		},
	}
	v := reflect.ValueOf(&s)
	doubler := makeIntDoubler(v.Type())
	doubler(v)
	fmt.Printf("%v\n", v.Interface())

}
Output:

&{6 string {10}}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

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

func NonElement(t reflect.Type) reflect.Type

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

func NonPointer(t reflect.Type) reflect.Type

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

func TypeName(t reflect.Type) string

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 reflec.Ptr and Elme.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.

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

type Tag struct {
	Tag   string
	Value string
}

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 GetTag added in v0.5.0

func GetTag(tags reflect.StructTag, tag string) Tag

func LookupTag added in v0.5.0

func LookupTag(tags reflect.StructTag, tag string) (Tag, bool)

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"`
	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"`
	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 Square:true Jump:false Ignore: N:9}
Bar: {Name:different 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.

func (TagSet) Get added in v0.2.0

func (s TagSet) Get(tag string) Tag

func (TagSet) Lookup added in v0.2.0

func (s TagSet) Lookup(tag string) (Tag, bool)

type Tags added in v0.2.0

type Tags []Tag

func SplitTag added in v0.1.0

func SplitTag(tags reflect.StructTag) Tags

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.

func (Tags) Set added in v0.2.0

func (t Tags) Set() TagSet

Directories

Path Synopsis
internal
foo

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL