文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

使用 Mapstructure 解析 Json,你学会了吗?

2024-11-30 03:26

关注

图片

其实质就是在面对 value 类型不确定的情况下,怎么解析这个 json?

我下意识就想到了 [mapstructure](https://github.com/mitchellh/mapstructure) 这个库,它可以帮助我们类似 PHP 那样去处理弱类型的结构。

介绍

先来介绍一下 mapstructure 这个库主要用来做什么的吧,官网是这么介绍的:

mapstructure 是一个 Go 库,用于将通用映射值解码为结构,反之亦然,同时提供有用的错误处理。

该库在解码数据流(JSON、Gob 等)中的值时最为有用,因为在读取部分数据之前,您并不十分清楚底层数据的结构。因此,您可以读取 map[string]interface{} 并使用此库将其解码为适当的本地 Go 底层结构。

简单来说,它擅长解析一些我们并不十分清楚底层数据结构的数据流到我们定义的结构体中。

下面我们通过几个例子来简单介绍一下 mapstructure 怎么使用。

例子

普通形式

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

 // 此输入可以来自任何地方,但通常来自诸如解码 JSON 之类的东西,我们最初不太确定结构。
 input := map[string]interface{}{
  "name":   "Tim",
  "age":    31,
  "emails": []string{"one@gmail.com", "two@gmail.com", "three@gmail.com"},
  "extra": map[string]string{
   "twitter": "Tim",
  },
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

输出:

main.Person{Name:"Tim", Age:31, Emails:[]string{"one@gmail.com", "two@gmail.com", "three@gmail.com"}, Extra:map[string]string{"twitter":"Tim"}}

这个方式应该是我们最经常使用的,非常简单的将 map[string]interface{} 映射到我们的结构体中。

在这里,我们并没有指定每个 field 的 tag,让 mapstructure 自动去映射。

如果我们的 input 是一个 json 字符串,那么我们需要将 json 字符串解析为 map[string]interface{} 之后,再将其映射到我们的结构体中。

func jsonDecode() {
 var jsonStr = `{
 "name": "Tim",
 "age": 31,
 "gender": "male"
}`

 type Person struct {
  Name   string
  Age    int
  Gender string
 }
 m := make(map[string]interface{})
 err := json.Unmarshal([]byte(jsonStr), &m)
 if err != nil {
  panic(err)
 }

 var result Person
 err = mapstructure.Decode(m, &result)
 if err != nil {
  panic(err.Error())
 }
 fmt.Printf("%#v\n", result)
}

输出:

main.Person{Name:"Tim", Age:31, Gender:"male"}

嵌入式结构

mapstructure 允许我们压缩多个嵌入式结构,并通过 squash 标签进行处理。

func embeddedStructDecode() {
 // 使用 squash 标签允许压缩多个嵌入式结构。通过创建多种类型的复合结构并对其进行解码来演示此功能。
 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": "Tim",
  "LastName":  "Liu",
  "City":      "China, Guangdong",
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%s %s, %s\n", result.FirstName, result.LastName, result.City)
}

输出:

Tim Liu, China, Guangdong

在这个例子中, Person 里面有着 Location 和 Family 的嵌入式结构体,通过 squash 标签进行压缩,从而达到平铺的作用。

元数据

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

 // 此输入可以来自任何地方,但通常来自诸如解码 JSON 之类的东西,我们最初不太确定结构。
 input := map[string]interface{}{
  "name":  "Tim",
  "age":   31,
  "email": "one@gmail.com",
 }

 // 对于元数据,我们制作了一个更高级的 DecoderConfig,以便我们可以更细致地配置所使用的解码器。在这种情况下,我们只是告诉解码器我们想要跟踪元数据。
 var md mapstructure.Metadata
 var result Person
 config := &mapstructure.DecoderConfig{
  Metadata: &md,
  Result:   &result,
 }

 decoder, err := mapstructure.NewDecoder(config)
 if err != nil {
  panic(err)
 }

 if err = decoder.Decode(input); err != nil {
  panic(err)
 }

 fmt.Printf("value: %#v, keys: %#v, Unused keys: %#v, Unset keys: %#v\n", result, md.Keys, md.Unused, md.Unset)
}

输出:

value: main.Person{Name:"Tim", Age:31, Gender:""}, keys: []string{"Name", "Age"}, Unused keys: []string{"email"}, Unset keys: []string{"Gender"}

从这个例子我们可以看出,使用 Metadata 可以记录我们结构体以及 map[string]interface{} 的差异,相同的部分会正确映射到对应的字段中,而差异则使用了 Unused 和 Unset 来表达。

避免空值的映射

这里的使用其实和内置的 json 库使用方式是一样的,都是借助 omitempty 标签来解决。

func omitemptyDecode() {
 // 添加 omitempty 注释以避免空值的映射键
 type Family struct {
  LastName string
 }
 type Location struct {
  City string
 }
 type Person struct {
  *Family   `mapstructure:",omitempty"`
  *Location `mapstructure:",omitempty"`
  Age       int
  FirstName string
 }

 result := &map[string]interface{}{}
 input := Person{FirstName: "Somebody"}
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

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

输出:

&map[Age:0 FirstName:Somebody]

这里我们可以看到 *Family 和 *Location 都被设置了 omitempty,所以在解析过程中会忽略掉空值。而 Age 没有设置,并且 input 中没有对应的 value,所以在解析中使用对应类型的零值来表达,而 int 类型的零值就是 0。

剩余字段

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

 input := map[string]interface{}{
  "name":   "Tim",
  "age":    31,
  "email":  "one@gmail.com",
  "gender": "male",
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

输出:

main.Person{Name:"Tim", Age:31, Other:map[string]interface {}{"email":"one@gmail.com", "gender":"male"}}

从代码可以看到 Other 字段被设置了 remain,这意味着 input 中没有正确映射的字段都会被放到 Other 中,从输出可以看到,email 和 gender 已经被正确的放到 Other 中了。

自定义标签

func tagDecode() {
 // 请注意,结构类型中定义的 mapstructure 标签可以指示将值映射到哪些字段。
 type Person struct {
  Name string `mapstructure:"person_name"`
  Age  int    `mapstructure:"person_age"`
 }

 input := map[string]interface{}{
  "person_name": "Tim",
  "person_age":  31,
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

输出:

main.Person{Name:"Tim", Age:31}

在 Person 结构中,我们将 person_name 和 person_age 分别映射到 Name 和 Age 中,从而达到在不破坏结构的基础上,去正确的解析。

弱类型解析

正如前面所说,mapstructure 提供了类似 PHP 解析弱类型结构的方法。

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

 // 此输入可以来自任何地方,但通常来自诸如解码 JSON 之类的东西,由 PHP 等弱类型语言生成。
 input := map[string]interface{}{
  "name":   123,  // number => string
  "age":    "31", // string => number
  "emails": map[string]interface{}{}, // empty map => empty array
 }

 var result Person
 config := &mapstructure.DecoderConfig{
  WeaklyTypedInput: true,
  Result:           &result,
 }

 decoder, err := mapstructure.NewDecoder(config)
 if err != nil {
  panic(err)
 }

 err = decoder.Decode(input)
 if err != nil {
  panic(err)
 }

 fmt.Printf("%#v\n", result)
}

输出:

main.Person{Name:"123", Age:31, Emails:[]string{}}

从代码可以看到,input 中的 name、age 和 Person 结构体中的 Name、Age 类型不一致,而 email 更是离谱,一个字符串数组,一个是 map。

但是我们通过自定义 DecoderConfig,将 WeaklyTypedInput 设置成 true 之后,mapstructure 很容易帮助我们解决这类弱类型的解析问题。

但是也不是所有问题都能解决,通过源码我们可以知道有如下限制:

//   - bools to string (true = "1", false = "0")
//   - numbers to string (base 10)
//   - bools to int/uint (true = 1, false = 0)
//   - strings to int/uint (base implied by prefix)
//   - int to bool (true if value != 0)
//   - string to bool (accepts: 1, t, T, TRUE, true, True, 0, f, F,
//     FALSE, false, False. Anything else is an error)
//   - empty array = empty map and vice versa
//   - negative numbers to overflowed uint values (base 10)
//   - slice of maps to a merged map
//   - single values are converted to slices if required. Each
//     element is weakly decoded. For example: "4" can become []int{4}
//     if the target type is an int slice.

大家使用这种弱类型解析的时候也需要注意。

错误处理

mapstructure 错误提示非常的友好,下面我们来看看遇到错误时,它是怎么提示的。

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

 input := map[string]interface{}{
  "name":   123,
  "age":    "bad value",
  "emails": []int{1, 2, 3},
 }

 var result Person
 err := mapstructure.Decode(input, &result)
 if err != nil {
  fmt.Println(err.Error())
 }
}

输出:

5 error(s) decoding:

* 'Age' expected type 'int', got unconvertible type 'string', value: 'bad value'
* 'Emails[0]' expected type 'string', got unconvertible type 'int', value: '1'
* 'Emails[1]' expected type 'string', got unconvertible type 'int', value: '2'
* 'Emails[2]' expected type 'string', got unconvertible type 'int', value: '3'
* 'Name' expected type 'string', got unconvertible type 'int', value: '123'

这里的错误提示会告诉我们每个字段,字段里的值应该需要怎么表达,我们可以通过这些错误提示,比较快的去修复问题。

总结

从上面这些例子看看到 mapstructure 的强大之处,很好的帮我们解决了实实在在的问题,也在节省我们的开发成本。

但是从源码来看,内部使用了大量的反射,这可能会对一些特殊场景带来性能隐患。所以大家在使用的时候,一定要充分考虑产品逻辑以及场景。

以下贴一小段删减过的源码:

// Decode decodes the given raw interface to the target pointer specified
// by the configuration.
func (d *Decoder) Decode(input interface{}) error {
 return d.decode("", input, reflect.ValueOf(d.config.Result).Elem())
}

// Decodes an unknown data type into a specific reflection value.
func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
 ....

 var err error
 outputKind := getKind(outVal)
 addMetaKey := true
 switch outputKind {
 case reflect.Bool:
  err = d.decodeBool(name, input, outVal)
 case reflect.Interface:
  err = d.decodeBasic(name, input, outVal)
 case reflect.String:
  err = d.decodeString(name, input, outVal)
 case reflect.Int:
  err = d.decodeInt(name, input, outVal)
 case reflect.Uint:
  err = d.decodeUint(name, input, outVal)
 case reflect.Float32:
  err = d.decodeFloat(name, input, outVal)
 case reflect.Struct:
  err = d.decodeStruct(name, input, outVal)
 case reflect.Map:
  err = d.decodeMap(name, input, outVal)
 case reflect.Ptr:
  addMetaKey, err = d.decodePtr(name, input, outVal)
 case reflect.Slice:
  err = d.decodeSlice(name, input, outVal)
 case reflect.Array:
  err = d.decodeArray(name, input, outVal)
 case reflect.Func:
  err = d.decodeFunc(name, input, outVal)
 default:
  // If we reached this point then we weren't able to decode it
  return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
 }

 // If we reached here, then we successfully decoded SOMETHING, so
 // mark the key as used if we're tracking metainput.
 if addMetaKey && d.config.Metadata != nil && name != "" {
  d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
 }

 return err
}


来源:爱发白日梦的后端内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