Go Package: conv
Get:
go get -u github.com/cstockton/go-conv
Example:
fmt.Printf("Basics:\n `TRUE` -> %#v\n `12.3` -> %#v\n `1m2s` -> %#v\n\n",
conv.Bool("YES"), conv.Int64("12.3"), conv.Duration("1m2s"))
from := []string{"1.2", "34.5", "-678.9"}
var into []float64
conv.Slice(&into, from)
fmt.Printf("Slice:\n %#v\n -> %#v\n\n", from, into
Output:
Basics:
`TRUE` -> true
`12.3` -> 12
`1m2s` -> 62000000000
Slice:
[]string{"1.2", "34.5", "-678.9"}
-> []float64{1.2, 34.5, -678.9}
Intro
Package conv provides fast and intuitive conversions across Go types. This library uses reflection to be robust but will bypass it for common conversions, for example string conversion to any type will never use reflection. In most cases this library is as fast or faster then the standard library for similar operations due to various aggressive (but safe) optimizations. The only external dependency (iter) has 100% test coverage and is maintained by me. It is used to walk the values given for map and slice conversion and will never panic. All methods and functions are safe for concurrent use by multiple Goroutines, with a single exception that Slice and Map conversion under certain circumstances may produce undefined results if they are mutated while being traversed.
Overview
All methods and functions accept any type of value for conversion, if unable
to find a reasonable conversion path they will return the target types zero
value. The Conv struct will also report an error on failure, while all the
top level functions (conv.Bool(...), conv.Time(...), etc) will only return a
single value for cases that you wish to leverage zero values. These functions
are powered by the "DefaultConverter" variable so you may replace it with
your own Converter or a Conv struct to adjust behavior.
Example:
fmt.Println(conv.Time("bad time string"))
fmt.Println(conv.Time("Sat Mar 7 11:06:39 PST 2015"))
type MyString string
fmt.Println(conv.Int(MyString(`123`)))
s := `123`
fmt.Println(conv.Int(&s))
var c conv.Conv
i, err := c.Int(`Foo`)
fmt.Printf("Got %v because of err: %v", i, err
Output:
0001-01-01 00:00:00 +0000 UTC
2015-03-07 11:06:39 +0000 PST
123
123
Got 0 because of err: cannot convert "Foo" (type string) to int
Strings
String conversion from any values outside the cases below will simply be the
result of calling fmt.Sprintf("%v", value), meaning it can not fail. An error
is still provided and you should check it to be future proof.
Example:
fmt.Println(conv.String(`Foo`))
fmt.Println(conv.String([]byte(`Foo`)))
fmt.Println(conv.String(struct{ msg string }{"Foo"})
Output:
Foo
Foo
{Foo}
Bools
Bool Conversions supports all the paths provided by the standard libraries
strconv.ParseBool when converting from a string, all other conversions are
simply true when not the types zero value. As a special case zero length map
and slice types are also false, even if initialized.
Example:
fmt.Println(conv.Bool(true), conv.Bool(false))
fmt.Println(conv.Bool("T"), conv.Bool("False"))
fmt.Println(conv.Bool(int64(123)), conv.Bool(int64(0)))
fmt.Println(conv.Bool(time.Duration(123)), conv.Bool(time.Duration(0)))
fmt.Println(conv.Bool(time.Now()), conv.Bool(time.Time{}))
fmt.Println(conv.Bool(struct{ string }{""})
Output:
true false
true false
true false
true false
true false
false
Numerics
Numeric conversion from other numeric values of an identical type will be
returned without modification. Numeric conversions deviate slightly from Go
when dealing with under/over flow. When performing a conversion operation
that would overflow, we instead assign the maximum value for the target type.
Similarly, conversions that would underflow are assigned the minimun value
for that type, meaning unsigned integers are given zero values isntead of
spilling into large positive integers.
Example:
fmt.Println(conv.Int(`-123.456`))
fmt.Println(conv.Uint(`-123.456`))
Output:
-123
0
Durations
Duration conversion supports all the paths provided by the standard libraries
time.ParseDuration when converting from strings, with a couple enhancements
outlined below.
Example:
fmt.Println(conv.Duration(time.Duration(time.Second)))
fmt.Println(conv.Duration("1h30m"))
fmt.Println(conv.Duration("12.15"))
fmt.Println(conv.Duration(`123456`))
Output:
1s
1h30m0s
12.15s
34h17m36s
Slices
Slice conversion will infer the element type from the given slice, using the
associated conversion function as the given structure is traversed
recursively. The behavior if the value is mutated during iteration is
undefined, though at worst an error will be returned as this library will
never panic.
An error is returned if the below restrictions are not met:
- It must be a pointer to a slice, it does not have to be initialized
- The element must be a T or *T of a type supported by this library
Example:
var into []int64
err := conv.Slice(&into, []string{"123", "456", "6789"})
if err != nil {
log.Fatal("err:", err)
}
for _, v := range into {
fmt.Println("v:", v)
Output:
v: 123
v: 456
v: 6789
Maps
Map conversion will infer the conversion functions to use from the key and
element types of the given map. The second argument will be walked as
described in the supporting package, go-iter.
An error is returned if the below restrictions are not met:
- It must be a non-pointer, non-nil initialized map
- Both the key and element T must be supported by this library
- The key must be a value T, the element may be a T or *T
Excerpt from github.com/cstockton/go-iter iter.Walk:
Walk will recursively walk the given interface value as long as an error does
not occur. The pair func will be given a interface value for each value
visited during walking and is expected to return an error if it thinks the
traversal should end. A nil value and error is given to the walk func if an
inaccessible value (can't reflect.Interface()) is found.
Walk is called on each element of maps, slices and arrays. If the underlying
iterator is configured for channels it receives until one fails. Channels
should probably be avoided as ranging over them is more concise.
Example:
into := make(map[string]int64)
err := conv.Map(into, []string{"123", "456", "6789"})
if err != nil {
log.Fatal("err:", err)
}
var keys []string
for k := range into {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println("k:", k, "v:", into[k])
Output:
k: 0 v: 123
k: 1 v: 456
k: 2 v: 6789
Panics
In short, panics should not occur within this library under any circumstance.
This obviously excludes any oddities that may surface when the runtime is not
in a healthy state, i.e. uderlying system instability, memory exhaustion. If
you are able to create a reproducible panic please file a bug report.
Example:
fmt.Println(conv.Bool(nil))
fmt.Println(conv.Bool([][]int{}))
fmt.Println(conv.Bool((chan string)(nil)))
fmt.Println(conv.Bool((*interface{})(nil)))
fmt.Println(conv.Bool((*interface{})(nil)))
fmt.Println(conv.Bool((**interface{})(nil))
Output:
false
false
false
false
false
false
Contributing
Feel free to create issues for bugs, please ensure code coverage remains 100%
with any pull requests.
Bugs and Patches
Feel free to report bugs and submit pull requests.