Documentation ¶
Overview ¶
Package set is a performant reflect wrapper supporting loose type conversion, struct mapping and population, and slice building.
Type Coercion ¶
Value and its methods provide a generous facility for type coercion:
var t T // T is a target data type, t is a variable of that type. var s S // S is a source data type, s is a variable of that type. set.V(&t).To(s) // Sets s into t with a "best effort" approach.
See documentation and examples for Value.To.
The examples subdirectory contains additional examples for the Value type.
Finally you may wish to work directly with the coerce subpackage, which is the workhorse underneath Value.To.
Populating Structs by Lookup Function ¶
See examples for GetterFunc.
Populating Structs by Map ¶
See example for MapGetter.
Struct Mapping ¶
Struct and struct hierarchies can be mapped to a flat list of string keys. This is useful for deserializers and unmarshalers that need to convert a friendly string such as a column name or environment variable and use it to locate a target field within a struct or its hierarchy.
See examples for Mapper.
Mapping, BoundMapping, and PreparedMapping ¶
Once an instance of Mapper is created it can be used to create Mapping, BoundMapping, and PreparedMapping instances that facilitate struct traversal and population.
BoundMapping and PreparedMapping are specialized types that bind to an instance of T and allow performant access to T's fields or values.
See examples for Mapper.Bind and Mapper.Prepare.
In tight-loop scenarios an instance of BoundMapping or PreparedMapping can be bound to a new instance T with the Rebind method.
See examples for BoundMapping.Rebind and PreparedMapping.Rebind.
If neither BoundMapping nor PreparedMapping are suitable for your use case you can call Mapper.Map to get a general collection of data in a Mapping. The data in a Mapping may be helpful for creating your own traversal or population algorithm without having to dive into all the complexities of the reflect package.
BoundMapping vs PreparedMapping ¶
BoundMapping allows adhoc access to the bound struct T. You can set or retrieve fields in any order. Conceptually a BoundMapping is similar to casting a struct and its hierarchy into a map of string keys that target fields within the hierarchy.
PreparedMapping requires an access plan to be set by calling the Plan method. Once set the bound value's fields must be set or accessed in the order described by the plan. A PreparedMapping is similar to a prepared SQL statement.
Of the two PreparedMapping yields better performance. You should use PreparedMapping when you know every bound value will have its fields accessed in a determinate order. If fields will not be accessed in a determinate order then you should use a BoundMapping.
BoundMapping methods require the field(s) as arguments; in some ways this can help with readability as your code will read:
b.Set("FooField", "Hello") b.Set("Number", 100)
whereas code using PreparedMapping will read:
p.Set("Hello") p.Set(100)
What is Rebind ¶
The BoundMapping, PreparedMapping, and Value types internally contain meta data about the types they are working with. Most of this meta data is obtained with calls to reflect and calls to reflect can be expensive.
In one-off scenarios the overhead of gathering meta data is generally not a concern. But in tight-loop situations this overhead begins to add up and is a key reason reflect has gained a reputation for being slow in the Go community.
Where appropriate types in this package have a Rebind method. Rebind swaps the current value being worked on with a new incoming value without regathering reflect meta data. When used appropriately with Rebind the BoundMapping, PreparedMapping, and Value types become much more performant.
A Note About Package Examples ¶
Several examples ignore errors for brevity:
_ = p.Plan(...) // error ignored for brevity _ = b.Set(...) // error ignored for brevity
This is a conscious decision because error checking is not the point of the examples. However in production code you should check errors appropriately.
Example (BoundMappingErrors) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates some of the errors returned from the BoundMapping type. type Person struct { Name string `json:"name"` Age int `json:"age"` } type Company struct { Name string `json:"name"` Owner Person `json:"owner"` } mapper := set.Mapper{ Tags: []string{"json"}, Join: "_", } var company Company var err error readonly, _ := mapper.Bind(company) b, _ := mapper.Bind(&company) // set: BoundMapping.Assignables: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] _, err = readonly.Assignables([]string{"name"}, nil) fmt.Println(err) // set: BoundMapping.Field: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] _, err = readonly.Field("name") fmt.Println(err) // set: BoundMapping.Fields: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] _, err = readonly.Fields([]string{"name"}, nil) fmt.Println(err) // set: BoundMapping.Set: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] err = readonly.Set("foobar", "ABC Widgets") fmt.Println(err) // set: BoundMapping.Assignables: unknown field: field [foobar] not found in type *set_test.Company _, err = b.Assignables([]string{"foobar"}, nil) fmt.Println(err) // set: BoundMapping.Field: unknown field: field [foobar] not found in type *set_test.Company _, err = b.Field("foobar") fmt.Println(err) // set: BoundMapping.Fields: unknown field: field [foobar] not found in type *set_test.Company _, err = b.Fields([]string{"foobar"}, nil) fmt.Println(err) // set: BoundMapping.Set: unknown field: field [foobar] not found in type *set_test.Company err = b.Set("foobar", "ABC Widgets") fmt.Println(err) }
Output: set: BoundMapping.Assignables: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] set: BoundMapping.Field: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] set: BoundMapping.Fields: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] set: BoundMapping.Set: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] set: BoundMapping.Assignables: unknown field: field [foobar] not found in type *set_test.Company set: BoundMapping.Field: unknown field: field [foobar] not found in type *set_test.Company set: BoundMapping.Fields: unknown field: field [foobar] not found in type *set_test.Company set: BoundMapping.Set: unknown field: field [foobar] not found in type *set_test.Company
Example (CSVUnmarshaler) ¶
package main import ( "encoding/csv" "errors" "fmt" "io" "reflect" "strings" "github.com/nofeaturesonlybugs/set" ) // This example demonstrates how Mapper might be used to create a general // purpose CSV unmarshaler. // CSVUnmarshaler is a general purpose CSV unmarshaler. type CSVUnmarshaler struct { Mapper *set.Mapper } // Load reads CSV data from r and deserializes each row into dst. func (u CSVUnmarshaler) Load(r io.Reader, dst interface{}) error { // Expect dst to be a *[]T or pointer chain to []T where T is struct or pointer chain to struct. slice, err := set.Slice(dst) if err != nil { return err } else if slice.ElemEndType.Kind() != reflect.Struct { return fmt.Errorf("dst elements should be struct or pointer to struct") } // m := u.Mapper if m == nil { // When the Mapper member is nil use a default mapper. m = &set.Mapper{ Tags: []string{"csv"}, } } // // Now the CSV can be read, processed, and deserialized into dst. c := csv.NewReader(r) // // Load the column names; these will be used later when calling BoundMapping.Set headers, err := c.Read() if err != nil { return err } c.ReuseRecord = true // From this point forward the csv reader can reuse the slice. // // Create a BoundMapping for element's type. b, err := m.Bind(slice.Elem()) if err != nil { return err } for { row, err := c.Read() if errors.Is(err, io.EOF) { return nil } else if err != nil { return err } // Create a new element and bind to it. elemValue := slice.Elem() // Elem() returns reflect.Value and Rebind() conveniently b.Rebind(elemValue) // allows reflect.Value as an argument. // Row is a slice of data and headers has the mapped names in corresponding indexes. for k, columnName := range headers { _ = b.Set(columnName, row[k]) // err will be checked after iteration. } // b.Err() returns the first error encountered from b.Set() if err = b.Err(); err != nil { return err } // Append to dst. slice.Append(elemValue) } } func main() { type Address struct { ID int `json:"id"` Street string `json:"street" csv:"address"` City string `json:"city"` State string `json:"state"` Zip string `json:"zip" csv:"postal"` } PrintAddresses := func(all []Address) { for _, a := range all { fmt.Printf("ID=%v %v, %v, %v %v\n", a.ID, a.Street, a.City, a.State, a.Zip) } } // loader := CSVUnmarshaler{ Mapper: &set.Mapper{ // Tags are listed in order of priority so csv has higher priority than json // when generating mapped names. Tags: []string{"csv", "json"}, }, } var data = `id,address,city,state,postal 1,06 Hoepker Court,Jacksonville,Florida,32209 2,92 Cody Hill,Falls Church,Virginia,22047 3,242 Burning Wood Terrace,Fort Worth,Texas,76105 4,41 Clarendon Pass,Fort Myers,Florida,33913 ` var addresses []Address err := loader.Load(strings.NewReader(data), &addresses) if err != nil { fmt.Println(err) return } PrintAddresses(addresses) // Notice here this data has the columns in a different order fmt.Println() data = `city,address,postal,id,state Tuscaloosa,2607 Hanson Junction,35487,1,Alabama Bakersfield,2 Sherman Place,93305,2,California Kansas City,4 Porter Place,64199,3,Missouri New York City,23 Sachtjen Alley,10160,4,New York` addresses = addresses[0:0] err = loader.Load(strings.NewReader(data), &addresses) if err != nil { fmt.Println(err) return } PrintAddresses(addresses) }
Output: ID=1 06 Hoepker Court, Jacksonville, Florida 32209 ID=2 92 Cody Hill, Falls Church, Virginia 22047 ID=3 242 Burning Wood Terrace, Fort Worth, Texas 76105 ID=4 41 Clarendon Pass, Fort Myers, Florida 33913 ID=1 2607 Hanson Junction, Tuscaloosa, Alabama 35487 ID=2 2 Sherman Place, Bakersfield, California 93305 ID=3 4 Porter Place, Kansas City, Missouri 64199 ID=4 23 Sachtjen Alley, New York City, New York 10160
Example (MapperErrors) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates some of the errors returned from the Mapper type. type Person struct { Name string `json:"name"` Age int `json:"age"` } type Company struct { Name string `json:"name"` Owner Person `json:"owner"` } mapper := set.Mapper{ Tags: []string{"json"}, Join: "_", } var company Company var err error // set: Mapper.Bind: read only value: set_test.Company is not writable: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] _, err = mapper.Bind(company) fmt.Println(err) // set: Mapper.Prepare: read only value: set_test.Company is not writable: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] _, err = mapper.Prepare(company) fmt.Println(err) }
Output: set: Mapper.Bind: read only value: hint=[call to Mapper.Bind(set_test.Company) should have been Mapper.Bind(*set_test.Company)] set: Mapper.Prepare: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)]
Example (PreparedMappingErrors) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates some of the errors returned from the PreparedMapping type. type Person struct { Name string `json:"name"` Age int `json:"age"` } type Company struct { Name string `json:"name"` Owner Person `json:"owner"` } mapper := set.Mapper{ Tags: []string{"json"}, Join: "_", } var company Company var err error readonly, _ := mapper.Prepare(company) p, _ := mapper.Prepare(&company) // set: PreparedMapping.Assignables: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] _, err = readonly.Assignables(nil) fmt.Println(err) // set: PreparedMapping.Field: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] _, err = readonly.Field() fmt.Println(err) // set: PreparedMapping.Fields: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] _, err = readonly.Fields(nil) fmt.Println(err) // set: PreparedMapping.Plan: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] err = readonly.Plan("name") fmt.Println(err) // set: PreparedMapping.Field: no plan: hint=[call PreparedMapping.Plan to prepare access plan for *set_test.Company] _, err = p.Field() fmt.Println(err) // set: PreparedMapping.Set: no plan: hint=[call PreparedMapping.Plan to prepare access plan for *set_test.Company] err = p.Set("ABC Widgets") fmt.Println(err) // set: PreparedMapping.Plan: unknown field: field [foobar] not found in type *set_test.Company err = p.Plan("foobar") fmt.Println(err) _ = p.Plan("name", "owner_name") // No error _, _ = p.Field() // No error _, _ = p.Field() // No error // set: PreparedMapping.Field: attempted access extends plan: value of *set_test.Company _, err = p.Field() fmt.Println(err) _ = p.Plan("name", "owner_name") // No error _ = p.Set("ABC Widgets") // No error _ = p.Set("Larry") // No error // set: PreparedMapping.Set: attempted access extends plan: value of *set_test.Company err = p.Set("extended the plan") fmt.Println(err) }
Output: set: PreparedMapping.Assignables: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] set: PreparedMapping.Field: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] set: PreparedMapping.Fields: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] set: PreparedMapping.Plan: read only value: hint=[call to Mapper.Prepare(set_test.Company) should have been Mapper.Prepare(*set_test.Company)] set: PreparedMapping.Field: no plan: hint=[call PreparedMapping.Plan to prepare access plan for *set_test.Company] set: PreparedMapping.Set: no plan: hint=[call PreparedMapping.Plan to prepare access plan for *set_test.Company] set: PreparedMapping.Plan: unknown field: field [foobar] not found in type *set_test.Company set: PreparedMapping.Field: attempted access extends plan: value of *set_test.Company set: PreparedMapping.Set: attempted access extends plan: value of *set_test.Company
Example (ValueErrors) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates some of the errors returned from the Value type. type S struct { A struct { AA int BB int } B int } var s S var slice []int var n int var err error // set: Value.Append: unsupported: nil value: hint=[set.V(nil) was called] err = set.V(nil).Append(42, 24) fmt.Println(err) // set: Value.Append: read only value: []int is not writable: hint=[call to set.V([]int) should have been set.V(*[]int)] err = set.V(slice).Append(42, 24) fmt.Println(err) // set: Value.Append: unsupported: can not append to int err = set.V(&n).Append(42) fmt.Println(err) // set: Value.FieldByIndex: unsupported: nil value: hint=[set.V(nil) was called] _, err = set.V(nil).FieldByIndex([]int{0}) fmt.Println(err) // set: Value.FieldByIndex: read only value: set_test.S is not writable: hint=[call to set.V(set_test.S) should have been set.V(*set_test.S)] _, err = set.V(s).FieldByIndex([]int{0}) fmt.Println(err) // set: Value.FieldByIndex: unsupported: empty index _, err = set.V(&s).FieldByIndex(nil) fmt.Println(err) // set: Value.FieldByIndex: set: index out of bounds: index 2 exceeds max 1 _, err = set.V(&s).FieldByIndex([]int{2}) fmt.Println(err) // set: Value.FieldByIndex: unsupported: want struct but got int _, err = set.V(&s).FieldByIndex([]int{1, 0}) fmt.Println(err) // TODO Fill+FillByTag // set: Value.Zero: unsupported: nil value: hint=[set.V(nil) was called] err = set.V(nil).Zero() fmt.Println(err) // set: Value.Zero: read only value: int is not writable: hint=[call to set.V(int) should have been set.V(*int)] err = set.V(n).Zero() fmt.Println(err) // set: Value.To: unsupported: nil value: hint=[set.V(nil) was called] err = set.V(nil).To("Hello!") fmt.Println(err) err = set.V(n).To(42) // set: Value.To: read only value: int is not writable: hint=[call to set.V(int) should have been set.V(*int)] fmt.Println(err) }
Output: set: Value.Append: unsupported: nil value: hint=[set.V(nil) was called] set: Value.Append: read only value: []int is not writable: hint=[call to set.V([]int) should have been set.V(*[]int)] set: Value.Append: unsupported: can not append to int set: Value.FieldByIndex: unsupported: nil value: hint=[set.V(nil) was called] set: Value.FieldByIndex: read only value: set_test.S is not writable: hint=[call to set.V(set_test.S) should have been set.V(*set_test.S)] set: Value.FieldByIndex: unsupported: empty index set: Value.FieldByIndex: index out of bounds: index 2 exceeds max 1 set: Value.FieldByIndex: unsupported: want struct but got int set: Value.Zero: unsupported: nil value: hint=[set.V(nil) was called] set: Value.Zero: read only value: int is not writable: hint=[call to set.V(int) should have been set.V(*int)] set: Value.To: unsupported: nil value: hint=[set.V(nil) was called] set: Value.To: read only value: int is not writable: hint=[call to set.V(int) should have been set.V(*int)]
Index ¶
- Variables
- func Writable(v reflect.Value) (V reflect.Value, CanWrite bool)
- type BoundMapping
- func (b BoundMapping) Assignables(fields []string, rv []interface{}) ([]interface{}, error)
- func (b BoundMapping) Copy() BoundMapping
- func (b BoundMapping) Err() error
- func (b BoundMapping) Field(field string) (Value, error)
- func (b BoundMapping) Fields(fields []string, rv []interface{}) ([]interface{}, error)
- func (b *BoundMapping) Rebind(v interface{})
- func (b *BoundMapping) Set(field string, value interface{}) error
- type CanPanic
- type Field
- type Getter
- type GetterFunc
- type Mapper
- type Mapping
- type PreparedMapping
- func (p PreparedMapping) Assignables(rv []interface{}) ([]interface{}, error)
- func (p PreparedMapping) Copy() PreparedMapping
- func (p PreparedMapping) Err() error
- func (p *PreparedMapping) Field() (Value, error)
- func (p PreparedMapping) Fields(rv []interface{}) ([]interface{}, error)
- func (p *PreparedMapping) Plan(fields ...string) error
- func (p *PreparedMapping) Rebind(v interface{})
- func (p *PreparedMapping) Set(value interface{}) error
- type SliceValue
- type TypeInfo
- type TypeInfoCache
- type TypeList
- type Value
- func (v Value) Append(items ...interface{}) error
- func (v Value) Copy() Value
- func (v Value) FieldByIndex(index []int) (reflect.Value, error)
- func (v Value) FieldByIndexAsValue(index []int) (Value, error)
- func (v Value) Fields() []Field
- func (v Value) FieldsByTag(key string) []Field
- func (v Value) Fill(getter Getter) error
- func (v Value) FillByTag(key string, getter Getter) error
- func (v Value) NewElem() (Value, error)
- func (v *Value) Rebind(arg interface{})
- func (v Value) To(arg interface{}) error
- func (v Value) Zero() error
Examples ¶
- Package (BoundMappingErrors)
- Package (CSVUnmarshaler)
- Package (MapperErrors)
- Package (PreparedMappingErrors)
- Package (ValueErrors)
- BoundMapping.Rebind
- BoundMapping.Rebind (Panic)
- BoundMapping.Rebind (ReflectValue)
- GetterFunc
- GetterFunc (FillByTag)
- MapGetter
- Mapper
- Mapper (TreatAsScalar)
- Mapper (TreatAsScalarTime)
- Mapper.Bind
- Mapper.Bind (ElevatedEmbed)
- Mapper.Bind (Embedded)
- Mapper.Bind (Nesting)
- Mapper.Bind (ReflectValue)
- Mapper.Prepare
- Mapper.Prepare (ElevatedEmbed)
- Mapper.Prepare (Embedded)
- Mapper.Prepare (Nesting)
- Mapper.Prepare (ReflectValue)
- PreparedMapping.Rebind
- PreparedMapping.Rebind (Panic)
- PreparedMapping.Rebind (ReflectValue)
- Slice
- Slice (Errors)
- Value.Rebind
- Value.Rebind (Panic)
- Value.Rebind (ReflectValue)
- Value.Rebind (Value)
- Value.To
- Value.To (ScalarToSlice)
- Value.To (SliceToScalar)
- Value.To (SliceToSlice)
- Writable
Constants ¶
This section is empty.
Variables ¶
var ( // ErrIndexOutOfBounds is returned when an index operation exceeds a bounds check. ErrIndexOutOfBounds = errors.New("index out of bounds") // ErrInvalidSlice is returned by NewSlice when the in coming value is not pointer-to-slice. ErrInvalidSlice = errors.New("invalid slice") // ErrNoPlan is returned when a PreparedMapping does not have a valid access plan. ErrNoPlan = errors.New("no plan") // ErrPlanOutOfBounds is returned when an access to a PreparedMapping exceeds the // fields specified by the earlier call to Plan. ErrPlanOutOfBounds = errors.New("attempted access extends plan") // ErrReadOnly is returned when an incoming argument is expected to be passed by address // but is passed by value instead. ErrReadOnly = errors.New("read only value") // ErrUnknownField is returned by BoundMapping and PreparedMapping when given field // has no correlating mapping within the struct hierarchy. ErrUnknownField = errors.New("unknown field") // ErrUnsupported is returned when an assignment or coercion is incompatible due to the // destination and source type(s). ErrUnsupported = errors.New("unsupported") )
var DefaultMapper = &Mapper{
Join: "_",
}
DefaultMapper joins names by "_" but performs no other modifications.
var Panics = CanPanic{}
Panics is a global instance of CanPanic; it is provided for convenience.
var TypeCache = NewTypeInfoCache()
TypeCache is a global TypeInfoCache
Functions ¶
func Writable ¶
Writable attempts to make a reflect.Value usable for writing. It will follow and instantiate nil pointers if necessary.
Example ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { var value, writable reflect.Value var ok bool var s string var sp *string value = reflect.ValueOf(s) _, ok = set.Writable(value) fmt.Printf("ok= %v\n", ok) value = reflect.ValueOf(sp) _, ok = set.Writable(value) fmt.Printf("ok= %v\n", ok) value = reflect.ValueOf(&sp) writable, ok = set.Writable(value) writable.SetString("Hello") fmt.Printf("ok= %v sp= %v\n", ok, *sp) }
Output: ok= false ok= false ok= true sp= Hello
Types ¶
type BoundMapping ¶
type BoundMapping struct {
// contains filtered or unexported fields
}
BoundMapping is returned from Mapper's Bind method.
A BoundMapping must not be copied except via its Copy method.
A BoundMapping should be used in iterative code that needs to read or mutate many instances of the same struct. Bound mappings allow for adhoc or indeterminate field access within the bound data.
// adhoc access means different fields can be accessed between calls to Rebind() var a, b T bound := myMapper.Map(&a) bound.Set("Field", 10) bound.Set("Other", "Hello") bound.Rebind(&b) bound.Set("Bar", 27)
In the preceding example the BoundMapping is first bound to a and later bound to b and each instance had different field(s) accessed.
func (BoundMapping) Assignables ¶
func (b BoundMapping) Assignables(fields []string, rv []interface{}) ([]interface{}, error)
Assignables returns a slice of pointers to the fields in the currently bound struct in the order specified by the fields argument.
To alleviate pressure on the garbage collector the return slice can be pre-allocated and passed as the second argument to Assignables. If non-nil it is assumed len(fields) == len(rv) and failure to provide an appropriately sized non-nil slice will cause a panic.
During traversal this method will allocate struct fields that are nil pointers.
An example use-case would be obtaining a slice of pointers for Rows.Scan() during database query results.
func (BoundMapping) Copy ¶ added in v0.3.0
func (b BoundMapping) Copy() BoundMapping
Copy creates an exact copy of the BoundMapping.
One use case for Copy is to create a set of BoundMappings early in a program's init phase. During later execution when a BoundMapping is needed for type T it can be obtained by calling Copy on the cached BoundMapping for that type.
func (BoundMapping) Err ¶
func (b BoundMapping) Err() error
Err returns an error that may have occurred during repeated calls to Set(); it is reset on calls to Rebind()
func (BoundMapping) Field ¶
func (b BoundMapping) Field(field string) (Value, error)
Field returns the Value for field.
func (BoundMapping) Fields ¶ added in v0.3.0
func (b BoundMapping) Fields(fields []string, rv []interface{}) ([]interface{}, error)
Fields returns a slice of values to the fields in the currently bound struct in the order specified by the fields argument.
To alleviate pressure on the garbage collector the return slice can be pre-allocated and passed as the second argument to Fields. If non-nil it is assumed len(fields) == len(rv) and failure to provide an appropriately sized non-nil slice will cause a panic.
During traversal this method will allocate struct fields that are nil pointers.
An example use-case would be obtaining a slice of query arguments by column name during database queries.
func (*BoundMapping) Rebind ¶
func (b *BoundMapping) Rebind(v interface{})
Rebind will replace the currently bound value with the new variable v.
v must have the same type as the original value used to create the BoundMapping otherwise a panic will occur.
As a convenience Rebind allows v to be an instance of reflect.Value. This prevents unnecessary calls to reflect.Value.Interface().
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // Once a BoundMapping has been created you can swap out the value it mutates // by calling Rebind. This yields better performance in tight loops where // you intend to mutate many instances of the same type. m := &set.Mapper{} type S struct { Str string Num int } values := []map[string]interface{}{ {"Str": "First", "Num": 1}, {"Str": "Second", "Num": 2}, {"Str": "Third", "Num": 3}, } slice := make([]S, 3) b, _ := m.Bind(&slice[0]) // We can bind on the first value to get a BoundMapping for k := range slice { b.Rebind(&slice[k]) // The BoundMapping now affects slice[k] for key, value := range values[k] { _ = b.Set(key, value) // error ignored for brevity } } fmt.Println(slice[0].Num, slice[0].Str) fmt.Println(slice[1].Num, slice[1].Str) fmt.Println(slice[2].Num, slice[2].Str) }
Output: 1 First 2 Second 3 Third
Example (Panic) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // BoundMapping.Rebind panics if the new instance is not the same type. m := &set.Mapper{} type S struct { Str string } type Different struct { Str string } defer func() { if r := recover(); r != nil { fmt.Println(r) } }() var s S var d Different b, _ := m.Bind(&s) b.Rebind(&d) }
Output: mismatching types during Rebind; have *set_test.S and got *set_test.Different
Example (ReflectValue) ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience BoundMapping.Rebind can be called with an instance of reflect.Value // if-and-only-if the reflect.Value is holding a type compatible with the BoundMapping. m := &set.Mapper{} type S struct { Str string Num int } var s S // Errors ignored for brevity. b, _ := m.Bind(&s) _ = b.Set("Num", 42) // b.Set errors ignored for brevity _ = b.Set("Str", "Hello") rv := reflect.New(reflect.TypeOf(s)) // reflect.New creates a *S which is the type b.Rebind(rv) // originally bound. Therefore b.Rebind(rv) is valid. _ = b.Set("Num", 100) _ = b.Set("Str", "reflect.Value!") r := rv.Elem().Interface().(S) fmt.Println(s.Str, s.Num) fmt.Println(r.Str, r.Num) }
Output: Hello 42 reflect.Value! 100
func (*BoundMapping) Set ¶
func (b *BoundMapping) Set(field string, value interface{}) error
Set effectively sets V[field] = value.
type CanPanic ¶
type CanPanic struct{}
CanPanic is a namespace for operations prioritizing speed over type safety or error checking. Reach for this namespace when your usage of the `set` package is carefully crafted to ensure panics will not result from your actions.
Methods within CanPanic will not validate that points are non-nil.
It is strongly encouraged to create suitable `go tests` within your project when reaching for CanPanic.
You do not need to create or instantiate this type; instead you can use the global `var Panics`.
type Field ¶
type Field struct { Value Value Field reflect.StructField TagValue string }
Field is a struct field; it contains a Value and a reflect.StructField.
type Getter ¶
type Getter interface { // Get accepts a name and returns the value. Get(name string) interface{} }
Getter returns a value by name.
func MapGetter ¶
func MapGetter(m interface{}) Getter
MapGetter accepts a map and returns a Getter.
Map keys must be string or interface{}.
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // A map can also be used as a Getter by casting to MapGetter. // // Note that the map key must be either string or interface{}. // // If the map contains nested maps that can be used as MapGetter // then those maps can populate nested structs in the hierarchy. m := map[string]interface{}{ "name": "Bob", "age": 42, "address": map[interface{}]string{ "street1": "97531 Some Street", "street2": "", "city": "Big City", "state": "ST", "zip": "12345", }, } myGetter := set.MapGetter(m) type Address struct { Street1 string `key:"street1"` Street2 string `key:"street2"` City string `key:"city"` State string `key:"state"` Zip string `key:"zip"` } type Person struct { Name string `key:"name"` Age uint `key:"age"` Address Address `key:"address"` } var t Person _ = set.V(&t).FillByTag("key", myGetter) // error ignored for brevity fmt.Println(t.Name, t.Age) fmt.Printf("%v, %v, %v %v\n", t.Address.Street1, t.Address.City, t.Address.State, t.Address.Zip) }
Output: Bob 42 97531 Some Street, Big City, ST 12345
type GetterFunc ¶
type GetterFunc func(name string) interface{}
GetterFunc casts a function into a Getter.
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // A GetterFunc allows a function to be used as the producer for // populating a struct. // Note in this example the key names match the field names of the struct // and Value.Fill is used to populate the struct. // F has the correct signature to become a GetterFunc. F := func(name string) interface{} { switch name { case "Name": return "Bob" case "Age": return "42" default: return nil } } myGetter := set.GetterFunc(F) type T struct { Name string Age uint } var t T _ = set.V(&t).Fill(myGetter) // error ignored for brevity fmt.Println(t.Name, t.Age) }
Output: Bob 42
Example (FillByTag) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // In this example the key names are lower case and match values // in the struct tags. To populate a struct by tag use Value.FillByTag. // Also note that `address` returns a new GetterFunc used to populate // the nested Address struct in Person. F := func(key string) interface{} { switch key { case "name": return "Bob" case "age": return "42" case "address": return set.GetterFunc(func(key string) interface{} { switch key { case "street1": return "97531 Some Street" case "street2": return "" case "city": return "Big City" case "state": return "ST" case "zip": return "12345" default: return nil } }) default: return nil } } myGetter := set.GetterFunc(F) type Address struct { Street1 string `key:"street1"` Street2 string `key:"street2"` City string `key:"city"` State string `key:"state"` Zip string `key:"zip"` } type Person struct { Name string `key:"name"` Age uint `key:"age"` Address Address `key:"address"` } var t Person _ = set.V(&t).FillByTag("key", myGetter) // error ignored for brevity fmt.Println(t.Name, t.Age) fmt.Printf("%v, %v, %v %v\n", t.Address.Street1, t.Address.City, t.Address.State, t.Address.Zip) }
Output: Bob 42 97531 Some Street, Big City, ST 12345
func (GetterFunc) Get ¶
func (me GetterFunc) Get(name string) interface{}
Get accepts a name and returns the value.
type Mapper ¶
type Mapper struct { // If the types you wish to map contain embedded structs or interfaces you do not // want to map to string names include those types in the Ignored member. // // See also NewTypeList(). Ignored TypeList // Struct fields that are also structs or embedded structs will have their name // as part of the generated name unless it is included in the Elevated member. // // See also NewTypeList(). Elevated TypeList // Types in this list are treated as scalars when generating mappings; in other words // their exported fields are not mapped and the mapping created targets the type as // a whole. This is useful when you want to create mappings for types such as sql.NullString // without traversing within the sql.NullString itself. TreatAsScalar TypeList // A list of struct tags that will be used for name generation in order of preference. // An example would be using this feature for both JSON and DB field name specification. // If most of your db and json names match but you occasionally want to override the json // struct tag value with the db struct tag value you could set this member to: // []string{ "db", "json" } // struct tag `db` used before struct tag `json` Tags []string // When TaggedFieldsOnly is true the Map() method only maps struct fields that have tags // matching a value in the Tags field. In other words exported tag-less struct fields are not // mapped. TaggedFieldsOnly bool // Join specifies the string used to join generated names as nesting increases. Join string // If set this function is called when the struct field name is being used as // the generated name. This function can perform string alteration to force all // names to lowercase, string replace, etc. Transform func(string) string // contains filtered or unexported fields }
Mapper creates Mapping instances from structs and struct hierarchies.
Mapper allows you to take any struct or struct hierarchy and flatten it to a set of key or column names that index into the hierarchy.
Each of the public fields on Mapper controls an aspect of its behavior in order to provide fine grained control over how key names are generated or how types unknown to the set package are treated.
Instantiate mappers as pointers:
myMapper := &set.Mapper{}
Example ¶
package main import ( "fmt" "strings" "github.com/nofeaturesonlybugs/set" ) func main() { // This example maps the same type (Person) with three different Mapper instances. // Each Mapper instance has slightly different configuration to control the mapped // names that are generated. type CommonDb struct { Pk int `t:"pk"` CreatedTime string `t:"created_time"` UpdatedTime string `t:"updated_time"` } type Person struct { CommonDb `t:"common"` Name string `t:"name"` Age int `t:"age"` } var data Person { mapper := &set.Mapper{ Elevated: set.NewTypeList(CommonDb{}), Join: "_", } mapping := mapper.Map(&data) fmt.Println(strings.ReplaceAll(mapping.String(), "\t\t", " ")) } { fmt.Println("") fmt.Println("lowercase with dot separators") mapper := &set.Mapper{ Join: ".", Transform: strings.ToLower, } mapping := mapper.Map(&data) fmt.Println(strings.ReplaceAll(mapping.String(), "\t\t", " ")) } { fmt.Println("") fmt.Println("specify tags") mapper := &set.Mapper{ Join: "_", Tags: []string{"t"}, } mapping := mapper.Map(&data) fmt.Println(strings.ReplaceAll(mapping.String(), "\t\t", " ")) } }
Output: [0 0] Pk [0 1] CreatedTime [0 2] UpdatedTime [1] Name [2] Age lowercase with dot separators [0 0] commondb.pk [0 1] commondb.createdtime [0 2] commondb.updatedtime [1] name [2] age specify tags [0 0] common_pk [0 1] common_created_time [0 2] common_updated_time [1] name [2] age
Example (TreatAsScalar) ¶
package main import ( "database/sql" "fmt" "strings" "github.com/nofeaturesonlybugs/set" ) func main() { // By default Mapper maps **all** exported fields in nested or embedded structs. type S struct { N sql.NullString // sql.NullString has public fields that can be mapped. } var s S // Notice that **this** Mapper maps the public fields in the sql.NullString type. fmt.Println("Without TreatAsScalar") all := &set.Mapper{ Join: ".", } m := all.Map(&s) fmt.Println(strings.ReplaceAll(m.String(), "\t\t", " ")) // While **this** Mapper treats N as a scalar field and does not map its public fields. fmt.Println("TreatAsScalar") scalar := &set.Mapper{ TreatAsScalar: set.NewTypeList(sql.NullString{}), // Fields of sql.NullString are now treated as scalars. Join: ".", } m = scalar.Map(&s) fmt.Println(strings.ReplaceAll(m.String(), "\t\t", " ")) }
Output: Without TreatAsScalar [0 0] N.String [0 1] N.Valid TreatAsScalar [0] N
Example (TreatAsScalarTime) ¶
package main import ( "fmt" "strings" "time" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience time.Time and *time.Time are always treated as scalars. type S struct { T time.Time TPtr *time.Time } var s S // Even though this mapper does not configure TreatAsScalar the time.Time fields **are** // treated as scalars. all := &set.Mapper{ Join: ".", } m := all.Map(&s) fmt.Println(strings.ReplaceAll(m.String(), "\t\t", " ")) }
Output: [0] T [1] TPtr
func (*Mapper) Bind ¶
func (me *Mapper) Bind(I interface{}) (BoundMapping, error)
Bind creates a BoundMapping that is initially bound to I.
BoundMappings are provided for performance critical code that needs to read or mutate many instances of the same type repeatedly without constraint on field access between instances.
See documentation for BoundMapping for more details.
I must be an addressable type.
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates a simple flat struct of primitives. type S struct { Num int Str string } var s S // Create a mapper. m := &set.Mapper{} b, _ := m.Bind(&s) // err ignored for brevity // b.Set errors ignored for brevity _ = b.Set("Str", 3.14) // 3.14 coerced to "3.14" _ = b.Set("Num", "42") // "42" coerced to 42 fmt.Println(s.Num, s.Str) }
Output: 42 3.14
Example (ElevatedEmbed) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how to access fields in an elevated embedded struct. // Elevated types can be embedded without their type name becoming part of the field name. type Embed struct { V int } type S struct { Num int Str string Embed } var s S // Create a mapper. m := &set.Mapper{ Elevated: set.NewTypeList(Embed{}), // Elevate embedded fields of type Embed Join: ".", // Nested and embedded fields join with DOT } b, _ := m.Bind(&s) // err ignored for brevity // b.Set errors ignored for brevity _ = b.Set("Str", 3.14) // 3.14 coerced to "3.14" _ = b.Set("Num", "42") // "42" coerced to 42 _ = b.Set("V", 3.14) // 3.14 coerced to 3 fmt.Println(s.Num, s.Str, s.V) }
Output: 42 3.14 3
Example (Embedded) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how to access fields in an embedded struct. // Notice in this example the field is named Embed.V -- the struct type name // becomes part of the mapping name. type Embed struct { V int } type S struct { Num int Str string Embed } var s S // Create a mapper. m := &set.Mapper{ Join: ".", // Nested and embedded fields join with DOT } b, _ := m.Bind(&s) // err ignored for brevity // b.Set errors ignored for brevity _ = b.Set("Str", 3.14) // 3.14 coerced to "3.14" _ = b.Set("Num", "42") // "42" coerced to 42 _ = b.Set("Embed.V", 3.14) // 3.14 coerced to 3 fmt.Println(s.Num, s.Str, s.V) }
Output: 42 3.14 3
Example (Nesting) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how nested structs are accessible via their mapped names. type Nest struct { V int } type S struct { Num int Str string Foo Nest } var s S // Create a mapper. m := &set.Mapper{ Join: ".", // Nested and embedded fields join with DOT } b, _ := m.Bind(&s) // err ignored for brevity // b.Set errors ignored for brevity _ = b.Set("Str", 3.14) // 3.14 coerced to "3.14" _ = b.Set("Num", "42") // "42" coerced to 42 _ = b.Set("Foo.V", 3.14) // 3.14 coerced to 3 fmt.Println(s.Num, s.Str, s.Foo.V) }
Output: 42 3.14 3
Example (ReflectValue) ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience Mapper.Bind will accept a reflect.Value to perform the binding. type S struct { Num int Str string } var s, t, u S // Create a mapper. m := &set.Mapper{} b, _ := m.Bind(reflect.ValueOf(&s)) // err ignored for brevity // b.Set errors ignored for brevity _ = b.Set("Str", 3.14) _ = b.Set("Num", "42") b.Rebind(reflect.ValueOf(&t)) _ = b.Set("Str", -3.14) _ = b.Set("Num", "24") // Even though the BoundMapping was created with reflect.Value it will still accept *S directly. b.Rebind(&u) _ = b.Set("Str", "Works!") _ = b.Set("Num", uint(100)) fmt.Println("s", s.Num, s.Str) fmt.Println("t", t.Num, t.Str) fmt.Println("u", u.Num, u.Str) }
Output: s 42 3.14 t 24 -3.14 u 100 Works!
func (*Mapper) Map ¶
Map adds T to the Mapper's list of known and recognized types.
Map is goroutine safe. Multiple goroutines can call Map() simultaneously and the returned Mappings will behave identically. However if multiple goroutines simultaneously call Map(T) for the same type each goroutine may receiver a Mapping with its own underlying memory. If you require returned Mappings to use shared memory for the slice and map members then you should call Map(T) from a high level goroutine to build up the cache before calling it from other goroutines.
Mappings that are returned are shared resources and should not be altered in any way. If this is your use-case then create a copy of the Mapping with Mapping.Copy.
func (*Mapper) Prepare ¶ added in v0.5.0
func (me *Mapper) Prepare(I interface{}) (PreparedMapping, error)
Prepare creates a PreparedMapping that is initially bound to I.
PreparedMappings are provided for performance critical code that needs to read or mutate many instances of the same type repeatedly and for every instance the same fields will be accessed in the same order.
See documentation for PreparedMapping for more details.
I must be an addressable type.
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates a simple flat struct of primitives. type S struct { Num int Str string } var s S // Create a mapper. m := &set.Mapper{} p, _ := m.Prepare(&s) // err ignored for brevity // PreparedBindings require a call to Plan indicating field order. _ = p.Plan("Str", "Num") // err ignored for brevity _ = p.Set(3.14) // 3.14 coerced to "3.14" _ = p.Set("42") // "42" coerced to 42 fmt.Println(s.Num, s.Str) }
Output: 42 3.14
Example (ElevatedEmbed) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how to access fields in an elevated embedded struct. // Elevated types can be embedded without their type name becoming part of the field name. type Embed struct { V int } type S struct { Num int Str string Embed } var s S // Create a mapper. m := &set.Mapper{ Elevated: set.NewTypeList(Embed{}), // Elevate embedded fields of type Embed Join: ".", // Nested and embedded fields join with DOT } p, _ := m.Prepare(&s) // err ignored for brevity // PreparedBindings require a call to Plan indicating field order. _ = p.Plan("Str", "Num", "V") // err ignored for brevity _ = p.Set(3.14) // 3.14 coerced to "3.14" _ = p.Set("42") // "42" coerced to 42 _ = p.Set(3.14) // 3.14 coerced to 3 fmt.Println(s.Num, s.Str, s.V) }
Output: 42 3.14 3
Example (Embedded) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how to access fields in an embedded struct. // Notice in this example the field is named Embed.V -- the struct type name // becomes part of the mapping name. type Embed struct { V int } type S struct { Num int Str string Embed } var s S // Create a mapper. m := &set.Mapper{ Join: ".", // Nested and embedded fields join with DOT } p, _ := m.Prepare(&s) // err ignored for brevity // PreparedBindings require a call to Plan indicating field order. _ = p.Plan("Str", "Num", "Embed.V") // err ignored for brevity _ = p.Set(3.14) // 3.14 coerced to "3.14" _ = p.Set("42") // "42" coerced to 42 _ = p.Set(3.14) // 3.14 coerced to 3 fmt.Println(s.Num, s.Str, s.V) }
Output: 42 3.14 3
Example (Nesting) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how nested structs are accessible via their mapped names. type Nest struct { V int } type S struct { Num int Str string Foo Nest } var s S // Create a mapper. m := &set.Mapper{ Join: ".", // Nested and embedded fields join with DOT } p, _ := m.Prepare(&s) // err ignored for brevity // PreparedBindings require a call to Plan indicating field order. _ = p.Plan("Str", "Num", "Foo.V") // err ignored for brevity _ = p.Set(3.14) // 3.14 coerced to "3.14" _ = p.Set("42") // "42" coerced to 42 _ = p.Set(3.14) // 3.14 coerced to 3 fmt.Println(s.Num, s.Str, s.Foo.V) }
Output: 42 3.14 3
Example (ReflectValue) ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience Mapper.Prepare will accept a reflect.Value to perform the binding. type S struct { Num int Str string } var s, t, u S // Create a mapper. m := &set.Mapper{} p, _ := m.Prepare(reflect.ValueOf(&s)) // err ignored for brevity _ = p.Plan("Str", "Num") // err ignored for brevity _ = p.Set(3.14) _ = p.Set("42") p.Rebind(reflect.ValueOf(&t)) _ = p.Set(-3.14) _ = p.Set("24") // Even though the PreparedMapping was created with reflect.Value it will still accept *S directly. p.Rebind(&u) _ = p.Set("Works!") _ = p.Set(uint(100)) fmt.Println("s", s.Num, s.Str) fmt.Println("t", t.Num, t.Str) fmt.Println("u", u.Num, u.Str) }
Output: s 42 3.14 t 24 -3.14 u 100 Works!
type Mapping ¶
type Mapping struct { // Keys contains the names generated by the Mapper that created this Mapping. // // See Mapper documentation for information on controlling how and what names // are generated. Keys []string // Indeces contains each mapped field's index as an int slice ([]int) such as // would be appropriate for passing to reflect.Value.FieldByIndex([]int). // // However bear in mind reflect.Value.FieldByIndex([]int) essentially requires that // none of the intermediate fields described by the index are pointers or nil. // // The Value type in this package also has a FieldByIndex([]int) method. Value.FieldByIndex([]int) // will traverse and instantiate pointers and pointer chains where as // reflect.Value.FieldByIndex([]int) may panic. Indeces map[string][]int // StructFields can be used to look up the reflect.StructField by a generated // key name. // // This field is provided as a convenience so you can map a struct and inspect // field tags without having to use the reflect package yourself. StructFields map[string]reflect.StructField // ReflectPaths can be used to retrieve a path.ReflectPath by mapped name. // // A path.ReflectPath is slightly different and slightly more informative representation // for a path than a plain []int. ReflectPaths map[string]path.ReflectPath // HasPointers will be true if any of the pathways traverse a field that is a pointer. HasPointers bool }
A Mapping is the result of traversing a struct hierarchy to map pathways from the origin (i.e. top-level struct) to fields within the hierarchy.
Mappings are created by calling Mapper.Map(s) where s is the struct you wish to map.
func (Mapping) Get ¶
Get returns the indeces associated with key in the mapping. If no such key is found a nil slice is returned.
type PreparedMapping ¶ added in v0.5.0
type PreparedMapping struct {
// contains filtered or unexported fields
}
PreparedMapping is returned from Mapper's Prepare method.
A PreparedMapping must not be copied except via its Copy method.
PreparedMappings should be used in iterative code that needs to read or mutate many instances of the same struct. PreparedMappings do not allow for indeterminate field access between instances -- every struct instance must have the same fields accessed in the same order. This behavior is akin to prepared statements in a database engine; if you need adhoc or indeterminate access use a BoundMapping.
var a, b T p := myMapper.Prepare(&a) _ = p.Plan("Field", "Other") // check err in production p.Set(10) // a.Field = 10 p.Set("Hello") // a.Other = "Hello" p.Rebind(&b) // resets internal plan counter p.Set(27) // b.Field = 27 p.Set("World") // b.Other = "World"
All methods that return an error will return ErrPlanInvalid until Plan is called specifying an access plan. Methods that do not return an error can be called before a plan has been specified.
func (PreparedMapping) Assignables ¶ added in v0.5.0
func (p PreparedMapping) Assignables(rv []interface{}) ([]interface{}, error)
Assignables returns a slice of pointers to the fields in the currently bound struct in the order specified by the last call to Plan.
To alleviate pressure on the garbage collector the return slice can be pre-allocated and passed as the argument to Assignables. If non-nil it is assumed len(plan) == len(rv) and failure to provide an appropriately sized non-nil slice will cause a panic.
During traversal this method will allocate struct fields that are nil pointers.
An example use-case would be obtaining a slice of pointers for Rows.Scan() during database query results.
func (PreparedMapping) Copy ¶ added in v0.5.0
func (p PreparedMapping) Copy() PreparedMapping
Copy creates an exact copy of the PreparedMapping.
One use case for Copy is to create a set of PreparedMappings early in a program's init phase. During later execution when a PreparedMapping is needed for type T it can be obtained by calling Copy on the cached PreparedMapping for that type.
func (PreparedMapping) Err ¶ added in v0.5.0
func (p PreparedMapping) Err() error
Err returns an error that may have occurred during repeated calls to Set.
Err is reset on calls to Plan or Rebind.
func (*PreparedMapping) Field ¶ added in v0.5.0
func (p *PreparedMapping) Field() (Value, error)
Field returns the Value for the next field.
Each call to Field advances the internal access pointer in order to traverse the fields in the same order as the last call to Plan.
ErrPlanInvalid is returned if Plan has not been called. If this call to Field exceeds the length of the plan then ErrPlanExceeded is returned. Other errors from this package or standard library may also be returned.
func (PreparedMapping) Fields ¶ added in v0.5.0
func (p PreparedMapping) Fields(rv []interface{}) ([]interface{}, error)
Fields returns a slice of values to the fields in the currently bound struct in the order specified by the last call to Plan.
To alleviate pressure on the garbage collector the return slice can be pre-allocated and passed as the argument to Fields. If non-nil it is assumed len(plan) == len(rv) and failure to provide an appropriately sized non-nil slice will cause a panic.
During traversal this method will allocate struct fields that are nil pointers.
An example use-case would be obtaining a slice of query arguments by column name during database queries.
func (*PreparedMapping) Plan ¶ added in v0.5.0
func (p *PreparedMapping) Plan(fields ...string) error
Plan builds the field access plan and must be called before any other methods that return an error.
Each call to plan:
- Resets any internal error to nil
- Resets the internal plan-step counter.
If an unknown field is specified then ErrUnknownField is wrapped with the field name and the internal error is set to ErrPlanInvalid.
func (*PreparedMapping) Rebind ¶ added in v0.5.0
func (p *PreparedMapping) Rebind(v interface{})
Rebind will replace the currently bound value with the new variable v.
v must have the same type as the original value used to create the PreparedMapping otherwise a panic will occur.
As a convenience Rebind allows v to be an instance of reflect.Value. This prevents unnecessary calls to reflect.Value.Interface().
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // Once a PreparedMapping has been created you can swap out the value it mutates // by calling Rebind. This yields better performance in tight loops where // you intend to mutate many instances of the same type. m := &set.Mapper{} type S struct { Str string Num int } values := [][]interface{}{ {"First", 1}, {"Second", 2}, {"Third", 3}, } slice := make([]S, 3) p, _ := m.Prepare(&slice[0]) // We can prepare on the first value to get a PreparedMapping // We must call Plan with our intended field access order. _ = p.Plan("Str", "Num") for k := range slice { p.Rebind(&slice[k]) // The PreparedMapping now affects slice[k] for _, value := range values[k] { _ = p.Set(value) // error ignored for brevity } } fmt.Println(slice[0].Num, slice[0].Str) fmt.Println(slice[1].Num, slice[1].Str) fmt.Println(slice[2].Num, slice[2].Str) }
Output: 1 First 2 Second 3 Third
Example (Panic) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // PreparedMapping.Rebind panics if the new instance is not the same type. m := &set.Mapper{} type S struct { Str string } type Different struct { Str string } defer func() { if r := recover(); r != nil { fmt.Println(r) } }() var s S var d Different p, _ := m.Prepare(&s) p.Rebind(&d) }
Output: mismatching types during Rebind; have *set_test.S and got *set_test.Different
Example (ReflectValue) ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience PreparedMapping.Rebind can be called with an instance of reflect.Value // if-and-only-if the reflect.Value is holding a type compatible with the PreparedMapping. m := &set.Mapper{} type S struct { Str string Num int } var s S // Errors ignored for brevity. p, _ := m.Prepare(&s) _ = p.Plan("Num", "Str") _ = p.Set(42) _ = p.Set("Hello") rv := reflect.New(reflect.TypeOf(s)) // reflect.New creates a *S which is the type p.Rebind(rv) // originally bound. Therefore b.Rebind(rv) is valid. _ = p.Set(100) _ = p.Set("reflect.Value!") r := rv.Elem().Interface().(S) fmt.Println(s.Str, s.Num) fmt.Println(r.Str, r.Num) }
Output: Hello 42 reflect.Value! 100
func (*PreparedMapping) Set ¶ added in v0.5.0
func (p *PreparedMapping) Set(value interface{}) error
Set effectively sets V[field] = value.
Each call to Set advances the internal access pointer in order to traverse the fields in the same order as the last call to Plan.
ErrPlanInvalid is returned if Plan has not been called. If this call to Set exceeds the length of the plan then ErrPlanExceeded is returned. Other errors from this package or standard library may also be returned.
type SliceValue ¶ added in v0.5.0
type SliceValue struct { // Top is the original values passed to Slice. Top reflect.Value // V represents []T. V reflect.Value // ElemType and ElemEndType describe the type of elements in the slice. // // ElemType!=reflect.Ptr means ElemType equals ElemEndType; they describe the same type. // ElemType==reflect.Ptr means ElemEndType is the type at the end of the pointer chain. ElemType reflect.Type ElemEndType reflect.Type }
SliceValue wraps around a slice []T and facilitates element creation and appending.
func Slice ¶ added in v0.5.0
func Slice(v interface{}) (SliceValue, error)
Slice expects its argument to be a *[]T or a pointer chain ending in []T.
As a convenience Slice will also accept a reflect.Value as long as it represents a writable []T value.
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { var v set.Value var nums []int var numsppp ***[]int slice, err := set.Slice(&nums) if err != nil { fmt.Println(err) return } for k, elem := range []interface{}{"42", false, "true", "3.14"} { if k == 0 { v = set.V(slice.Elem()) } else { v.Rebind(slice.Elem()) } if err = v.To(elem); err != nil { fmt.Println(err) return } slice.Append(v.TopValue) } slice, err = set.Slice(&numsppp) if err != nil { fmt.Println(err) return } for k, elem := range []interface{}{"42", false, "true", "3.14"} { if k == 0 { v = set.V(slice.Elem()) } else { v.Rebind(slice.Elem()) } if err = v.To(elem); err != nil { fmt.Println(err) return } slice.Append(v.TopValue) } fmt.Println(nums) fmt.Println(***numsppp) }
Output: [42 0 1 3] [42 0 1 3]
Example (Errors) ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { var n int var numspp **[]int _, err := set.Slice(n) fmt.Println(err) _, err = set.Slice(&n) fmt.Println(err) _, err = set.Slice(reflect.ValueOf(&n)) fmt.Println(err) _, err = set.Slice(numspp) fmt.Println(err) }
Output: set: Slice: invalid slice: expected pointer to slice; got int set: Slice: invalid slice: expected pointer to slice; got *int set: Slice: invalid slice: expected pointer to slice; got *int set: Slice: read only value: can not set **[]int
func (*SliceValue) Append ¶ added in v0.5.0
func (s *SliceValue) Append(elem reflect.Value)
Append appends an element created by the Elem method.
If the slice is []T then Elem returns a *T. Append automatically dereferences the incoming value so that a T is appended as expected.
func (SliceValue) Elem ¶ added in v0.5.0
func (s SliceValue) Elem() reflect.Value
Elem returns a newly allocated slice element.
If the slice is []T then Elem returns a *T. This is so the created element can be passed directly into function wanting to populate T.
type TypeInfo ¶
type TypeInfo struct { // True if the Value is a scalar type: // bool, float32, float64, string // int, int8, int16, int32, int64 // uint, uint8, uint16, uint32, uint64 IsScalar bool // True if the Value is a map. IsMap bool // True if the Value is a slice. IsSlice bool // True if the Value is a struct. IsStruct bool // Kind is the reflect.Kind; when Stat() or StatType() were called with a pointer this will be the final // kind at the end of the pointer chain. Otherwise it will be the original kind. Kind reflect.Kind // Type is the reflect.Type; when Stat() or StatType() were called with a pointer this will be the final // type at the end of the pointer chain. Otherwise it will be the original type. Type reflect.Type // When IsMap or IsSlice are true then ElemType will be the reflect.Type for elements that can be directly // inserted into the map or slice; it is not the type at the end of the chain if the element type is a pointer. ElemType reflect.Type // When IsStruct is true then StructFields will contain the reflect.StructField values for the struct. StructFields []reflect.StructField }
TypeInfo summarizes information about a type T in a meaningful way for this package.
type TypeInfoCache ¶
type TypeInfoCache interface { // Stat accepts an arbitrary variable and returns the associated TypeInfo structure. Stat(T interface{}) TypeInfo // StatType is the same as Stat() except it expects a reflect.Type. StatType(T reflect.Type) TypeInfo }
TypeInfoCache builds a cache of TypeInfo types; when requesting TypeInfo for a type T that is a pointer the TypeInfo returned will describe the type T' at the end of the pointer chain.
If Stat() or StatType() are called with nil or an Interface(nil) then a zero TypeInfo is returned; essentially nothing useful can be done with the type needed to be described.
func NewTypeInfoCache ¶
func NewTypeInfoCache() TypeInfoCache
NewTypeInfoCache creates a new TypeInfoCache.
type TypeList ¶
TypeList is a list of reflect.Type.
func NewTypeList ¶
func NewTypeList(args ...interface{}) TypeList
NewTypeList creates a new TypeList type from a set of instantiated types.
type Value ¶
type Value struct { // TypeInfo describes the type T in WriteValue. When the value is created with a pointer P // this TypeInfo will describe the final type at the end of the pointer chain. // // To conserve memory and maintain speed this TypeInfo object may be shared with // other Value instances. Altering the members within TypeInfo will most likely // crash your program with a panic. // // Treat this value as read only. TypeInfo // CanWrite specifies if WriteValue.CanSet() would return true. CanWrite bool // TopValue is the original value passed to V() but wrapped in a reflect.Value. TopValue reflect.Value // WriteValue is a reflect.Value representing the modifiable value wrapped within this Value. // // If you call V( &t ) then CanWrite will be true and WriteValue will be a usable reflect.Value. // If you call V( t ) where t is not a pointer or does not point to allocated memory then // CanWrite will be false and any attempt to set values on WriteValue will probably panic. // // All methods on this type that alter the value Append(), Fill*(), To(), etc work on this // value. Generally you should avoid it but it's also present if you really know what you're doing. WriteValue reflect.Value // When IsMap or IsSlice are true then ElemTypeInfo is a TypeInfo struct describing the element type. ElemTypeInfo TypeInfo // contains filtered or unexported fields }
Value wraps around a Go variable and performs magic.
Once created a Value should only be copied via its Copy method.
func V ¶
func V(arg interface{}) Value
V returns a new Value.
The returned Value must not be copied except via its Copy method.
func (Value) Append ¶
Append appends the item(s) to the end of the Value assuming it is some type of slice and every item can be type-coerced into the slice's data type. Either all items are appended without an error or no items are appended and an error is returned describing the type of the item that could not be appended.
func (Value) Copy ¶ added in v0.3.0
Copy creates a clone of the Value and its internal members.
If you need to create many Value for a type T in order to Rebind(T) in a goroutine architecture then consider creating and caching a V(T) early in your application and then calling Copy() on that cached copy before using Rebind().
func (Value) FieldByIndex ¶
FieldByIndex returns the nested field corresponding to index.
Key differences between this method and the built-in method on reflect.Value.FieldByIndex() are the built-in causes panics while this one will return errors and this method will instantiate nil struct members as it traverses.
func (Value) FieldByIndexAsValue ¶
FieldByIndexAsValue calls into FieldByIndex and if there is no error the resulting reflect.Value is wrapped within a call to V() to return a Value.
func (Value) Fields ¶
Fields returns a slice of Field structs when Value is wrapped around a struct; for all other values nil is returned.
This function has some overhead because it creates a new Value for each struct field. If you only need the reflect.StructField information consider using the public StructFields member.
func (Value) FieldsByTag ¶
FieldsByTag is the same as Fields() except only Fields with the given struct-tag are returned and the TagValue member of Field will be set to the tag's value.
func (Value) Fill ¶
Fill iterates a struct's fields and calls To() on each one by passing the field name to the Getter. Fill stops and returns on the first error encountered.
func (Value) FillByTag ¶
FillByTag is the same as Fill() except the argument passed to Getter is the value of the struct-tag.
func (Value) NewElem ¶
NewElem instantiates and returns a Value that can be Panics.Append()'ed to this type; only valid if Value.ElemType describes a valid type.
func (*Value) Rebind ¶
func (v *Value) Rebind(arg interface{})
Rebind will swap the underlying original value used to create Value with the incoming value if:
Type(Original) == Type(Incoming).
If Rebind succeeds the following public members will have been replaced appropriately:
CanWrite TopValue WriteValue
Reach for this function to translate:
var slice []T // populate slice for _, item := range slice { v := set.V( item ) // Creates new Value every iteration -- can be expensive! // manipulate v in order to affect item }
to:
var slice []T v := set.V( T{} ) // Create a single Value for the type T // populate slice for _, item := range slice { v.Rebind( item ) // Reuse the existing Value -- will be faster! // manipulate v in order to affect item }
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // Once a Value has been created you can swap out the value it mutates // by calling Rebind. This yields better performance in tight loops where // you intend to mutate many instances of the same type. values := []string{"3.14", "false", "true", "5"} slice := make([]int, 4) v := set.V(&slice[0]) for k, str := range values { v.Rebind(&slice[k]) if err := v.To(str); err != nil { fmt.Println(err) return } } fmt.Println(slice) }
Output: [3 0 1 5]
Example (Panic) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // Value.Rebind panics if the new instance is not the same type. defer func() { if r := recover(); r != nil { fmt.Println(r) } }() var a int var b string v := set.V(&a) v.Rebind(&b) }
Output: mismatching types during Rebind; have *int and got *string
Example (ReflectValue) ¶
package main import ( "fmt" "reflect" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience Value.Rebind will accept reflect.Value // if-and-only-if the reflect.Value is holding a type compatible // with the Value. var a, b int v, rv := set.V(&a), reflect.ValueOf(&b) if err := v.To("42"); err != nil { fmt.Println(err) return } v.Rebind(rv) if err := v.To("24"); err != nil { fmt.Println(err) return } fmt.Printf("a=%v b=%v", a, b) }
Output: a=42 b=24
Example (Value) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // As a convenience Value.Rebind will accept another Value // as long as the internal types are compatible. var a, b int av, bv := set.V(&a), set.V(&b) if err := av.To("42"); err != nil { fmt.Println(err) return } av.Rebind(bv) if err := av.To("24"); err != nil { fmt.Println(err) return } fmt.Printf("a=%v b=%v", a, b) }
Output: a=42 b=24
func (Value) To ¶
To attempts to assign the argument into Value.
If Value is wrapped around an unwritable reflect.Value or the type is reflect.Invalid an error will be returned. You probably forgot to call set.V() with an address to your type.
If the assignment can not be made but the wrapped value is writable then the wrapped value will be set to an appropriate zero type to overwrite any existing data.
set.V(&T).To(S) T is scalar, S is scalar, same type -> direct assignment If S is a pointer then dereference until final S value and continue... T is scalar, S is scalar, different types -> assignment with attempted type coercion T is scalar, S is slice []S -> T is assigned S[ len( S ) - 1 ]; i.e. last element in S if length greater than 0. T is slice []T, S is scalar -> T is set to []T{ S }; i.e. a slice of T with S as the only element. T is slice []T, S is slice []S -> T is set to []T{ S... }; i.e. a new slice with elements from S copied. -> Note: T != S; they are now different slices; changes to T do not affect S and vice versa. -> Note: If the elements themselves are pointers then, for example, T[0] and S[0] point at the same memory and will see changes to whatever is pointed at.
Example ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // Value wraps around scalars or slices and the To method performs type coercion. // // When calling set.V(a) `a` must represent an addressable or writable value similarly // to invoking deserializers like json.Unmarshal([]byte, a). // Fails because not addressable var b bool err := set.V(b).To("true") // Should have been &b -- address of b fmt.Println(err) // Succeed because we pass addresses of destinations. var s string var n int var u8 uint8 err = set.V(&s).To(3.14) fmt.Println("s", s, err) err = set.V(&n).To("42") fmt.Println("n", n, err) err = set.V(&u8).To("27") fmt.Println("u8", u8, err) // When passing the address of a nil ptr it will be created. var nptr *int // nptr == nil err = set.V(&nptr).To("100") // nptr != nil now fmt.Println("nptr", *nptr, err) // If a pointer already points at something you can pass it directly. sptr := &s // sptr != nil and points at s err = set.V(sptr).To("Something") fmt.Println("sptr", *sptr, err, "s", s) // new(T) works the same as sptr above. f32 := new(float32) err = set.V(f32).To("3") fmt.Println("f32", *f32, err) }
Output: set: Value.To: read only value: bool is not writable: hint=[call to set.V(bool) should have been set.V(*bool)] s 3.14 <nil> n 42 <nil> u8 27 <nil> nptr 100 <nil> sptr Something <nil> s Something f32 3 <nil>
Example (ScalarToSlice) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how scalars are coerced to slices. // Given // ↪ var t []T // A target slice // ↪ var s S // A scalar value // Then // ↪ _ = set.V(&t).To(s) // Yields // ↪ t == []T{ s } // A slice with a single element var n []int var s []string err := set.V(&n).To("9") fmt.Println("n", n, err) err = set.V(&s).To(1234) fmt.Println("s", s, err) // When the incoming value can not be type coerced the slice will be an empty slice. err = set.V(&n).To("Hello") fmt.Println("n", n == nil, err == nil) }
Output: n [9] <nil> s [1234] <nil> n true false
Example (SliceToScalar) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how slices are coerced to scalars. // Given // ↪ var t T // A target scalar // ↪ var s []S // A slice value // Then // ↪ _ = set.V(&t).To(s) // Yields // ↪ t == s[ len(s) -1 ] // t is set to last value in slice var n int var s string err := set.V(&n).To([]interface{}{9, "3.14", false, "next value wins", "9999"}) fmt.Println("n", n, err) err = set.V(&s).To([]interface{}{9, "3.14", false, "next value wins", "I win!"}) fmt.Println("s", s, err) // When the incoming slice can not be type coerced the scalar will be zero value. err = set.V(&n).To([]interface{}{9, "3.14", false, "next value wins", "9999", "Fred"}) fmt.Println("n", n == 0, err == nil) // When the incoming slice is nil or empty the scalar will be zero value. err = set.V(&s).To([]int(nil)) // Sets s to "" -- empty string! fmt.Println("s", s, err) }
Output: n 9999 <nil> s I win! <nil> n true false s <nil>
Example (SliceToSlice) ¶
package main import ( "fmt" "github.com/nofeaturesonlybugs/set" ) func main() { // This example demonstrates how slices are coerced to slices. // Given // ↪ var t []T // A target slice // ↪ var s []S // A slice value // Then // ↪ _ = set.V(&t).To(s) // Yields // ↪ t == s // t is a slice equal in length to s with elements type coerced to T // However // ↪ t is a copy of s var n []int var s []string values := []interface{}{9, "3.14", false, "9999"} err := set.V(&n).To(values) fmt.Println("n", n, err) err = set.V(&s).To(values) fmt.Println("s", s, err) // If any element can not be coerced the target (or dest) will be zero value slice. values = append(values, "Hello") // "Hello" can not be coereced to int err = set.V(&n).To(values) fmt.Println("n", n == nil, err == nil) // When dealing with slices the target (or dest) is always a copy. m := []int{2, 4, 6, 8} err = set.V(&n).To(m) // Even though m and n are same type n will be a copy fmt.Println("n", n, err) m[1] = -4 // Change element in m fmt.Println("m", m, "n", n) }
Output: n [9 3 0 9999] <nil> s [9 3.14 false 9999] <nil> n true false n [2 4 6 8] <nil> m [2 -4 6 8] n [2 4 6 8]
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package coerce provides loose type coercion and assignment into native Go types.
|
Package coerce provides loose type coercion and assignment into native Go types. |
Package path provides more granular information about paths or pathways from a top-level root struct through its descendents to leaf fields.
|
Package path provides more granular information about paths or pathways from a top-level root struct through its descendents to leaf fields. |