GoAny: A Powerful Type Conversion Library for Go
GoAny is a versatile Go lib that provides a way to convert different data types such as base types, lists, maps, structures, and more, into a specified target type. This utility uses reflection to dynamically handle type conversion, making it a flexible tool for various Go applications.
Installation
To install GoAny, use the go get command:
go get github.com/linchengzhi/goany
Example
GoAny provides the generic conversion function ToAny(in interface{}, out interface{}, options... Options) error, which can convert base types, list, map, struct, etc. It also provides a set of basic type conversion functions, such as ToInt(v interface{}) int, ToString(v interface{}) string, ToTimeE(v interface{}, op ...Options) (time.Time, error), etc. Functions ending with 'E' return an error along with the result.
func TestToAny_Example(t *testing.T) {
var err error
// string to int
vint := goany.ToInt("123")
fmt.Println(vint) //123
vint64, err := goany.ToInt64E("123") //An E ending indicates that an error is returned
fmt.Println(vint64, err) //123 nil
//nil to int, if nil, return default value
vnil := goany.ToInt(nil)
fmt.Println(vnil) //0
// string to time, with options
op := goany.NewOptions().SetLocation(time.UTC)
vtime := goany.ToTime("2020-10-01 21:06:11", *op)
fmt.Println(vtime) //2020-10-01 21:06:11 +0000 UTC
// int to string
var int1, str1 = 123, ""
err = goany.ToAny(int1, &str1)
fmt.Println(str1, err) //123 nil
// time to string
var time2, str2 = time.Date(2020, 10, 1, 21, 6, 11, 0, time.UTC), ""
err = goany.ToAny(time2, &str2)
fmt.Println(str2, err) //2020-10-01 21:06:11 nil
str3, err := goany.ToStringE(time2)
fmt.Println(str3, err) //2020-10-01 21:06:11 nil
//map to struct
type Person struct {
Id string `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var m1 = map[string]interface{}{
"id": 1,
"name": "John",
"age": 20,
}
var p1 Person
err = goany.ToAny(m1, &p1)
fmt.Println(p1, err) //{1, John 20} nil
}
PS:More examples please see test.
Supported Types
GoAny supports the following conversions:
-
Basic types (integers, unsigned integers, floats, bool, string)
Provides common conversions between base types, for example:
Base types have direct conversion functions.
v := goany.ToInt("1") //"1"
v, err := goany.ToInt64E(123) //123 nil Functions ending with 'E' return an error along with the result.
v := goany.ToString(1) //"1"
PS:If struct(except time.Time),list,map to string, it will be converted to json. if list is []byte, it will be converted to string
-
Slices and arrays
Provides list, map, string to list conversion, string must be json format
var in = []interface{}{1, 2}
var out = []string{}
err := goany.ToAny(in, &out) //[]string{"1", "2"}
-
Maps
Provides list, map, struct, string to map conversion, string must be json format
type player struct {
Id int `json:"id"`
Name string `json:"name"`
}
var in = player{Id: 1, Name: "a"}
var out = make(map[string]interface{})
err := goany.ToAny(in, &out) //map[string]interface{}{"id": 1, "name": "a"}
-
Structs (including time.Time)
Provides list, map, struct, string to struct conversion, string must be json format
type player struct {
Id int `json:"id"`
Name string `json:"name"`
}
var in = map[string]interface{}{"id": 1, "name": "a"}
var out = player{}
err := goany.ToAny(in, &out) //player{Id: 1, Name: "a"}
-
Interface
Everything can to interface.If in is struct, option structToMapDetail is true, it will be converted to map[string]interface{}
var in = map[string]interface{}{"id": 1, "name": "a"}
var out interface{}
err := goany.ToAny(in, &out) //map[string]interface{}{"id": 1, "name": "a"}
Options
-
location
Time zone default is "UTC".
locationShanghai, _ := time.LoadLocation("Asia/Shanghai")
var in = "2020-10-01 21:06:11"
op := goany.NewOptions().SetLocation(locationShanghai)
out, err := goany.ToTimeE(in, *op)
fmt.Println(out, err) //2020-10-01 21:06:11 +0800 CST, nil
-
Time format default is "2006-01-02 15:04:05"
locationShanghai, _ := time.LoadLocation("Asia/Shanghai")
in := time.Date(2020, 10, 1, 21, 6, 11, 0, locationShanghai)
op := goany.NewOptions().SetLocation(locationShanghai).SetTimeFormat(time.RFC3339)
out, err := goany.ToStringE(in, *op)
fmt.Println(out, err)//2020-10-01T21:06:11+08:00 <nil>
-
mapKeyField
When list struct to map struct, use the mapKeyField option to specify which struct field to use as the map key when converting a list of structs to a map.
type player struct {
Id int `json:"id"`
Name string `json:"name"`
}
var in = []player{{Id: 1, Name: "a"}, {Id: 2, Name: "b"}}
var out = make(map[string]player)
op := goany.NewOptions().SetMapKeyField("name")
err := goany.ToAny(in, &out, *op)
fmt.Println(out, err)//map[a:{1 a} b:{2 b}] nil
-
mapKeyToList
When map is converted to list, value is converted to list by default. When mapKeyToList is true, map key is used
var in = map[string]interface{}{"id": 1, "name": "a"}
var out = make([]interface{}, 0)
op := goany.NewOptions().SetMapKeyToList(true)
err := goany.ToAny(in, &out, *op)
fmt.Println(out, err)//[id name] nil
-
tagName
Specify the tag of the struct as the field name. The default tag is json. If there is no tag, use the field name
type player struct {
Id int `bson:"id"`
Name string `bson:"name"`
}
var in = map[string]interface{}{"id": 1, "name": "a"}
var out = player{}
op := goany.NewOptions().SetTagName("bson")
err := goany.ToAny(in, &out, *op)
fmt.Println(out, err)//player{Id: 1, Name: "a"}
-
exportedUnExported
If the exportedUnExported value is true, exported fields that are not exportable in the structure can be exported.
If in contains non-exportable structure fields to be exported, in must be a pointer
type player struct {
Id int `json:"id"`
Name string `json:"name"`
age int `json:"age"` //unexported
}
var in = map[string]interface{}{"id": 1, "name": "a", "age": 20}
var out = player{}
op := goany.NewOptions().SetExportedUnExported(true)
err := goany.ToAny(in, &out, *op)
fmt.Println(out, err)//player{Id: 1, Name: "a", age: 20}
-
structToMapDetail
When you want to convert structure depth to map, set structToMapDetail to true.If in contains non-exportable structure fields to be exported, in must be used with a use pointer
type player struct {
Id int `json:"id"`
Name string `json:"name"`
}
type account struct {
Name string `json:"name"`
player player `json:"player"` //nest struct
}
var in = account{Name: "a", player: player{Id: 1, Name: "a"}}
var out = make(map[string]interface{})
op := goany.NewOptions().SetStructToMapDetail(true).SetExportedUnExported(true)
err := goany.ToAny(&in, &out, *op) // in is pointer
fmt.Println(out, err)//map[string]interface{}{"name": "a", "player": map[string]interface{}{"id": 1, "name": "a"}}
-
assignKey
When you want to convert structure to another structure, you can use assignKey to assign the field name
type player struct {
Id int `json:"id"`
Name string `json:"name"`
}
type student struct {
Sid int `json:"sid"` //id to sid
Name string `json:"name"`
}
in := &player{Id: 1, Name: "a"}
out := &student{}
op := goany.NewOptions().SetAssignKey(map[string]string{"id": "sid"})
err := goany.ToAny(in, out, *op)
fmt.Println(out, err)//&student{Sid: 1, Name: "a"}
-
hooks
When you need to customize the parsing process, you can use hooks. The hook function is defined as: func(in interface{}, out reflect.Value) (int, error). Here, in is the input value and out is the output value. The returned integer indicates the parsing state.
If the hook returns goany.DecodeContinue, the parsing continues. If it returns goany.DecodeSkip, the current value is skipped. If it returns goany.DecodeStop, it halts all parsing. Parsing also stops if the error is not nil.
type A struct {
Name string `json:"name"`
}
type B struct {
Name string `json:"name"`
}
hook := func(in interface{}, out reflect.Value) (int, error) {
inType, inVal := goany.ReflectTypeValue(in)
if inType.Kind() == reflect.Struct {
for i := 0; i < inType.NumField(); i++ {
if inType.Field(i).Name == "Name" {
inVal.Field(i).SetString(inVal.Field(i).String() + "_test")
}
}
}
return goany.DecodeContinue, nil
}
a := A{Name: "a"}
b := B{}
err := goany.ToAny(&a, &b, *goany.NewOptions().AddHook(hook))
fmt.Println(b, err) //{a_test} <nil>
-
ignoreBasicTypeErr
If the ignoreBasicTypeErr value is true, the underlying type conversion failure in the struct is skipped and the default value is used.
type player struct {
Name int `json:"name"`
Id int `json:"id"`
}
var in = map[string]interface{}{"id": 1, "name": "a"}
var out = player{}
op := NewOptions().SetIgnoreBasicTypeErr(true)
err := ToAny(in, &out, *op)
fmt.Println(out, err) //player{Id: 1, Name: 0}
Contributing
Contributions to improve ToAny are welcome! Please feel free to submit issues and pull requests to the repository.
License
GoAny is licensed under the MIT license. See the LICENSE file for details.