set

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 31, 2021 License: MIT Imports: 8 Imported by: 6

README

Documentation Go Report Card Build Status codecov

Package set is a small wrapper around the official reflect package that facilitates loose type conversion, assignment into native Go types, and utilities to populate deeply nested Go structs.

Read the godoc for more detailed explanations and examples but here are some enticing snippets.

Scalars and Type-Coercion

{
    // type coercion
    b, i := true, 42
    set.V(&b).To("False")    // Sets b to false
    set.V(&i).To("3.14")     // Sets i to 3
}

{
    // type coercion
    a := int(0)
    b := uint(42)
    set.V(&a).To(b)             // This coerces b into a if possible.
    set.V(&a).To("-57")         // Also works.
    set.V(&a).To("Hello")       // Returns an error.    
}

Pointer Allocation Plus Type-Coercion

{
    // pointer allocation and type coercion
    var bppp ***bool
    set.V(&bppp).To("True")
    fmt.Println(***bppp) // Prints true
}

Scalars-to-Slices and Slices-to-Scalars

{
    // assign scalars to slices
    var b []bool
    set.V(&b).To("True") // b is []bool{ true }

    // or slices to scalars (last element wins)
    var b bool
    set.V(&b).To([]bool{ false, false, true } ) // b is true, coercion not needed.
    set.V(&b).To([]interface{}{ float32(1), uint(0) }) // b is false, coercion needed.
}

Slices to Slices Including Type-Coercion

{
    // slices to slices with or without type coercion; new slice is always created!
    var t []bool
    var s []interface{}
    s = []interface{}{ "true", 0, float64(1) }
    set.V(&t).To(s) // t is []bool{ true, false, true }
}

{
    var t []bool
    var s []bool
    s = []bool{ true, false, true }
    set.V(&t).To(s) // t is []bool{ true, false, true } and t != s
}

Filling Structs by Field Name

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 
    Street2 string 
    City    string 
    State   string 
    Zip     string 
}
type Person struct {
    Name    string  
    Age     uint    
    Address Address 
}
var t Person
set.V(&t).Fill(myGetter)

Filling Structs by Struct Tag

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)

Allocating Struct Pointers and Pointer Fields

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"`
}
m := map[string]interface{}{
    "name": "Bob",
    "age":  42,
    "address": map[interface{}]string{
        "street1": "97531 Some Street",
        "street2": "",
        "city":    "Big City",
        "state":   "ST",
        "zip":     "12345",
    },
}
getter := set.MapGetter(m)

var t *Person
set.V(&t).FillByTag("key", getter)
fmt.Println(t.Name)                 // Bob
fmt.Println(t.Address.Street1)      // 97531 Some Street

Field Pointers Always Allocated

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"`
}
m := map[string]interface{}{
    "name": "Bob",
    "age":  42,
    // address is missing!
}
getter := set.MapGetter(m)
var t *Person
set.V(&t).FillByTag("key", getter)
fmt.Printf("%p\n", t.Address) // Prints a memory address; the field was allocated.

Pointer-to-Slices-of-Struct-Pointers -- OH MY!

Also noteworthy in this example is the same fuzzy logic for assigning scalar-to-slice or slice-to-scalar also works for struct-to-[]struct and []struct-to-struct.

