一、Map转化结构体

mapstructure Godoc

mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.

This library is most useful when decoding values from some data stream (JSON,
Gob, etc.) where you don’t quite know the structure of the underlying data
until you read a part of it. You can therefore read a map[string]interface{}
and use this library to decode it into the proper underlying native Go
structure.

Installation

Standard go get:

$ go get github.com/mitchellh/mapstructure  
```

### Usage & Example

For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).

The `Decode` function has examples associated with it there.

### But Why?!

Go offers fantastic standard libraries for decoding formats such as JSON.
The standard method is to have a struct pre-created, and populate that struct
from the bytes of the encoded format. This is great, but the problem is if
you have configuration or an encoding that changes slightly depending on
specific fields. For example, consider this JSON:

```json
{
"type": "person",
"name": "Mitchell"
}
```

Perhaps we can't populate a specific structure without first reading
the "type" field from the JSON. We could always do two passes over the
decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a `map[string]interface{}`
structure, read the "type" key, then use something like this library
to decode it into the proper structure.


## 示例
```go
package mapstructure

import (
"fmt"
)

func ExampleDecode() {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}

// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"emails": []string{"one", "two", "three"},
"extra": map[string]string{
"twitter": "mitchellh",
}, }
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}}

func ExampleDecode_errors() {
type Person struct {
Name string
Age int
Emails []string
Extra map[string]string
}

// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{
"name": 123,
"age": "bad value",
"emails": []int{1, 2, 3},
}
var result Person
err := Decode(input, &result)
if err == nil {
panic("should have an error")
}
fmt.Println(err.Error())
// Output:
// 5 error(s) decoding: // // * 'Age' expected type 'int', got unconvertible type 'string' // * 'Emails[0]' expected type 'string', got unconvertible type 'int' // * 'Emails[1]' expected type 'string', got unconvertible type 'int' // * 'Emails[2]' expected type 'string', got unconvertible type 'int' // * 'Name' expected type 'string', got unconvertible type 'int'}

func ExampleDecode_metadata() {
type Person struct {
Name string
Age int
}

// This input can come from anywhere, but typically comes from
// something like decoding JSON where we're not quite sure of the // struct initially. input := map[string]interface{}{
"name": "Mitchell",
"age": 91,
"email": "foo@bar.com",
}
// For metadata, we make a more advanced DecoderConfig so we can
// more finely configure the decoder that is used. In this case, we // just tell the decoder we want to track metadata. var md Metadata
var result Person
config := &DecoderConfig{
Metadata: &md,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
if err := decoder.Decode(input); err != nil {
panic(err)
}
fmt.Printf("Unused keys: %#v", md.Unused)
// Output:
// Unused keys: []string{"email"}}

func ExampleDecode_weaklyTypedInput() {
type Person struct {
Name string
Age int
Emails []string
}

// This input can come from anywhere, but typically comes from
// something like decoding JSON, generated by a weakly typed language // such as PHP. input := map[string]interface{}{
"name": 123, // number => string
"age": "42", // string => number
"emails": map[string]interface{}{}, // empty map => empty array
}

var result Person
config := &DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
}
decoder, err := NewDecoder(config)
if err != nil {
panic(err)
}
err = decoder.Decode(input)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
}

func ExampleDecode_tags() {
// Note that the mapstructure tags defined in the struct type
// can indicate which fields the values are mapped to. type Person struct {
Name string `mapstructure:"person_name"`
Age int `mapstructure:"person_age"`
}

