/set

Package set is a small wrapper around the official reflect package that facilitates loose type conversion and assignment into native Go types.

Primary LanguageGoMIT LicenseMIT

Go Reference Go Report Card Build Status codecov License: MIT

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.