func TestValue_fillNestedStructPointerToSlicesAsPointers(t *testing.T) {
    chk := assert.New(t)
    //
    var err error
    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"`
    }
    type Company struct {
        Name         string     `key:"name"`
        // Within the Getter "employees" is a []map so it is intuitive that Company.Employees becomes
        // a slice with as many entries as Getter( "employees" ).
        Employees    *[]*Person `key:"employees"`
        // The "employees" key is reused here but now it is going into a single struct pointer; the
        // last set of data in Getter( "employees" ) wins, aka Sally.
        LastEmployee *Person    `key:"employees"`
        // Note the "slice" key in the getter is not a []map itself; but a single *Person is
        // created and inserted into Company.Slice.
        Slice        *[]*Person `key:"slice"`
    }
    //
    // Also noteworthy is that Company.Employees and Company.Slice are pointers to slices and
    // they are instantiated automatically by this package.  The syntax to use them becomes unwieldly
    // so I don't know why you'd want to do this but hey -- it works and that's neat.
    //
    m := map[string]interface{}{
        "name": "Some Company",
        "slice": map[string]interface{}{
            "name": "Slice",
            "age":  2,
            "address": map[interface{}]string{
                "street1": "Slice Street",
                "street2": "",
                "city":    "Slice City",
                "state":   "SL",
                "zip":     "99999",
            },
        },
        "employees": []map[string]interface{}{
            {
                "name": "Bob",
                "age":  42,
                "address": map[interface{}]string{
                    "street1": "97531 Some Street",
                    "street2": "",
                    "city":    "Big City",
                    "state":   "ST",
                    "zip":     "12345",
                },
            },
            {
                "name": "Sally",
                "age":  48,
                "address": map[interface{}]string{
                    "street1": "555 Small Lane",
                    "street2": "",
                    "city":    "Other City",
                    "state":   "OO",
                    "zip":     "54321",
                },
            },
        },
    }
    getter := set.MapGetter(m)

    var t *Company
    err = set.V(&t).FillByTag("key", getter)
    chk.NoError(err)
    chk.Equal("Some Company", t.Name)
    //
    chk.Equal(2, len(*t.Employees))
    //
    chk.Equal("Bob", (*t.Employees)[0].Name)
    chk.Equal(uint(42), (*t.Employees)[0].Age)
    chk.Equal("97531 Some Street", (*t.Employees)[0].Address.Street1)
    chk.Equal("", (*t.Employees)[0].Address.Street2)
    chk.Equal("Big City", (*t.Employees)[0].Address.City)
    chk.Equal("ST", (*t.Employees)[0].Address.State)
    chk.Equal("12345", (*t.Employees)[0].Address.Zip)
    //
    chk.Equal("Sally", (*t.Employees)[1].Name)
    chk.Equal(uint(48), (*t.Employees)[1].Age)
    chk.Equal("555 Small Lane", (*t.Employees)[1].Address.Street1)
    chk.Equal("", (*t.Employees)[1].Address.Street2)
    chk.Equal("Other City", (*t.Employees)[1].Address.City)
    chk.Equal("OO", (*t.Employees)[1].Address.State)
    chk.Equal("54321", (*t.Employees)[1].Address.Zip)
    //
    chk.Equal("Sally", t.LastEmployee.Name)
    chk.Equal(uint(48), t.LastEmployee.Age)
    chk.Equal("555 Small Lane", t.LastEmployee.Address.Street1)
    chk.Equal("", t.LastEmployee.Address.Street2)
    chk.Equal("Other City", t.LastEmployee.Address.City)
    chk.Equal("OO", t.LastEmployee.Address.State)
    chk.Equal("54321", t.LastEmployee.Address.Zip)
    //
    chk.Equal(1, len(*t.Slice))
    chk.Equal("Slice", (*t.Slice)[0].Name)
    chk.Equal("Slice Street", (*t.Slice)[0].Address.Street1)
    chk.Equal("", (*t.Slice)[0].Address.Street2)
    chk.Equal("Slice City", (*t.Slice)[0].Address.City)
    chk.Equal("SL", (*t.Slice)[0].Address.State)
    chk.Equal("99999", (*t.Slice)[0].Address.Zip)
}

Value.FieldByIndex() and Value.FieldByIndexAsValue()

Similarly to the standard reflect package Value also supports accessing struct types by field indeces. Unlike the standard reflect package this package will instantiate and create nil members as it traverses the struct.

type A struct {
	B struct {
		C *struct {
			CString string
		}
		BString string
	}
}
var a A
var v, f reflect.Value
v = reflect.Indirect(reflect.ValueOf(&a))
f = v.FieldByIndex([]int{0, 1})     // Accesses a.B.BString -- Ok because B is not nil.
f = v.FieldByIndex([]int{0, 0, 0})  // Accesses a.B.C.CString -- panic because C is nil.

