Skip to content

Commit

Permalink
Add func StringMapE #2
Browse files Browse the repository at this point in the history
* Support JSON string of map
* Support any `map` type
* Support any `struct` type
  • Loading branch information
shockerli committed Nov 28, 2021
1 parent 880ebeb commit 7d83d70
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 3 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,30 @@ cvt.SliceStringE(map[int]string{2: "222", 1: "11.1"}) // []string{"11.1", "222
> more case see [slice_test.go](slice_test.go)

### map
- StringMapE

```go
// JSON
// expect: map[string]interface{}{"name": "cvt", "age": 3.21}
cvt.StringMapE(`{"name":"cvt","age":3.21}`)

// Map
// expect: map[string]interface{}{"111": "cvt", "222": 3.21}
cvt.StringMapE(map[interface{}]interface{}{111: "cvt", "222": 3.21})

// Struct
// expect: map[string]interface{}{"Name": "cvt", "Age": 3}
cvt.StringMapE(struct {
Name string
Age int
}{"cvt", 3})
```

> more case see [map_test.go](map_test.go)


## License

This project is under the terms of the [MIT](LICENSE) license.
24 changes: 24 additions & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,30 @@ cvt.SliceStringE(map[int]string{2: "222", 1: "11.1"}) // []string{"11.1", "222
> 更多示例: [slice_test.go](slice_test.go)

### map
- StringMapE

```go
// JSON
// expect: map[string]interface{}{"name": "cvt", "age": 3.21}
cvt.StringMapE(`{"name":"cvt","age":3.21}`)

// Map
// expect: map[string]interface{}{"111": "cvt", "222": 3.21}
cvt.StringMapE(map[interface{}]interface{}{111: "cvt", "222": 3.21})

// Struct
// expect: map[string]interface{}{"Name": "cvt", "Age": 3}
cvt.StringMapE(struct {
Name string
Age int
}{"cvt", 3})
```

> 更多示例: [map_test.go](map_test.go)


## 开源协议

本项目基于 [MIT](LICENSE) 协议开放源代码。
9 changes: 9 additions & 0 deletions cvte.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ func ptrType(rt reflect.Type) reflect.Type {
return rt
}

func ptrValue(rv reflect.Value) reflect.Value {
if rv.Kind() == reflect.Ptr {
for rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
}
return rv
}

// returns the value with base type
func indirect(a interface{}) (val interface{}, rv reflect.Value) {
if a == nil {
Expand Down
3 changes: 0 additions & 3 deletions cvte_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ func assertError(t *testing.T, err error, msgAndArgs ...interface{}) {
fail(t, "An error is expected but got nil", msgAndArgs...)
return
}
return
}

// assert no error
Expand All @@ -200,7 +199,6 @@ func assertNoError(t *testing.T, err error, msgAndArgs ...interface{}) {
fail(t, fmt.Sprintf("Received unexpected error:\n\t\t\t\t%+v", err), msgAndArgs...)
return
}
return
}

// assert equal
Expand All @@ -217,7 +215,6 @@ func assertEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...inter
"actual : %s", expected, actual), msgAndArgs...)
return
}
return
}

