go-maps
A support library for map[string]interface{}
to handle deep merging and type casting without reflection library dependency.
sales pitch
This library offers the following functionality:
- casting to
bool
- casting to
string
- casting to
int64
- casting to
float64
- merging of two or more
map[string]interface
with map friendly deep copying
- casting to a defined struct leveraging the
encoding/json
package
- simply acquiring an
interface{}
at any depth in a map
The casting logic has custom behavior to deal with data types common to other, more dynamic, languages. This gives it great utility with minimal modification external to go. For example any non-zero number, unidentifiable type, or non-empty string without explicit "false", "nil", "null", or "0" value is inferred as true. Similarly, converts numbers to strings sanely and vice-versa.
There may be some strange behavior when casting from float32 to float64; not much I can do, review the IEEE-754, and avoid using float32 when using my library.
Since this package leverages the json library, behavior should be identical.
Attempts were made to leverage the reflect
package, but the tradeoff in code was not beneficial. Near equal lines, and deferrederror-trapping were needed, and there was no support for safely translating between types such as displaying a number as a string or accepting the string "true" as a truthy boolean value. I may try again and put together a benchmark comparison.
The decision to use 64-bit types was both performance and precision based; it also seems to be the default for most packages.
Latest iteration comes complete with table-drive tests to completely validate behavior, as well as benchmarks:
BenchmarkCastBool-8 30000000 57.9 ns/op
BenchmarkCastString-8 10000000 124 ns/op
BenchmarkCastInt-8 20000000 67.1 ns/op
BenchmarkCastFloat-8 20000000 66.4 ns/op
intended purpose
This library was built to deal with map[string]interace{}
data, specifically coming from three other libraries I wrote to gather application configuration:
usage
Merge example:
m1 := make(map[string]interface{})
m2 := make(map[string]interface{})
m1["key"] = "value"
m2["key"] = "value2"
m1 = maps.Merge(m1, m2)
if m1["key"] == "value2" {
//this will be true
}
Casting can also implicitly perform merges, it accepts a reference to a struct plus one or more maps:
var data CustomStruct
maps.To(&data, config, flags)
Casting accepts the map, a fallback or "default" value, and one or more keys (more keys is interpretted as nested map depth):
m := make(map[string]interface{})
m["boolExample"] = true
b, err := maps.Bool(m, false, "boolExample")
if err != nil {
// handle or ignore error
}
You can choose to ignore the returned error when supplying a sane-default for the "fallback" value.