Go 语言 mapstructure 使用

7,077 阅读3分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

前言

我们经常遇到如何将 map[string]interface{} 转化为 struct, 这个过程会用到反射, 通过反射可以实现,不确定的成员依然适用 map[string]interface{} 表示,确定结构后,再将 map[string]interface{} 解析为具体的某个结构。

主要使用的是 mapstructure 来实现,将 map 转换称 struct

一个第三方库,地址:github.com/mitchellh/m…

json 转换成 map ,然后 map 转换成 struct

json 转换成 struct

json 转换成 struct 只需要使用 json.unmashal 即可

type User struct {
	Name      string
	FansCount int64
}
func TestJsonUnmarshal(t *testing.T) {
	const jsonStream = `
        {"name":"ethancai", "fansCount": 9223372036854775807}
    `
	var user User // 类型为User
	err := JsonUnmarshal(jsonStream, &user)
	if err != nil {
		fmt.Println("error:", err)
	}

	fmt.Printf("%+v \n", user)
}

// 反json化, jsonStr是json化的字符串,v传空的结构体
func JsonUnmarshal(jsonStr string, v interface{}) error {
	var fasterJson = jsoniter.ConfigCompatibleWithStandardLibrary
	byteValue := []byte(jsonStr)
	err := fasterJson.Unmarshal(byteValue, v)
	if err != nil {
		logs.Error("JsonUnmarshal failed for v: %+v, err = %s", v, err.Error())
		return errors.New("JsonUnmarshal failed for v: %+v")
	}
	return nil
}

map 转换成 struct

代码原理: json.Unmarshal 将字节流解码为map[string]interface{}类型, 然后使用mapstructure.Decode将该 JSON 串分别解码为对象值。

下载包:

go get https://github.com/mitchellh/mapstructure

测试代码

package jsontool

import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/mitchellh/mapstructure"
)

type Blog struct {
	BlogId  string `mapstructure:"blogId"`
	Title   string `mapstructrue:"title"`
	Content string `mapstructure:"content"`
	Uid     string `mapstructure:"uid"`
	State   string `mapstructure:"state"`
}

type Event struct {
	Type     string              `json:"type"`
	Database string              `json:"database"`
	Table    string              `json:"table"`
	Data     []map[string]string `json:"data"`
}

func TestMapStructure(t *testing.T) {
	e := Event{}
	msg := []byte(`{    "type": "UPDATE",    "database": "blog",    "table": "blog",    "data": [        {            "blogId": "100001",            "title": "title",            "content": "this is a blog",            "uid": "1000012",            "state": "1"        }    ]}`)
	if err := json.Unmarshal(msg, &e); err != nil {
		panic(err)
	}
	if e.Table == "blog" {
		var blogs []Blog
		if err := mapstructure.Decode(e.Data, &blogs); err != nil {
			panic(err)
		}
		fmt.Println(blogs)
	}

}

执行结果:

=== RUN   TestMapStructure
[{100001 title this is a blog 1000012 1}]
--- PASS: TestMapStructure (0.00s)
PASS

其中 msg 数据结构如下: 在这里插入图片描述

字段标签 mapstructure

默认情况下 mapstructure 使用结构体中字段的名称做映射,例如结构体中有一个 Title 字段, mapstructure 解码时会在 map[string]interface{}中查找键名title,并且字段不区分大小写。

也可以指定字段,设置 mapstructure 标签

type Blog struct {
	BlogId  string `mapstructure:"blogId"`
	Title   string `mapstructrue:"title"`
	Content string `mapstructure:"content"`
	Uid     string `mapstructure:"uid"`
	State   string `mapstructure:"state"`
}

jpath 标签

通过jpath标签指明字段对应的map中实际的值

比如 有个字段Age,需要从map的birth字段转换过来,那么我的map和字段处理如下:

//map的结构:
{
		"people":{
			"age":{
				"birth":"10", //这个是需要转换成Age的字段
				"birthDay":"2021-1-27"	
			}
		}
}

type example struct{
	Age `jpath:"age.birth"`
}

Metadata

mapstructure 执行转换的过程中会可能会一些错误,这个错误主要记录在 mapstructure.Metadata

结构如下:

// mapstructure.go
type Metadata struct {
  Keys   []string
  Unused []string
}
  • Keys:解码成功的键名;
  • Unused:在源数据中存在,但是目标结构中不存在的键名。

测试程序:

var document3 = `{"categories":["12","222","333"],"people":{"name":"jack","age":{"birth":10,"year":2000,"animals":[{"barks":"yes","tail":"yes"},{"barks":"no","tail":"yes"}]}}}`

type Items1 struct {
	Categories []string `mapstructure:"Categories"`
	Peoples    People1  `mapstructure:"People"` // Specify the location of the array
}

type People1 struct {
	Age     int      `mapstructure:"age.birth"` // jpath is relative to the array
	Animals []Animal `mapstructure:"age.animals"`
}
type Animal struct {
	Barks string `jpath:"barks"`
}

func TestMetaData(t *testing.T) {
	var items1 Items1
	config := &mapstructure.DecoderConfig{
		Metadata: &mapstructure.Metadata{
			Keys:   nil,
			Unused: nil,
		},
		Result: &items1,
	}
	decoder, _ := mapstructure.NewDecoder(config)
	docScript := []byte(document3)
	var docMap map[string]interface{}
	_ = json.Unmarshal(docScript, &docMap)

	_ = decoder.Decode(docMap)
	fmt.Println(config.Metadata.Keys)
	fmt.Println(config.Metadata.Unused)
}

执行结果:

=== RUN   TestMetaData
[Categories[0] Categories[1] Categories[2] Categories People]
[People.age People.name]
--- PASS: TestMetaData (0.00s)
PASS

欢迎关注公众号:程序员财富自由之路

参考资料