// However with set.Value.FieldByIndex()
f := set.V(&a).FieldByIndex([]int{0, 0, 0}) // Creates C and returns reflect.Value for CString.

// FieldByIndex() returns the field as a reflect.Value but you can also return it as a *set.Value:
asValue := set.V(&a).FieldByIndexAsValue([]int{0, 0, 0})

Mapper and Mapping for Nested Struct Access

Mapper traverses a nested struct hierarchy to generate a Mapping. From a Mapping you can use string keys to access the struct members by struct field index.

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

Depending on the public Mapper members (aka options) the following mappings can be created:

// Type CommonDb doesn't affect names; join nestings with "_"
mapper := &set.Mapper{
    Elevated: set.NewTypeList(CommonDb{}),
    Join:     "_",
}
generates = `
[0 0] Pk
[0 1] CreatedTime
[0 2] UpdatedTime
[1] Name
[2] Age
`

// lowercase with dot separators
// This time CommonDb is not elevated and becomes part of the generated names.
mapper := &set.Mapper{
    Join:      ".",
    Transform: strings.ToLower,
}
generates = `
[0 0] commondb.pk
[0 1] commondb.createdtime
[0 2] commondb.updatedtime
[1] name
[2] age
`

// specify tags
// CommonDb not elevated again, tags are used to generate names.
mapper := &set.Mapper{
    Join: "_",
    Tags: []string{"t"},
}
generates = `
[0 0] common_pk
[0 1] common_created_time
[0 2] common_updated_time
[1] name
[2] age
`

BoundMappings combine Mapper.Map() and Value.FieldByIndex()

Mapper.Bind() will return a BoundMapping that you can use to set nested struct members on a variable via strings. You can Rebind() a BoundMapping to switch the underlying value. Consider the following struct hierarchy:

type Common struct {
    Id int
}
type Timestamps struct {
    CreatedTime  string
    ModifiedTime string
}
type Person struct {
    Common
    Timestamps // Not used but present anyways
    First      string
    Last       string
}
type Vendor struct {
    Common
    Timestamps  // Not used but present anyways
    Name        string
    Description string
    Contact     Person
}
type T struct {
    Common
    Timestamps
    //
    Price    int
    Quantity int
    Total    int
    //
    Customer Person
    Vendor   Vendor
}

Create a set.Mapper with your desired options and create a binding:

mapper := &set.Mapper{
    Elevated: set.NewTypeList(Common{}, Timestamps{}),
    Join:     "_",
}
//
dest := new(T)
// bound will become our BoundMapping and the accessor into instances of T
bound := mapper.Bind(&dest) 

Now you can iterate a dataset and populate instances of T:

for k := 0; k < b.N; k++ {
    // `rows` is a big pile of randomized data.  We are assigning data from `row` into our new T
    // but using strings to access into the nested structure of T.
    row := rows[k%size]
    // Create a new T
    dest = new(T)
    // Change our binding to the new T
    bound.Rebind(&dest)
    //
    bound.Set("Id", row.Id)
    bound.Set("CreatedTime", row.CreatedTime)
    bound.Set("ModifiedTime", row.ModifiedTime)
    bound.Set("Price", row.Price)
    bound.Set("Quantity", row.Quantity)
    bound.Set("Total", row.Total)
    //
    bound.Set("Customer_Id", row.CustomerId)
    bound.Set("Customer_First", row.CustomerFirst)
    bound.Set("Customer_Last", row.CustomerLast)
    //
    bound.Set("Vendor_Id", row.VendorId)
    bound.Set("Vendor_Name", row.VendorName)
    bound.Set("Vendor_Description", row.VendorDescription)
    bound.Set("Vendor_Contact_Id", row.VendorContactId)
    bound.Set("Vendor_Contact_First", row.VendorContactFirst)
    bound.Set("Vendor_Contact_Last", row.VendorContactLast)
    //
    if err = bound.Err(); err != nil {
        b.Fatalf("Unable to set: %v", err.Error())
    }
}

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.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 small wrapper around the official reflect package that facilitates loose type conversion, assignment into native Go types, and utilities to populate deeply nested Go structs.