func fail(t *testing.T, failureMessage string, msgAndArgs ...interface{}) {
Expand Down
56 changes: 56 additions & 0 deletions map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cvt

import (
"encoding/json"
"reflect"
)

// StringMapE convert an interface to `map[string]interface{}`
// * Support JSON string of map
// * Support any `map` type
// * Support any `struct` type
func StringMapE(val interface{}) (m map[string]interface{}, err error) {
m = make(map[string]interface{})
if val == nil {
return nil, errUnsupportedTypeNil
}

_, rv := indirect(val)
switch rv.Kind() {
case reflect.Map:
for _, key := range rv.MapKeys() {
m[String(key.Interface())] = rv.MapIndex(key).Interface()
}
case reflect.Struct:
m = struct2map(rv)
case reflect.String:
// JSON string of map
err = json.Unmarshal([]byte(rv.String()), &m)
}

return
}

func struct2map(rv reflect.Value) map[string]interface{} {
var m = make(map[string]interface{})
if !rv.IsValid() {
return m
}

for j := 0; j < rv.NumField(); j++ {
f := rv.Type().Field(j)
t := ptrType(f.Type)
vv := ptrValue(rv.Field(j))
if f.Anonymous && t.Kind() == reflect.Struct {
for k, v := range struct2map(vv) {
// anonymous sub-field has a low priority
if _, ok := m[k]; !ok {
m[k] = v
}
}
} else if vv.IsValid() && vv.CanInterface() {
m[f.Name] = vv.Interface()
}
}
return m
}
88 changes: 88 additions & 0 deletions map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package cvt_test

import (
"fmt"
"github.com/shockerli/cvt"
"testing"
)

func TestStringMapE(t *testing.T) {
tests := []struct {
input interface{}
expect map[string]interface{}
isErr bool
}{
// JSON String
{`{"name":"cvt","age":3.21}`, map[string]interface{}{"name": "cvt", "age": 3.21}, false},
{`{"name":"cvt","tag":"convert"}`, map[string]interface{}{"name": "cvt", "tag": "convert"}, false},
{`{"name":"cvt","build":true}`, map[string]interface{}{"name": "cvt", "build": true}, false},

// Map
{map[string]interface{}{}, map[string]interface{}{}, false},
{map[string]interface{}{"name": "cvt", "age": 3.21}, map[string]interface{}{"name": "cvt", "age": 3.21}, false},
{map[interface{}]interface{}{"name": "cvt", "age": 3.21}, map[string]interface{}{"name": "cvt", "age": 3.21}, false},
{map[interface{}]interface{}{111: "cvt", "222": 3.21}, map[string]interface{}{"111": "cvt", "222": 3.21}, false},

// Struct
{struct {
Name string
Age int
}{"cvt", 3}, map[string]interface{}{"Name": "cvt", "Age": 3}, false},
{&struct {
Name string
Age int
}{"cvt", 3}, map[string]interface{}{"Name": "cvt", "Age": 3}, false},
{struct {
A1 string
TestStructC
}{"a1", TestStructC{"c1"}}, map[string]interface{}{"A1": "a1", "C1": "c1"}, false},
{struct {
A1 string
TestStructC
C1 string
}{"a1", TestStructC{"c1-1"}, "c1-2"}, map[string]interface{}{"A1": "a1", "C1": "c1-2"}, false},
{struct {
A1 string
*TestStructC
C1 string
}{"a1", &TestStructC{"c1-1"}, "c1-2"}, map[string]interface{}{"A1": "a1", "C1": "c1-2"}, false},
{struct {
C1 string
*TestStructC
A1 string
}{"c1-1", &TestStructC{"c1-2"}, "a1"}, map[string]interface{}{"A1": "a1", "C1": "c1-1"}, false},
{struct {
AliasTypeInt8
}{5}, map[string]interface{}{"AliasTypeInt8": AliasTypeInt8(5)}, false},
{struct {
*AliasTypeInt
}{&aliasTypeInt0}, map[string]interface{}{"AliasTypeInt": aliasTypeInt0}, false},
{struct {
*AliasTypeInt
}{}, map[string]interface{}{}, false},
{struct {
*TestStructC
}{}, map[string]interface{}{}, false},

// errors
{nil, nil, true},
{"", nil, true},
{"hello", nil, true},
}

for i, tt := range tests {
msg := fmt.Sprintf(
"i = %d, input[%+v], expect[%+v], isErr[%v]",
i, tt.input, tt.expect, tt.isErr,
)

v, err := cvt.StringMapE(tt.input)
if tt.isErr {
assertError(t, err, "[HasErr] "+msg)
continue
}

assertNoError(t, err, "[NoErr] "+msg)
assertEqual(t, tt.expect, v, "[WithE] "+msg)
}
}

0 comments on commit 7d83d70

Please sign in to comment.