set

package module
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2022 License: MIT Imports: 9 Imported by: 6

README

Documentation Go Report Card Build Status codecov

Package set is a performant reflect wrapper supporting loose type conversion, struct mapping and population, and slice building.

The godoc documentation has detailed information and many examples but following is the high-level view.

Type Coercion

Type coercion allows assignment from loosey-goosey sources -- for example incoming string data -- to strongly typed Go types.

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.

Struct Population with Getter

set.Value has two methods, Fill and FillByTag, that use the set.Getter interface as the provider for data to populate a struct and its hierarchy.

For convenience:

  • set.GetterFunc allows plain functions to be used as a set.Getter similar to http.HandlerFunc.
  • set.MapGetter allows either map[string]T or map[interface{}]T to be used as a set.Getter.

Struct Mapping

set.Mapper is a powerful and highly configurable struct mapping facility.

A mapper will traverse a struct hiearchy and create a 1:1 mapping of friendly names to traversal information. The friendly names can then be used to target associated fields within a struct value and its hierarchy.

Example usages of a mapper are to map CSV column headings to struct fields, database column names to struct fields, or creating a generic struct copier to marshal structs across different domains or boundaries in your application architecture.

set.Mapper contains several configuration fields that can be used to fully customize the generated friendly names:

  • Choose how nested names are combined: VendorName, Vendor_Name, Vendor.Name, vendor_name, etc.
  • Specify multiple tags in order of preference: db tag values can have higher precedence than json tags
  • Elevate types into a higher namespace: see the Mapper example(s)
  • Specify types that are ignored and don't get mapped.
  • Specify types that are treated as scalars: useful for sql.Null* types or similar

BoundMapping and PreparedMapping

Once a set.Mapper (described above) is created it can return BoundMapping or PreparedMapping types that are bound to Go structs. In turn BoundMapping and PreparedMapping provide performant access to the bound data via the friendly names generated by the mapper.

A BoundMapping provides an adhoc access to struct fields; each method takes the mapped name of the field to access. An example use case for BoundMapping is populating data when some of the data may be missing and you may not set data for every possible mapped field.

A PreparedMapping is similar to a prepared SQL statement and the access plan must be set with a call to its Plan method. An example use case for PreparedMapping is populating CSV data or database rows where every row is guaranteed to access the same fields in the same order.

Performance Notes

Package reflect is always slower than code not using reflect. A considerable effort has been spent designing and implementing this package to reduce reflect overhead.

  • reflect data is generally only gathered once (via reflect.TypeOf, reflect.ValueOf) when first encountering a type. This data is cached and retrieved from cache on further encounters with repeated types.
  • Value assigning is generally attempted first with type switches and then falls back to reflect. This strategy is heavily used during type coercion.
  • Appropriate types in this package have a Rebind method. Rebind will swap a "bound" Go type with a new incoming instance without making additional expensive calls into reflect. The outgoing and incoming types must be compatible but this is the expected usage in tight loops building slices of data.

Additionally this package attempts to be low allocation so as not to overwhelm the garbage collector.

  • Some of the methods on BoundMapping and PreparedMapping allow a dest slice to be pre-allocated.
  • BoundMapping, PreparedMapping, and Value are created and returned as structs instead of pointers.

API Consistency and Breaking Changes

I am making a very concerted effort to break the API as little as possible while adding features or fixing bugs. However this software is currently in a pre-1.0.0 version and breaking changes are allowed under standard semver. As the API approaches a stable 1.0.0 release I will list any such breaking changes here and they will always be signaled by a bump in minor version.

  • 0.4.0 ⭢ 0.5.0

    • README-0.4.0-to-0.5.0.md outlines many of the package changes, reasoning, and benchmarks

    • Remove erroneous documentation for Value.To method.
      The documentation indicated that when Dst and Src are both pointers with same level of indirection that direct assignment was performed. This is not true. The Value type uses the values at the end of pointers and pointer chains and therefore does not perform direct assignment of pointer values.

  • 0.3.0 ⭢ 0.4.0
    set.Mapper has new field TaggedFieldsOnly. TaggedFieldsOnly=false means no change in behavior. TaggedFieldsOnly=true means set.Mapper only maps exported fields with struct tags.

  • 0.2.3 ⭢ 0.3.0
    set.BoundMapping.Assignables has a second argument allowing you to pre-allocate the slice that is returned; you can also set it to nil to keep current behavior.

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

Examples

Constants

This section is empty.

Variables

View Source
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")
)
View Source
var DefaultMapper = &Mapper{
	Join: "_",
}

DefaultMapper joins names by "_" but performs no other modifications.

View Source
var Panics = CanPanic{}

Panics is a global instance of CanPanic; it is provided for convenience.

View Source
var TypeCache = NewTypeInfoCache()

TypeCache is a global TypeInfoCache

Functions

func Writable

func Writable(v reflect.Value) (V reflect.Value, CanWrite bool)

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`.

func (CanPanic) Append

func (p CanPanic) Append(dest Value, values ...Value)

Append appends any number of Value types to the dest Value. The safest way to use this method is when your code uses a variant of:

var records []*Record
v := set.V( &records )
for k := 0; k < 10; k++ {
	elem := v.Elem.New()
	set.Panics.Append( v, elem )
}

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

func (me *Mapper) Map(T interface{}) Mapping

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

func (me Mapping) Copy() Mapping

Copy creates a copy of the Mapping.

func (Mapping) Get

func (me Mapping) Get(key string) []int

Get returns the indeces associated with key in the mapping. If no such key is found a nil slice is returned.

func (Mapping) Lookup

func (me Mapping) Lookup(key string) (indeces []int, ok bool)

Lookup returns the value associated with key in the mapping. If no such key is found a nil slice is returned and ok is false; otherwise ok is true.

func (Mapping) String

func (me Mapping) String() string

String returns the Mapping as a string value.

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

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:

  1. Resets any internal error to nil
  2. 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

type TypeList map[reflect.Type]struct{}

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.

func (TypeList) Has

func (list TypeList) Has(T reflect.Type) bool

Has returns true if the specified type is in the list.

func (TypeList) Merge

func (list TypeList) Merge(from TypeList)

Merge adds entries in `from` to this list.

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

func (v Value) Append(items ...interface{}) error

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

func (v Value) Copy() Value

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

func (v Value) FieldByIndex(index []int) (reflect.Value, error)

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

func (v Value) FieldByIndexAsValue(index []int) (Value, error)

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

func (v Value) Fields() []Field

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

func (v Value) FieldsByTag(key string) []Field

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

func (v Value) Fill(getter Getter) error

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

func (v Value) FillByTag(key string, getter Getter) error

FillByTag is the same as Fill() except the argument passed to Getter is the value of the struct-tag.

func (Value) NewElem

func (v Value) NewElem() (Value, error)

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

func (v Value) To(arg interface{}) error

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]

func (Value) Zero

func (v Value) Zero() error

Zero sets the Value to the Zero value of the appropriate type.

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.

Jump to

Keyboard shortcuts

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