input := map[string]interface{}{
"person_name": "Mitchell",
"person_age": 91,
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%#v", result)
// Output:
// mapstructure.Person{Name:"Mitchell", Age:91}}

func ExampleDecode_embeddedStruct() {
// Squashing multiple embedded structs is allowed using the squash tag.
// This is demonstrated by creating a composite struct of multiple types // and decoding into it. In this case, a person can carry with it both // a Family and a Location, as well as their own FirstName. type Family struct {
LastName string
}
type Location struct {
City string
}
type Person struct {
Family `mapstructure:",squash"`
Location `mapstructure:",squash"`
FirstName string
}

input := map[string]interface{}{
"FirstName": "Mitchell",
"LastName": "Hashimoto",
"City": "San Francisco",
}
var result Person
err := Decode(input, &result)
if err != nil {
panic(err)
}
fmt.Printf("%s %s, %s", result.FirstName, result.LastName, result.City)
// Output:
// Mitchell Hashimoto, San Francisco}

二、结构体转化Map

这个仓库提供将Golang的结构体转化为map的函数。它支持:

  1. 使用tag去定义结构体中的域(field)的名字。如果没有指定,就使用结构体的域的名字。
  2. 域(field)可以自定义自己的转化成map的方法。这个自定义的方法要有(string,interface{})作为输出,其中string作为map的键(key),interface作为map的值。

并且,会跳过没有导出的域,空指针,和没有标签的域。

标签

目前,支持4种标签

  • ‘-‘:忽略当前这个域
  • ‘omitempty’ : 当这个域的值为空,忽略这个域
  • ‘dive’ : 递归地遍历这个结构体,将所有字段作为键
  • ‘wildcard’: 只适用于字符串类型,返回”%”+值+”%”,这是为了方便数据库的模糊查询

例子

举个例子,

type User struct {  
Name string `map:"name,omitempty"` // string
Github GithubPage `map:"github,dive,omitempty"` // struct dive
NoDive StructNoDive `map:"no_dive,omitempty"` // no dive struct
MyProfile Profile `map:"my_profile,omitempty"` // struct implements its own method
}

type GithubPage struct {
URL string `map:"url"`
Star int `map:"star"`
}

type StructNoDive struct {
NoDive int `map:"no_dive_int"`
}

type Profile struct {
Experience string `map:"experience"`
Date time.Time `map:"time"`
}

// 自定义实现转化方法,输出子对象,不自定义会递归建立map
func (p Profile) StructToMap() (key string, value interface{}) {
return "time", p.Date.Format(timeLayout)
}
```
使用
```go
res, err := structmap.StructToMap(&user, tag, methodName)
```
转化为
```go
map[string]interface{}{
"name": "user",
"no_dive": map[string]int{"no_dive_int": 1},
// dive struct field
"url": "https://github.com/liangyaopei",
"star": 1,
// customized method
"time": "2020-07-21 12:00:00",
}

mapstructure源码:

package structmap  

import (
"fmt"
"reflect" "strings")

const (
methodResNum = 2
)

const (
// OptIgnore 忽略
OptIgnore = "-"
// OptOmitempty 为空忽略
OptOmitempty = "omitempty"
// OptDive 递归遍历,所有子结构体key转化为父map -> 最终只有一个map 扁平化
OptDive = "dive"
// OptWildcard 只能用于string 输出为 %值%
OptWildcard = "wildcard"
)

const (
flagIgnore = 1 << iota
flagOmiEmpty flagDive flagWildcard)

// StructToMap convert a golang sturct to a map// key can be specified by tag, LIKE `map:"tag"`.
// If there is no tag, struct filed name will be used instead
// methodName is the name the field has implemented.
// If implemented, it uses the method to get the key and value
func StructToMap(s interface{}, tag string, methodName string) (res map[string]interface{}, err error) {
v := reflect.ValueOf(s)

if v.Kind() == reflect.Ptr && v.IsNil() {
return nil, fmt.Errorf("%s is a nil pointer", v.Kind().String())
} if v.Kind() == reflect.Ptr {
v = v.Elem()
} // only accept struct param
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("s is not a struct but %s", v.Kind().String())
}
t := v.Type()
res = make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
fieldType := t.Field(i)

// ignore unexported field
if fieldType.PkgPath != "" {
continue
}
// read tag
tagVal, flag := readTag(fieldType, tag)

if flag&flagIgnore != 0 {
continue
}

fieldValue := v.Field(i)
if flag&flagOmiEmpty != 0 && fieldValue.IsZero() {
continue
}

// ignore nil pointer in field
if fieldValue.Kind() == reflect.Ptr && fieldValue.IsNil() {
continue
}
if fieldValue.Kind() == reflect.Ptr {
fieldValue = fieldValue.Elem()
}
// get kind
switch fieldValue.Kind() {
case reflect.Slice, reflect.Array:
if methodName != "" {
_, ok := fieldValue.Type().MethodByName(methodName)
if ok {
key, value, err := callFunc(fieldValue, methodName)
if err != nil {
return nil, err
}
res[key] = value
continue
}
} res[tagVal] = fieldValue
case reflect.Struct:
if methodName != "" {
_, ok := fieldValue.Type().MethodByName(methodName)
if ok {
key, value, err := callFunc(fieldValue, methodName)
if err != nil {
return nil, err
}
res[key] = value
continue
}
}
// recursive
deepRes, deepErr := StructToMap(fieldValue.Interface(), tag, methodName)
if deepErr != nil {
return nil, deepErr
}
if flag&flagDive != 0 {
for k, v := range deepRes {
res[k] = v
}
} else {
res[tagVal] = deepRes
}
case reflect.Map:
res[tagVal] = fieldValue
case reflect.Chan:
res[tagVal] = fieldValue
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64:
res[tagVal] = fieldValue.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64:
res[tagVal] = fieldValue.Uint()
case reflect.Float32, reflect.Float64:
res[tagVal] = fieldValue.Float()
case reflect.String:
if flag&flagWildcard != 0 {
res[tagVal] = "%" + fieldValue.String() + "%"
} else {
res[tagVal] = fieldValue.String()
} case reflect.Bool:
res[tagVal] = fieldValue.Bool()
case reflect.Complex64, reflect.Complex128:
res[tagVal] = fieldValue.Complex()
case reflect.Interface:
res[tagVal] = fieldValue.Interface()
default:
}
} return
}

// readTag read tag with format `json:"name,omitempty"` or `json:"-"`// For now, only supports above format
func readTag(f reflect.StructField, tag string) (string, int) {
// 读取当前结构体标签内容
val, ok := f.Tag.Lookup(tag)
fieldTag := ""
flag := 0

// 没有标签信息
if !ok {
flag |= flagIgnore
return "", flag
}
// 分割标签内容
opts := strings.Split(val, ",")

fieldTag = opts[0]
// 循环检查是否包含忽略状态或者
for i := 0; i < len(opts); i++ {
switch opts[i] {
case OptIgnore:
flag |= flagIgnore
case OptOmitempty:
flag |= flagOmiEmpty
case OptDive:
flag |= flagDive
case OptWildcard:
flag |= flagWildcard
}
}
return fieldTag, flag
}

// callFunc
// @Description: 执行自定义映射目标方法
// @param
// @param methodName 目标方法名称
// @return string 返回的key
// @return interface{} 返回的v
// @return error
func callFunc(fv reflect.Value, methodName string) (string, interface{}, error) {
// 检查目标方法的返回值数量 并且执行目标方法
methodRes := fv.MethodByName(methodName).Call([]reflect.Value{})
// 因为必须转化为map 因此返回值是 string , interface if len(methodRes) != methodResNum {
return "", nil, fmt.Errorf("wrong method %s, should have 2 output: (string,interface{})", methodName)
} // 校验类型
if methodRes[0].Kind() != reflect.String {
return "", nil, fmt.Errorf("wrong method %s, first output should be string", methodName)
} // 返回方法执行结果
key := methodRes[0].String()
return key, methodRes[1], nil
}