Data Types

In this package documentation float, int, and uint include the bit-specific types. For example float includes float32 and float64, int includes int8, int16, int32, and int64, etc.

Scalar Types

This package considers the following types to be scalars:

bool, float, int, uint, & string
pointer to any of the above; e.g: *bool, *float, *int, etc
nested pointer to any of the above; e.g: ***bool, *****string, etc

Package Name Collision

I called this package `set` because I like typing short identifiers and I don't really use packages that implement logical set operations such as union or intersection.

I also like the semantics of typing: set.V().To() // i.e. set value to

If you find yourself dealing with name collision here are some alternate imports that are still short and keep the semantics (with varying success):

import (
	assign "github.com/nofeaturesonlybugs/set"
	accept "github.com/nofeaturesonlybugs/set"
	coerce "github.com/nofeaturesonlybugs/set"
	from "github.com/nofeaturesonlybugs/set"
	make "github.com/nofeaturesonlybugs/set"
	pin "github.com/nofeaturesonlybugs/set"
	will "github.com/nofeaturesonlybugs/set"
)

Basic Type Coercion

A simple example with type coercion:

b, i := true, 42
set.V(&b).To("False")		// Sets b to false
set.V(&i).To("3.14")		// Sets i to 3

In general:

var t T					// T is a target data type, t is a variable of that type.
var s S					// S is a source data, s is a variable of that type.
set.V(&t).To(s)				// Sets s into t with a "best effort" approach.

If t is not a pointer or is a pointer that is set to nil then pass the address of t:

var t bool		// Pass the address when t is not a pointer.
set.V(&t)

var t *int 		// Pass the address when t is a pointer but set to nil.
set.V(&t)

var i int
t := &i			// Do not pass the address when t is a pointer and not set to nil.
set.V(t)		// Note that values assigned go into i!

Scalar to Scalar of Same Type

When T and S are both scalars of the same type then assignment with this package is the same as direct assignment:

var a int			// Call this T
var b int			// Call this S
set.V(&a).To(b)			// Effectively the same as: a = b

// pointers to structs
x := &T{}				// Call this T
y := &T{"data", "more data"}		// Call this S
set.V(&x).To(y)				// Effectively the same as: x = y

This package grants no benefit when used as such.

Scalar to Scalar of Different Type

When T and S are both scalars but with different types then assignments can be made with type coercion:

a := int(0)			// Call this T
b := uint(42)			// Call this S
set.V(&a).To(b)			// This coerces b into a if possible.
set.V(&a).To("-57")		// Also works.
set.V(&a).To("Hello")		// Returns an error.

Pointers Are Tricky But Work Well

If a pointer already contains a memory address then you do not need to pass the pointer's address to set:

var b bool
bp := &b 		// bp already contains an address
set.V(bp).To("1")	// b is set to true

If a pointer does not contain an address then passing the pointer's address allows set to create the data type and assign it to the pointer:

var bp *bool
set.V(&bp).To("True")	// bp is now a pointer to bool and *bp is true.

This works even if the pointer is multiple levels of indirection:

var bppp ***bool
set.V(&bppp).To("True")
fmt.Println(***bppp) // Prints true

If S is a pointer it will be dereferenced until the final value; if the final value is a scalar then it will be coerced into T:

var ippp ***int
s := "42"
sp := &s
spp := &sp
set.V(&ippp).To(spp)
fmt.Println(***ippp) // Prints 42

Scalars to Slices

When T is a slice and S is a scalar then T is assigned a slice with S as its single element:

var b []bool
set.V(&b).To("True") // b is []bool{ true }

Slices to Scalars

When T is a scalar and S is a slice then the last element of S is assigned to T:

var b bool
set.V(&b).To([]bool{ false, false, true } ) // b is true, coercion not needed.
set.V(&b).To([]interface{}{ float32(1), uint(0) }) // b is false, coercion needed.

If S is a nil or empty slice then T is set to an appropriate zero value.

Slices to Slices

