Skip to content

Commit

Permalink
gvalue MapToStruct
Browse files Browse the repository at this point in the history
  • Loading branch information
snail007 committed Jan 9, 2024
1 parent df5ee9d commit 7743cc0
Show file tree
Hide file tree
Showing 4 changed files with 283 additions and 121 deletions.
123 changes: 4 additions & 119 deletions module/db/resultset.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@
package gdb

import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
gcore "github.com/snail007/gmc/core"
gcast "github.com/snail007/gmc/util/cast"
"reflect"
"strings"
gmap "github.com/snail007/gmc/util/map"
gvalue "github.com/snail007/gmc/util/value"
"time"
)

Expand Down Expand Up @@ -148,121 +144,10 @@ func (rs *ResultSet) Value(column string) (value string) {
return
}

var typeOfBytes = reflect.TypeOf([]byte(nil))

func (rs *ResultSet) mapToStruct(mapData map[string]string, Struct interface{}, tagName ...string) (struCt interface{}, err error) {
func (rs *ResultSet) mapToStruct(mapData map[string]string, structValue interface{}, tagName ...string) (_struct interface{}, err error) {
tag := "column"
if len(tagName) == 1 {
tag = tagName[0]
}
rv := reflect.New(reflect.TypeOf(Struct)).Elem()
if reflect.TypeOf(Struct).Kind() != reflect.Struct {
return nil, errors.New("v must be struct")
}
structType := rv.Type()
var value interface{}
for i, fieldCount := 0, rv.NumField(); i < fieldCount; i++ {
fieldVal := rv.Field(i)
if !fieldVal.CanSet() {
continue
}

field := structType.Field(i)
fieldType := field.Type
fieldKind := fieldType.Kind()
if fieldKind == reflect.Ptr {
fieldType = fieldType.Elem()
fieldKind = fieldType.Kind()
}
col := strings.Split(field.Tag.Get(tag), ",")[0]
val, ok := mapData[col]
if !ok {
val, ok = mapData[field.Name]
}
if !ok {
continue
}
BREAK:
switch fieldKind {
case reflect.Uint8:
value = gcast.ToUint8(val)
case reflect.Uint16:
value = gcast.ToUint16(val)
case reflect.Uint32:
value = gcast.ToUint32(val)
case reflect.Uint64:
value = gcast.ToUint64(val)
case reflect.Uint:
value = gcast.ToUint(val)
case reflect.Int8:
value = gcast.ToInt8(val)
case reflect.Int16:
value = gcast.ToInt16(val)
case reflect.Int32:
value = gcast.ToInt32(val)
case reflect.Int64:
value = gcast.ToInt64(val)
case reflect.Int:
value = gcast.ToInt(val)
case reflect.String, reflect.Interface:
value = gcast.ToString(val)
case reflect.Slice:
if fieldVal.Type() == typeOfBytes {
value = []byte(gcast.ToString(val))
}
case reflect.Bool:
value = gcast.ToBool(val)
case reflect.Float32:
value = gcast.ToFloat32(val)
case reflect.Float64:
value = gcast.ToFloat64(val)
case reflect.Map, reflect.Struct:
switch field.Type.Name() {
case "Time":
unix, e := gcast.ToInt64E(val)
if e == nil {
value = time.Unix(unix, 0).In(time.Local)
} else if v, e := gcast.StringToDateInDefaultLocation(gcast.ToString(val), time.Local); e == nil {
value = v
} else {
err = e
}
default:
d := []byte(gcast.ToString(val))
if !json.Valid(d) {
err = fmt.Errorf("convert json string to map field fail, json format error, field: %s, type: %s", field.Name, fieldKind.String())
break BREAK
}
var iv interface{}
ivIsPtr := false
if fieldKind == reflect.Struct {
ivIsPtr = true
iv = reflect.New(field.Type).Interface()
}
e := json.Unmarshal(d, &iv)
if e != nil {
err = fmt.Errorf("unspported json to map or struct field fail, field: %s, type: %s", field.Name, fieldKind.String())
break BREAK
}
if ivIsPtr {
value = reflect.ValueOf(iv).Elem().Interface()
} else {
value = iv
}
}
default:
err = fmt.Errorf("unspported struct field type, field: %s, type: %s", field.Name, fieldKind.String())
return nil, err
}
rValue := reflect.ValueOf(value)
if !rValue.IsValid() {
e := fmt.Errorf("unspported field: %s, type: %s", field.Name, fieldKind.String())
if err != nil {
e = errors.Wrapf(err, "convert to field error, field: %s, type: %s", field.Name, field.Type.String())
}
return nil, e
}
fieldVal.Set(rValue)
}
return rv.Interface(), err
return gvalue.MapToStruct(gmap.ToAny(mapData), structValue, tag)
}
4 changes: 2 additions & 2 deletions module/db/resultset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func TestMapToStruct(t *testing.T) {
"is_passed": "true",
"created_at": "2023-05-29T10:00:00Z",
"description": "Lorem ipsum",
"bytes": "SGVsbG8gd29ybGQ=",
"bytes": "SGVsbG8gd29ybGQ=", //base64: Hello world
}
rs := &ResultSet{}
result, err := rs.mapToStruct(mapData, TestStruct0{})
Expand All @@ -401,7 +401,7 @@ func TestMapToStruct(t *testing.T) {
IsPassed: true,
CreatedAt: time.Date(2023, 5, 29, 10, 0, 0, 0, time.UTC),
Description: "Lorem ipsum",
Bytes: []byte("SGVsbG8gd29ybGQ="),
Bytes: []byte("Hello world"),
}

if !reflect.DeepEqual(result, expected) {
Expand Down
151 changes: 151 additions & 0 deletions util/value/value.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package gvalue

import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/pkg/errors"
gbytes "github.com/snail007/gmc/util/bytes"
gcast "github.com/snail007/gmc/util/cast"
"golang.org/x/text/language"
Expand Down Expand Up @@ -1173,3 +1177,150 @@ func IndexOf(sliceInterface interface{}, value interface{}) int {

return -1
}

var typeOfBytes = reflect.TypeOf([]byte(nil))

// MapToStruct convert map to struct, _value is a struct, or it's pointer, by default struct tag 'mkey' will be used,
// if 'mkey' not exists the filed name will used as map key, for example: User{} or new(User).
// Returned newStruct is a struct, for example: newStruct.(User) to assert it type.
// Details refer to TestMapToStruct.
func MapToStruct(mapData map[string]interface{}, _value interface{}, tagName ...string) (newStruct interface{}, err error) {
if IsNil(_value) || (reflect.TypeOf(_value).Kind() != reflect.Struct &&
reflect.TypeOf(_value).Kind() != reflect.Ptr) {
return nil, errors.New("v must be struct or pointer")
}
tag := "mkey"
if len(tagName) == 1 {
tag = tagName[0]
}
var rv reflect.Value

if reflect.TypeOf(_value).Kind() == reflect.Ptr {
rv = reflect.ValueOf(_value).Elem()
} else {
rv = reflect.New(reflect.TypeOf(_value)).Elem()
}

structType := rv.Type()
var value interface{}
found := false
for i, fieldCount := 0, rv.NumField(); i < fieldCount; i++ {
fieldVal := rv.Field(i)
if !fieldVal.CanSet() {
continue
}

field := structType.Field(i)
fieldType := field.Type
fieldKind := fieldType.Kind()
if fieldKind == reflect.Ptr {
fieldType = fieldType.Elem()
fieldKind = fieldType.Kind()
}
col := strings.Split(field.Tag.Get(tag), ",")[0]
val, ok := mapData[col]
if !ok {
val, ok = mapData[field.Name]
}
if !ok {
continue
}
BREAK:
switch fieldKind {
case reflect.Uint8:
value = gcast.ToUint8(val)
case reflect.Uint16:
value = gcast.ToUint16(val)
case reflect.Uint32:
value = gcast.ToUint32(val)
case reflect.Uint64:
value = gcast.ToUint64(val)
case reflect.Uint:
value = gcast.ToUint(val)
case reflect.Int8:
value = gcast.ToInt8(val)
case reflect.Int16:
value = gcast.ToInt16(val)
case reflect.Int32:
value = gcast.ToInt32(val)
case reflect.Int64:
value = gcast.ToInt64(val)
case reflect.Int:
value = gcast.ToInt(val)
case reflect.String, reflect.Interface:
value = gcast.ToString(val)
case reflect.Slice:
if fieldVal.Type() == typeOfBytes {
value, err = base64.StdEncoding.DecodeString(gcast.ToString(val))
if err != nil {
err = nil
value = []byte(gcast.ToString(val))
}
}
case reflect.Bool:
value = gcast.ToBool(val)
case reflect.Float32:
value = gcast.ToFloat32(val)
case reflect.Float64:
value = gcast.ToFloat64(val)
case reflect.Map, reflect.Struct:
switch field.Type.Name() {
case "Time":
unix, e := gcast.ToInt64E(val)
if e == nil {
value = time.Unix(unix, 0).In(time.Local)
} else if v, e := gcast.StringToDateInDefaultLocation(gcast.ToString(val), time.Local); e == nil {
value = v
} else {
err = e
break BREAK
}
default:
d := []byte(gcast.ToString(val))
if len(d) == 0 {
d, _ = json.Marshal(val)
}
if !json.Valid(d) {
err = fmt.Errorf("convert json string to map field fail, json format error, field: %s, type: %s", field.Name, fieldKind.String())
break BREAK
}
var iv interface{}
ivIsPtr := false
if fieldKind == reflect.Struct {
ivIsPtr = true
iv = reflect.New(field.Type).Interface()
}
e := json.Unmarshal(d, &iv)
if e != nil {
err = fmt.Errorf("unspported json to map or struct field fail, field: %s, type: %s", field.Name, fieldKind.String())
break BREAK
}
if ivIsPtr {
value = reflect.ValueOf(iv).Elem().Interface()
} else {
value = iv
}
}
default:
err = fmt.Errorf("unspported struct field type, field: %s, type: %s", field.Name, fieldKind.String())
break BREAK
}
if err != nil {
return nil, err
}
rValue := reflect.ValueOf(value)
if !rValue.IsValid() {
e := fmt.Errorf("unspported field: %s, type: %s", field.Name, fieldKind.String())
if err != nil {
e = errors.Wrapf(err, "convert to field error, field: %s, type: %s", field.Name, field.Type.String())
}
return nil, e
}
found = true
fieldVal.Set(rValue)
}
if !found {
return nil, errors.New("any filed be mapped")
}
return rv.Interface(), err
}
Loading

0 comments on commit 7743cc0

Please sign in to comment.