mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
add typemap (#85)
* [FEATURE] typemap, quick map any type to specified type * [DOC] add more test case --------- Co-authored-by: zhijian.chen <zhijian.chen@longsys.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ fileutil/*.link
|
||||
fileutil/unzip/*
|
||||
slice/testdata/*
|
||||
cryptor/*.pem
|
||||
test
|
||||
50
README.md
50
README.md
@@ -1340,8 +1340,58 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/xerror.md#TryUnwrap)]
|
||||
[[play](https://go.dev/play/p/acyZVkNZEeW)]
|
||||
|
||||
### 22. quick map any to struct or any base type
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/typemap"
|
||||
```
|
||||
#### Example
|
||||
```go
|
||||
type (
|
||||
Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Phone string `json:"phone"`
|
||||
Addr Address `json:"address"`
|
||||
}
|
||||
|
||||
Address struct {
|
||||
Street string `json:"street"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
v := map[string]interface{}{
|
||||
"person":map[string]interface{}{
|
||||
"name":"Nothin",
|
||||
"age":123,
|
||||
"phone":"123421312",
|
||||
"address":map[string]interface{}{
|
||||
"street":"test",
|
||||
"number":1,
|
||||
},
|
||||
},
|
||||
"other":1234,
|
||||
}
|
||||
|
||||
var person Person
|
||||
err :=typemap.MapTo(v["person"],&person)
|
||||
if err != nil {
|
||||
//error handler ...
|
||||
}
|
||||
|
||||
log.Println(person)
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
## How to Contribute
|
||||
|
||||
|
||||
|
||||
|
||||
I really appreciate any code commits which make lancet lib powerful. Please follow the rules below to create your pull request.
|
||||
|
||||
1. Fork the repository.
|
||||
|
||||
@@ -1341,6 +1341,52 @@ import "github.com/duke-git/lancet/v2/xerror"
|
||||
[[doc](https://github.com/duke-git/lancet/blob/main/docs/xerror_zh-CN.md#TryUnwrap)]
|
||||
[[play](https://go.dev/play/p/acyZVkNZEeW)]
|
||||
|
||||
|
||||
|
||||
### 22. [typemap] 快速将map或者其他类型映射到结构体或者指定类型
|
||||
```go
|
||||
import "github.com/duke-git/lancet/v2/typemap"
|
||||
```
|
||||
#### Example
|
||||
```go
|
||||
type (
|
||||
Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Phone string `json:"phone"`
|
||||
Addr Address `json:"address"`
|
||||
}
|
||||
|
||||
Address struct {
|
||||
Street string `json:"street"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
v := map[string]interface{}{
|
||||
"person":map[string]interface{}{
|
||||
"name":"Nothin",
|
||||
"age":123,
|
||||
"phone":"123421312",
|
||||
"address":map[string]interface{}{
|
||||
"street":"test",
|
||||
"number":1,
|
||||
},
|
||||
},
|
||||
"other":1234,
|
||||
}
|
||||
|
||||
var person Person
|
||||
err :=typemap.MapTo(v["person"],&person)
|
||||
if err != nil {
|
||||
//error handler ...
|
||||
}
|
||||
|
||||
log.Println(person)
|
||||
}
|
||||
|
||||
|
||||
## 如何贡献代码
|
||||
|
||||
非常感激任何的代码提交以使 lancet 的功能越来越强大。创建 pull request 时请遵守以下规则。
|
||||
|
||||
7
go.mod
7
go.mod
@@ -6,3 +6,10 @@ require (
|
||||
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a
|
||||
golang.org/x/text v0.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.8.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
16
go.sum
16
go.sum
@@ -1,4 +1,20 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw=
|
||||
golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
180
typemap/typemap.go
Normal file
180
typemap/typemap.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package typemap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var mapUtils = map[reflect.Kind]func(reflect.Value, reflect.Value) error{
|
||||
reflect.String: convertNormal,
|
||||
reflect.Int: convertNormal,
|
||||
reflect.Int16: convertNormal,
|
||||
reflect.Int32: convertNormal,
|
||||
reflect.Int64: convertNormal,
|
||||
reflect.Uint: convertNormal,
|
||||
reflect.Uint16: convertNormal,
|
||||
reflect.Uint32: convertNormal,
|
||||
reflect.Uint64: convertNormal,
|
||||
reflect.Float32: convertNormal,
|
||||
reflect.Float64: convertNormal,
|
||||
reflect.Uint8: convertNormal,
|
||||
reflect.Int8: convertNormal,
|
||||
reflect.Struct: convertNormal,
|
||||
reflect.Complex64: convertNormal,
|
||||
reflect.Complex128: convertNormal,
|
||||
}
|
||||
|
||||
var _ = func() struct{} {
|
||||
mapUtils[reflect.Map] = convertMap
|
||||
mapUtils[reflect.Array] = convertSlice
|
||||
mapUtils[reflect.Slice] = convertSlice
|
||||
return struct{}{}
|
||||
}()
|
||||
|
||||
// MapTo try to map any interface to struct or base type
|
||||
/*
|
||||
Eg:
|
||||
v := map[string]interface{}{
|
||||
"service":map[string]interface{}{
|
||||
"ip":"127.0.0.1",
|
||||
"port":1234,
|
||||
},
|
||||
version:"v1.0.01"
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Service struct {
|
||||
Ip string `json:"ip"`
|
||||
Port int `json:"port"`
|
||||
} `json:"service"`
|
||||
Ver string `json:"version"`
|
||||
}
|
||||
|
||||
var dist Target
|
||||
if err := typemap.MapTo(v,&dist); err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println(dist)
|
||||
|
||||
*/
|
||||
func MapTo(src any, dst any) error {
|
||||
|
||||
dstRef := reflect.ValueOf(dst)
|
||||
if dstRef.Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("dst is not ptr")
|
||||
}
|
||||
|
||||
dstRef = reflect.Indirect(dstRef)
|
||||
|
||||
srcRef := reflect.ValueOf(src)
|
||||
if srcRef.Kind() == reflect.Ptr || srcRef.Kind() == reflect.Interface {
|
||||
srcRef = srcRef.Elem()
|
||||
}
|
||||
if f, ok := mapUtils[srcRef.Kind()]; ok {
|
||||
return f(srcRef, dstRef)
|
||||
}
|
||||
|
||||
return fmt.Errorf("no implemented:%s", srcRef.Type())
|
||||
}
|
||||
|
||||
func convertNormal(src reflect.Value, dst reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
if src.Type() == dst.Type() {
|
||||
dst.Set(src)
|
||||
} else if src.CanConvert(dst.Type()) {
|
||||
dst.Set(src.Convert(dst.Type()))
|
||||
} else {
|
||||
return fmt.Errorf("can not convert:%s:%s", src.Type().String(), dst.Type().String())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertSlice(src reflect.Value, dst reflect.Value) error {
|
||||
if dst.Kind() != reflect.Array && dst.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("error type:%s", dst.Type().String())
|
||||
}
|
||||
l := src.Len()
|
||||
target := reflect.MakeSlice(dst.Type(), l, l)
|
||||
if dst.CanSet() {
|
||||
dst.Set(target)
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
srcValue := src.Index(i)
|
||||
if srcValue.Kind() == reflect.Ptr || srcValue.Kind() == reflect.Interface {
|
||||
srcValue = srcValue.Elem()
|
||||
}
|
||||
if f, ok := mapUtils[srcValue.Kind()]; ok {
|
||||
err := f(srcValue, dst.Index(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertMap(src reflect.Value, dst reflect.Value) error {
|
||||
if src.Kind() != reflect.Map || dst.Kind() != reflect.Struct {
|
||||
if src.Kind() == reflect.Interface {
|
||||
return convertMap(src.Elem(), dst)
|
||||
} else {
|
||||
return fmt.Errorf("src or dst type error,%s,%s", src.Type().String(), dst.Type().String())
|
||||
}
|
||||
}
|
||||
dstType := dst.Type()
|
||||
num := dstType.NumField()
|
||||
exist := map[string]int{}
|
||||
for i := 0; i < num; i++ {
|
||||
k := dstType.Field(i).Tag.Get("json")
|
||||
if k == "" {
|
||||
k = dstType.Field(i).Name
|
||||
}
|
||||
if strings.Contains(k, ",") {
|
||||
taglist := strings.Split(k, ",")
|
||||
if taglist[0] == "" {
|
||||
|
||||
k = dstType.Field(i).Name
|
||||
} else {
|
||||
k = taglist[0]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
exist[k] = i
|
||||
}
|
||||
|
||||
keys := src.MapKeys()
|
||||
for _, key := range keys {
|
||||
if index, ok := exist[key.String()]; ok {
|
||||
v := dst.Field(index)
|
||||
if v.Kind() == reflect.Struct {
|
||||
err := convertMap(src.MapIndex(key), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if v.CanSet() {
|
||||
if v.Type() == src.MapIndex(key).Elem().Type() {
|
||||
v.Set(src.MapIndex(key).Elem())
|
||||
} else if src.MapIndex(key).Elem().CanConvert(v.Type()) {
|
||||
v.Set(src.MapIndex(key).Elem().Convert(v.Type()))
|
||||
} else if f, ok := mapUtils[src.MapIndex(key).Elem().Kind()]; ok && f != nil {
|
||||
err := f(src.MapIndex(key).Elem(), v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("error type:d(%s)s(%s)", v.Type(), src.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
121
typemap/typemap_test.go
Normal file
121
typemap/typemap_test.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package typemap
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type (
|
||||
Person struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
Phone string `json:"phone"`
|
||||
Addr Address `json:"address"`
|
||||
}
|
||||
|
||||
Address struct {
|
||||
Street string `json:"street"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
)
|
||||
|
||||
func TestTransform(t *testing.T) {
|
||||
src := map[string]interface{}{
|
||||
"name": "Nothin",
|
||||
"age": 28,
|
||||
"phone": "123456789",
|
||||
"address": map[string]interface{}{
|
||||
"street": "test",
|
||||
"number": 1,
|
||||
},
|
||||
}
|
||||
|
||||
var p Person
|
||||
err := MapTo(src, &p)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, src["name"], p.Name)
|
||||
assert.Equal(t, src["age"], p.Age)
|
||||
assert.Equal(t, src["phone"], p.Phone)
|
||||
assert.Equal(t, "test", p.Addr.Street)
|
||||
assert.Equal(t, 1, p.Addr.Number)
|
||||
}
|
||||
|
||||
func TestBaseType(t *testing.T) {
|
||||
tc := map[string]interface{}{
|
||||
"i32": -32,
|
||||
"i8": -8,
|
||||
"i16": -16,
|
||||
"i64": -64,
|
||||
"i": -1,
|
||||
"u32": 32,
|
||||
"u8": 8,
|
||||
"u16": 16,
|
||||
"u64": 64,
|
||||
"u": 1,
|
||||
"tf": true,
|
||||
"f32": 1.32,
|
||||
"f64": 1.64,
|
||||
"str": "hello mapto",
|
||||
"complex": 1 + 3i,
|
||||
}
|
||||
|
||||
type BaseType struct {
|
||||
I int `json:"i"`
|
||||
I8 int8 `json:"i8"`
|
||||
I16 int16 `json:"i16"`
|
||||
I32 int32 `json:"i32"`
|
||||
I64 int64 `json:"i64"`
|
||||
U uint `json:"u"`
|
||||
U8 uint8 `json:"u8"`
|
||||
U16 uint16 `json:"u16"`
|
||||
U32 uint32 `json:"u32"`
|
||||
U64 uint64 `json:"u64"`
|
||||
F32 float32 `json:"f32"`
|
||||
F64 float64 `json:"f64"`
|
||||
Tf bool `json:"tf"`
|
||||
Str string `json:"str"`
|
||||
Comp complex128 `json:"complex"`
|
||||
}
|
||||
var dist BaseType
|
||||
err := MapTo(tc, &dist)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, -1, dist.I)
|
||||
assert.EqualValues(t, -8, dist.I8)
|
||||
assert.EqualValues(t, -16, dist.I16)
|
||||
assert.EqualValues(t, -32, dist.I32)
|
||||
assert.EqualValues(t, -64, dist.I64)
|
||||
assert.EqualValues(t, 1, dist.U)
|
||||
assert.EqualValues(t, 8, dist.U8)
|
||||
assert.EqualValues(t, 16, dist.U16)
|
||||
assert.EqualValues(t, 32, dist.U32)
|
||||
assert.EqualValues(t, 64, dist.U64)
|
||||
assert.EqualValues(t, tc["f32"], dist.F32)
|
||||
assert.EqualValues(t, tc["f64"], dist.F64)
|
||||
assert.EqualValues(t, tc["str"], dist.Str)
|
||||
assert.EqualValues(t, tc["tf"], dist.Tf)
|
||||
assert.EqualValues(t, tc["complex"], dist.Comp)
|
||||
|
||||
var number float64
|
||||
|
||||
MapTo(tc["i"], &number)
|
||||
assert.EqualValues(t, -1, number)
|
||||
MapTo(tc["i8"], &number)
|
||||
assert.EqualValues(t, -8, number)
|
||||
MapTo(tc["i16"], &number)
|
||||
assert.EqualValues(t, -16, number)
|
||||
MapTo(tc["i32"], &number)
|
||||
assert.EqualValues(t, -32, number)
|
||||
MapTo(tc["i64"], &number)
|
||||
assert.EqualValues(t, -64, number)
|
||||
MapTo(tc["u"], &number)
|
||||
assert.EqualValues(t, 1, number)
|
||||
MapTo(tc["u8"], &number)
|
||||
assert.EqualValues(t, 8, number)
|
||||
MapTo(tc["u16"], &number)
|
||||
assert.EqualValues(t, 16, number)
|
||||
MapTo(tc["u32"], &number)
|
||||
assert.EqualValues(t, 32, number)
|
||||
MapTo(tc["u64"], &number)
|
||||
assert.EqualValues(t, 64, number)
|
||||
}
|
||||
Reference in New Issue
Block a user