When T and S are both slices set always creates a new slice []T and copies elements from []S into []T.

var t []bool
var s []interface{}
s = []interface{}{ "true", 0, float64(1) }
set.V(&t).To(s) // t is []bool{ true, false, true }

var t []bool
var s []bool
s = []bool{ true, false, true }
set.V(&t).To(s) // t is []bool{ true, false, true } and t != s

If a single element within []S can not be coerced into an element of T then []T will be empty:

var t []int
var s []string
s = []string{ "42", "24", "Hello!" }
set.V(&t).To(s) // t is []int{} because "Hello" can not coerce.

Populating Structs with Value.Fill() and a Getter

Structs can be populated by using Value.Fill() and a Getter; note the function is type casted to a set.GetterFunc.

// An example getter.
myGetter := set.GetterFunc(func( name string ) interface{} {
	switch name {
	case "Name":
		return "Bob"
	case "Age":
		return "42"
	default:
		return nil
	}
})

Populating a struct by field; i.e. the struct field names are the names passed to the Getter:

type T struct {
	Name string
	Age uint
}
var t T
set.V(&t).Fill(myGetter)

Populating a struct by struct tag; i.e. if the struct tag exists on the field then the tag's value is passed to the Getter:

type T struct {
	SomeField string `key:"Name"`
	OtherField uint `key:"Age"`
}
var t T
set.V(&t).FillByTag("key", myGetter)

Populating Nested Structs with Value.Fill() and a Getter

To populate nested structs a Getter needs to return a Getter for the given name:

myGetter := set.GetterFunc(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
	}
})

Using the Getter above:

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)

Maps as Getters

A more practical source of data for a Getter might be a map. To use a map as a Getter the map key has to be assignable to string; e.g: string or interface{}. If the map contains nested maps that also meet the criteria for becoming a Getter then those maps can be used to populate nested structs.

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)

Populating Structs with Mapper, Mapping, and BoundMap

If you need to populate or traverse structs using strings as lookups consider using a Mapper. A Mapper traverses a type T and generates a Mapping which contains members to facilitate accessing struct field indeces with strings.

When you index into Mapping.Indeces you will receive a slice of ints representing the indeces into the nested structure to the desired field.

For convenience a Mapper can create a BoundMapping which binds the Mapping to an instance of T. The BoundMapping can then be used to update the data within the instance. See the BoundMapping examples.

Rebinding

Both Value and BoundMapping support Rebind(). There is an amount of overhead instantiating either Value or BoundMapping and most of that overhead occurs with type introspection via reflect. In tight-loop situations where either *Value or BoundMapping are used to alter many values of the same type this overhead can be costly and is unnecessary since the first *Value or BoundMapping already contains the type information.

For optimal performance in tight loop situations create a single instance of *Value or BoundMapping and then call Rebind() with a new instance of data you wish to manipulate.

For example change:

for _, value := range someSliceOfTypeT {
	v := set.V( &value )			// <-- Expensive -- type information gathered for each instance created.
	// do something with v
}

to:

v := set.Value( &T{} ) // Create v once!
for _, value := range someSliceOfTypeT {
	v.Rebind( &value ) // <-- Reuses existing type information -- more performant!
	// do something with v
}

Examples Subdirectory

The examples subdirectory contains multiple examples for this package; separating them keeps this package documentation a little cleaner.

var myVar bool
value := set.V(&myVar)

To see what you can do with `value` in the above code find the `Bool` type in the examples package.

// Assuming SomeType is a struct.
var myVar SomeType
value := set.V(&myVar)

To see what you can do with `value` in the above code find the `Struct` type in the examples package.

Index

Examples

Constants

This section is empty.

