Golang mapstructure

mapstructure

转载:https://darjun.github.io/2020/07/29/godailylib/mapstructure/

作用:

用于将通用的map[string]interface{}解码到对应的 Go 结构体中,或者执行相反的操作。很多时候,解析来自多种源头的数据流时,我们一般事先并不知道他们对应的具体类型。只有读取到一些字段之后才能做出判断。这时,我们可以先使用标准的encoding/json库将数据解码为map[string]interface{}类型,然后根据标识字段利用mapstructure库转为相应的 Go 结构体以便使用。

快速构建:

$ mkdir mapstructure && cd mapstructure

$ go mod init github.com/sixinshuier/mapstructure

下载mapstructure库:

$ go get github.com/mitchellh/mapstructure
package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/mitchellh/mapstructure"
)

type Person struct {
	Name string
	Age  int
	Job  string
}

type Cat struct {
	Name  string
	Age   int
	Breed string
}

func main() {
	datas := []string{`
    { 
      "type": "person",
      "name":"dj",
      "age":18,
      "job": "programmer"
    }
  `,
		`
    {
      "type": "cat",
      "name": "kitty",
      "age": 1,
      "breed": "Ragdoll"
    }
  `,
	}

	for _, data := range datas {
		var m map[string]interface{}
		err := json.Unmarshal([]byte(data), &m)
		if err != nil {
			log.Fatal(err)
		}
		switch m["type"].(string) {
		case "person":
			var p Person
			mapstructure.Decode(m, &p)
			fmt.Println("person", p)

		case "cat":
			var cat Cat
			mapstructure.Decode(m, &cat)
			fmt.Println("cat", cat)
		}
	}
}

字段标签 mapstructure:"username"

type Person struct {
  Name string `mapstructure:"username"`
}

内嵌结构:mapstructure:",squash"

type Friend struct {
  Person `mapstructure:",squash"`  // 将person的字段提到 friend上,即将结构体的字段提到父结构上
}

未映射的值:mapstructure:",remain"

如果源数据中有未映射的值(即结构体中无对应的字段),mapstructure默认会忽略它。

我们可以在结构体中定义一个字段,为其设置mapstructure:",remain"标签。这样未映射的值就会添加到这个字段中。注意,这个字段的类型只能为map[string]interface{}map[interface{}]interface{}。```

package main

import (
   "encoding/json"
   "fmt"
   "github.com/mitchellh/mapstructure"
   "log"
)

type Person struct {
   Name  string
   Age   int
   Job   string
   Other map[string]interface{} `mapstructure:",remain"`
}

func main() {
   data := `
    { 
      "name": "dj",
      "age":18,
      "job":"programmer",
      "height":"1.8m",
      "handsome": true
    }
  `

   var m map[string]interface{}
   err := json.Unmarshal([]byte(data), &m)
   if err != nil {
      log.Fatal(err)
   }

   var p Person
   mapstructure.Decode(m, &p)
   fmt.Println("other", p.Other)
}

逆向反转:mapstructure:",omitempty"

前面我们都是将map[string]interface{}解码到 Go 结构体中。mapstructure当然也可以将 Go 结构体反向解码为map[string]interface{}。在反向解码时,我们可以为某些字段设置mapstructure:",omitempty"。这样当这些字段为默认值时,就不会出现在结构的map[string]interface{}中:

type Person struct {
  Name string
  Age  int
  Job  string `mapstructure:",omitempty"`
}

func main() {
  p := &Person{
    Name: "dj",
    Age:  18,
  }

  var m map[string]interface{}
  mapstructure.Decode(p, &m)

  data, _ := json.Marshal(m)
  fmt.Println(string(data))
}

Metadata

解码时会产生一些有用的信息,mapstructure可以使用Metadata收集这些信息。Metadata结构如下:

// mapstructure.go
type Metadata struct {
  Keys   []string
  Unused []string
}

Metadata只有两个导出字段:

  • Keys:解码成功的键名;
  • Unused:在源数据中存在,但是目标结构中不存在的键名。

为了收集这些数据,我们需要使用DecodeMetadata来代替Decode方法:

type Person struct {
  Name string
  Age  int
}

func main() {
  m := map[string]interface{}{
    "name": "dj",
    "age":  18,
    "job":  "programmer",
  }

  var p Person
  var metadata mapstructure.Metadata
  mapstructure.DecodeMetadata(m, &p, &metadata)

  fmt.Printf("keys:%#v unused:%#v\n", metadata.Keys, metadata.Unused)
}

返回结果:

keys:[]string{"Name", "Age"} unused:[]string{"job"}

弱类型输入:

有时候,我们并不想对结构体字段类型和map[string]interface{}的对应键值做强类型一致的校验。这时可以使用WeakDecode/WeakDecodeMetadata方法,它们会尝试做类型转换:

type Person struct {
  Name   string
  Age    int
  Emails []string
}

func main() {
  m := map[string]interface{}{
    "name":   123,
    "age":    "18",
    "emails": []int{1, 2, 3},
  }

  var p Person
  err := mapstructure.WeakDecode(m, &p)
  if err == nil {
    fmt.Println("person:", p)
  } else {
    fmt.Println(err.Error())
  }
}

解码器

除了上面介绍的方法外,mapstructure还提供了更灵活的解码器(Decoder)。可以通过配置DecoderConfig实现上面介绍的任何功能:

// mapstructure.go
type DecoderConfig struct {
	ErrorUnused       bool
	ZeroFields        bool
	WeaklyTypedInput  bool
	Metadata          *Metadata
	Result            interface{}
	TagName           string
}

各个字段含义如下:

  • ErrorUnused:为true时,如果输入中的键值没有与之对应的字段就返回错误;
  • ZeroFields:为true时,在Decode前清空目标map。为false时,则执行的是map的合并。用在structmap的转换中;
  • WeaklyTypedInput:实现WeakDecode/WeakDecodeMetadata的功能;
  • Metadata:不为nil时,收集Metadata数据;
  • Result:为结果对象,在mapstruct的转换中,Resultstruct类型。在structmap的转换中,Resultmap类型;
  • TagName:默认使用mapstructure作为结构体的标签名,可以通过该字段设置。
posted @ 2021-12-07 12:11  sixinshuier  阅读(1472)  评论(0编辑  收藏  举报