Variables

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)
	writable, ok = set.Writable(value)
	fmt.Printf("ok= %v\n", ok)

	value = reflect.ValueOf(sp)
	writable, 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 interface {
	// Assignables returns a slice of interfaces by field names where each element is a pointer
	// to the field in the bound variable.
	//
	// The second argument, if non-nil, will be the first return value.  In other words pre-allocating
	// rv will cut down on memory allocations.  It is assumed that if rv is non-nil that len(fields) == len(rv)
	// and the lengths are not checked, meaning your program could panic.
	//
	// During traversal this method will instantiate struct fields that are themselves structs.
	//
	// An example use-case would be obtaining a slice of pointers for Rows.Scan() during database
	// query results.
	Assignables(fields []string, rv []interface{}) ([]interface{}, error)
	// Copy creates an exact copy of the BoundMapping.  Since a BoundMapping is implicitly tied to a single
	// value sometimes it may be useful to create and cache a BoundMapping early in a program's initialization and
	// then call Copy() to work with multiple instances of bound values simultaneously.
	Copy() BoundMapping
	// Err returns an error that may have occurred during repeated calls to Set(); it is reset on
	// calls to Rebind()
	Err() error
	// Field returns the *Value for field.
	Field(field string) (*Value, error)
	// Fields returns a slice of interfaces by field names where each element is the field value.
	//
	// The second argument, if non-nil, will be the first return value.  In other words pre-allocating
	// rv will cut down on memory allocations.  It is assumed that if rv is non-nil that len(fields) == len(rv)
	// and the lengths are not checked, meaning your program could panic.
	//
	// During traversal this method will instantiate struct fields that are themselves structs.
	//
	// An example use-case would be obtaining a slice of query arguments by column name during
	// database queries.
	Fields(fields []string, rv []interface{}) ([]interface{}, error)
	// Rebind will replace the currently bound value with the new variable I.  If the underlying types do
	// not match a panic will occur.
	Rebind(I interface{})
	// Set effectively sets V[field] = value.
	Set(field string, value interface{}) error
}

BoundMapping binds a Mapping to a specific variable instance V.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/nofeaturesonlybugs/set"
)

func main() {
	type Person struct {
		First string
		Last  string
	}
	values := []map[string]string{
		{"first": "Bob", "last": "Smith"},
		{"first": "Sally", "last": "Smith"},
	}
	mapper := &set.Mapper{
		Transform: strings.ToLower,
	}
	var people []Person
	bound := mapper.Bind(&Person{})
	for _, m := range values {
		person := Person{}
		bound.Rebind(&person)
		for fieldName, fieldValue := range m {
			bound.Set(fieldName, fieldValue)
		}
		if err := bound.Err(); err != nil {
			fmt.Println(err.Error())
		}
		people = append(people, person)
	}
	fmt.Println(people[0].First, people[0].Last)
	fmt.Println(people[1].First, people[1].Last)

}
Output:

Bob Smith
Sally Smith

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 need to be either interface{} or string; i.e. the map needs to be of type map[string]* or map[interface{}]*.

type GetterFunc

type GetterFunc func(name string) interface{}

GetterFunc casts a function into a Getter.

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
	//
	// 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 is used to traverse structures to create Mappings and then navigate the nested structure using string keys.

Instantiate mappers as pointers:

myMapper := &set.Mapper{}
Example
package main

import (
	"fmt"
	"strings"

	"github.com/nofeaturesonlybugs/set"
)

func main() {
	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.Replace(mapping.String(), "\t\t", " ", -1))
	}
	{
		fmt.Println("")
		fmt.Println("lowercase with dot separators")
		mapper := &set.Mapper{
			Join:      ".",
			Transform: strings.ToLower,
		}
		mapping := mapper.Map(&data)
		fmt.Println(strings.Replace(mapping.String(), "\t\t", " ", -1))
	}
	{
		fmt.Println("")
		fmt.Println("specify tags")
		mapper := &set.Mapper{
			Join: "_",
			Tags: []string{"t"},
		}
		mapping := mapper.Map(&data)
		fmt.Println(strings.Replace(mapping.String(), "\t\t", " ", -1))
	}

}
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"
	"time"

	"github.com/nofeaturesonlybugs/set"
)

func main() {
	type T struct {
		S string
		T time.Time
		N sql.NullString
	}

	mapping := set.DefaultMapper.Map(T{})
	if mapping.Get("T") != nil {
		fmt.Println("T is mapped because time.Time is automatically treated as a scalar.")
	}
	if mapping.Get("N") == nil {
		fmt.Println("N can not be found because sql.NullString was not treated as a scalar.")
	}
	if mapping.Get("N_Valid") != nil {
		fmt.Println("N_Valid was mapped because the exported fields in sql.NullString were mapped.")
	}

	//
	// Now we'll treat sql.NullString as a scalar when mapping.
	mapper := &set.Mapper{
		TreatAsScalar: set.NewTypeList(sql.NullString{}),
	}
	mapping = mapper.Map(T{})
	if mapping.Get("N") != nil {
		fmt.Println("N is now mapped to the entire sql.NullString member.")
		v, _ := set.V(&T{}).FieldByIndex(mapping.Get("N"))
		fmt.Printf("N's type is %v\n", v.Type())
	}

}
Output:

T is mapped because time.Time is automatically treated as a scalar.
N can not be found because sql.NullString was not treated as a scalar.
N_Valid was mapped because the exported fields in sql.NullString were mapped.
N is now mapped to the entire sql.NullString member.
N's type is sql.NullString

func (*Mapper) Bind

func (me *Mapper) Bind(I interface{}) BoundMapping

Bind creates a Mapping bound to a specific instance I of a variable.

Example
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/set"
)

func main() {
	type CommonDb struct {
		Pk          int
		CreatedTime string
		UpdatedTime string
	}
	type Person struct {
		CommonDb
		Name string
		Age  int
	}
	Print := func(p Person) {
		fmt.Printf("Person: pk=%v created=%v updated=%v name=%v age=%v\n", p.Pk, p.CreatedTime, p.UpdatedTime, p.Name, p.Age)
	}
	data := []Person{{}, {}}
	Print(data[0])
	Print(data[1])
	//
	mapper := &set.Mapper{
		Elevated: set.NewTypeList(CommonDb{}),
	}
	bound := mapper.Bind(&data[0])
	bound.Set("Pk", 10)
	bound.Set("CreatedTime", "-5h")
	bound.Set("UpdatedTime", "-2h")
	bound.Set("Name", "Bob")
	bound.Set("Age", 30)
	if err := bound.Err(); err != nil {
		fmt.Println(err.Error())
	}
	//
	bound.Rebind(&data[1])
	bound.Set("Pk", 20)
	bound.Set("CreatedTime", "-15h")
	bound.Set("UpdatedTime", "-12h")
	bound.Set("Name", "Sally")
	bound.Set("Age", 20)
	if err := bound.Err(); err != nil {
		fmt.Println(err.Error())
	}
	//
	Print(data[0])
	Print(data[1])

}
Output:

Person: pk=0 created= updated= name= age=0
Person: pk=0 created= updated= name= age=0
Person: pk=10 created=-5h updated=-2h name=Bob age=30
Person: pk=20 created=-15h updated=-12h name=Sally age=20

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 written to be goroutine safe in that multiple goroutines can call it. If multiple goroutines call Map simultaneously it is not guaranteed that each goroutine receives the same Mapping instance; however it is guaranteed that each instance will behave identically.

If you depend on Map returning the same instance of Mapping for a type T every time it is called then you should call it once for each possible type T synchronously before using the Mapper in your 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.

type Mapping

type Mapping struct {
	// Keys is a slice of key names in the order they were encountered during the mapping.
	Keys []string
	// Using a mapped name as a key the []int is the slice of indeces needed to traverse
	// the mapped struct to the nested field.
	Indeces map[string][]int
	// Using a mapped name as a key the StructField is the nested field.  Access to this
	// member is useful if your client package needs to inspect struct-fields-by-mapped-name; such
	// as in obtaining struct tags.
	StructFields map[string]reflect.StructField
}

Mapping is the result of traversing nested structures to generate a mapping of Key-to-Fields.

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 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 types.
	ElemTypeInfo TypeInfo
	// contains filtered or unexported fields
}

Value wraps around a Go variable and performs magic.

func V

func V(arg interface{}) *Value

V returns a new Value.

Memory is possibly created when calling this function:

// No memory is created because b is a local variable and we pass its address.
var b bool
v := set.V(&b)

// No memory is created because bp points at an existing local variable and we pass the pointer bp.
var b bool
bp := &b
v := set.V(bp)

// Memory is created because the local variable is an unallocated pointer AND we pass its address.
var *bp bool
v := set.V(&bp) // bp now contains allocated memory.

func (*Value) Append

func (me *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 (me *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 (me *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 (me *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 (me *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 (me *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 (me *Value) Fill(getter Getter) error

Fill iterates a struct's fields and calls Set() on each one by passing the field name to the Getter. Fill stops and returns on the first error encountered.

func (*Value) FillByTag

func (me *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 (me *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 (me *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
}

func (*Value) To

func (me *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
T is pointer, S is pointer, same type and level of indirection
	-> 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 (Addressability)
package main

import (
	"fmt"

	"github.com/nofeaturesonlybugs/set"
)

func main() {
	fmt.Println("When using Value.To the target Value must be addressable.")
	//
	{
		var value bool
		if err := set.V(value).To(true); err != nil {
			// Expected
			fmt.Println("1. Error because address-of value was not passed.")
		} else {
			fmt.Printf("1. Value is %v\n", value)
		}
	}
	{
		var value bool
		if err := set.V(&value).To(true); err != nil {
			fmt.Println("2. Error because address-of value was not passed.")
		} else {
			// Expected
			fmt.Printf("2. Value is %v\n", value)
		}
	}
	{
		var value *int
		if err := set.V(value).To(42); err != nil {
			// Expected
			fmt.Println("3. Even though value is a pointer itself its address still needs to be passed.")
			if value == nil {
				// Expected
				fmt.Println("3. Also worth noting the pointer remains nil.")
			}
		} else {
			fmt.Printf("3. Value is %v\n", value)
		}
	}
	{
		var value *int
		if err := set.V(&value).To(42); err != nil {
			fmt.Println("4. Even though value is a pointer itself its address still needs to be passed.")
			if value == nil {
				fmt.Println("4. Also worth noting the pointer remains nil.")
			}
		} else {
			// Expected
			fmt.Printf("4. Value is %v\n", *value)
			if value != nil {
				// Expected
				fmt.Println("4. A pointer-to-int was created!")
			}
		}
	}
	{
		var value ***int
		if err := set.V(&value).To(42); err != nil {
			fmt.Println("5. Even though value is a pointer itself its address still needs to be passed.")
			if value == nil {
				fmt.Println("5. Also worth noting the pointer remains nil.")
			}
		} else {
			// Expected
			fmt.Printf("5. Value is %v\n", ***value)
			fmt.Println("5. Multiple pointers had to be created for this to work!")
		}
	}
	{
		value := new(int)
		if err := set.V(value).To(42); err != nil {
			fmt.Println("6. Even though value is a pointer itself its address still needs to be passed.")
		} else {
			// Expected
			fmt.Printf("6. Value is %v\n", *value)
		}
	}
	{
		value := new(**int)
		if err := set.V(value).To(42); err != nil {
			fmt.Println("7. Even though value is a pointer itself its address still needs to be passed.")
		} else {
			// Expected
			fmt.Printf("7. Value is %v\n", ***value)
		}
	}
	{
		var value string
		if err := set.V(&value).To("8. It works with strings too."); err == nil {
			fmt.Println(value)
		}
	}
	{
		var value ***string
		if err := set.V(&value).To("9. String pointers are no different."); err == nil {
			fmt.Println(***value)
		}
	}
}
Output:

When using Value.To the target Value must be addressable.
1. Error because address-of value was not passed.
2. Value is true
3. Even though value is a pointer itself its address still needs to be passed.
3. Also worth noting the pointer remains nil.
4. Value is 42
4. A pointer-to-int was created!
5. Value is 42
5. Multiple pointers had to be created for this to work!
6. Value is 42
7. Value is 42
8. It works with strings too.
9. String pointers are no different.

func (*Value) Zero

func (me *Value) Zero() error

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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