Golang中结构体映射mapstructure库深入详解

 更新时间:2023年01月02日 11:27:17   作者:alwaysrun  
mapstructure用于将通用的map[string]interface{}解码到对应的 Go 结构体中,或者执行相反的操作。很多时候,解析来自多种源头的数据流时,我们一般事先并不知道他们对应的具体类型。只有读取到一些字段之后才能做出判断

在数据传递时,需要先编解码;常用的方式是JSON编解码(参见《golang之JSON处理》)。但有时却需要读取部分字段后,才能知道具体类型,此时就可借助mapstructure库了。

mapstructure库

mapstructure可方便地实现map[string]interface{}struct间的转换;使用前,需要先导入库:

go get github.com/mitchellh/mapstructure

字段标签

默认情况下,mapstructure使用字段的名称做匹配映射(即在map中以字段名为键值查找字段值);注意匹配时是忽略大小写的。也可通过标签来设定字段映射名称:

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

内嵌结构

go中结构体是可以任意嵌套的;嵌套后即认为拥有对应的字段。但是,默认情况下mapstructure只处理当前结构定义的字段,若要自动处理内嵌字段需要添加标签squash

type Student struct {
  Person `mapstructure:",squash"`
  Age int 
}

未映射字段

若源数据中有未映射的值(即结构体中无对应的字段),mapstructure默认会忽略它。可以在结构体中定义一个特殊字段(类型为map[string]interface{},且标签要设置为mapstructure:",remain"),来存放所有未能映射的字段中。

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

Metadata

mapstructure中可以使用Metadata收集一些解码时会产生的有用信息。

// mapstructure.go
type Metadata struct {
  Keys   []string  // 解码成功的键
  Unused []string  // 源数据中存在,但目标结构中不存在的键
  Unset  []string  // 未设定的(源数据中缺失的)键
}

为了获取这些信息,需要使用DecodeMetadata来解码:

  var metadata mapstructure.Metadata
  err := mapstructure.DecodeMetadata(m, &p, &metadata)

弱类型输入

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

  • 布尔转字符串:true = “1”, false = “0”;
  • 布尔转数字:true = 1, false = 0;
  • 数字转布尔:true if value != 0;
  • 字符串转布尔:可接受,
  • 真:1, t, T, TRUE, true, True
  • 假:0, f, F, FALSE, false, False
  • 数字转字符串:自动base10转换;
  • 负数转为无符号数(上溢);
  • 字符串转数字:根据前缀(如0x等)转换;
  • 空数组与空map间互转;
  • 单个值转为切片;

逆向转换

除将map转换为结构体外,mapstructure也可以将结构体反向解码为map[string]interface{}。在反向解码时,我们可以为某些字段设置mapstructure:“,omitempty”,当这些字段为默认值时,就不会出现在map中:

  p := &Student{
    Name: "Mike",
    Age:  12,
  }
  var m map[string]interface{}
  mapstructure.Decode(p, &m)

解码器

mapstructure提供了解码器(Decoder),可灵活方便地控制解码:

type DecoderConfig struct {
    // 若设定,则在任何解码或类型转换(设定了WeaklyTypedInput)前调用;对于设定了squash的内嵌字段,整体调用一次;若返回错误,则整个解码失败
    DecodeHook DecodeHookFunc
    // 若设定,则源数据中存在未使用字段时,报错
    ErrorUnused bool
    // 若设定,则有字段未设定时,报错
    ErrorUnset bool
    // 若设定,则在设定字段前先清空(对于map等类型会先清理掉旧数据)
    ZeroFields bool
    // 若设定,支持若类型间的转换
    WeaklyTypedInput bool
    // Squash will squash embedded structs. 
    Squash bool
    // Metadata is the struct that will contain extra metadata about
    // the decoding. If this is nil, then no metadata will be tracked.
    Metadata *Metadata
    // Result is a pointer to the struct that will contain the decoded
    // value.
    Result interface{}
    // The tag name that mapstructure reads for field names. This
    // defaults to "mapstructure"
    TagName string
    // IgnoreUntaggedFields ignores all struct fields without explicit
    // TagName, comparable to `mapstructure:"-"` as default behaviour.
    IgnoreUntaggedFields bool
    // MatchName is the function used to match the map key to the struct
    // field name or tag. Defaults to `strings.EqualFold`. This can be used
    // to implement case-sensitive tag values, support snake casing, etc.
    MatchName func(mapKey, fieldName string) bool
}

一个支持弱类型转换的示例:要获取的结果放到config的result中

    Name string
    Age  int
}
func decoderConfig() {
    m := map[string]interface{}{
        "name": 123,
        "age":  "12",
        "job":  "programmer",
    }
    var p Person
    var metadata mapstructure.Metadata
    decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
        WeaklyTypedInput: true,
        Result:           &p,
        Metadata:         &metadata,
    })
    if err != nil {
        log.Fatal(err)
    }
    err = decoder.Decode(m)
    if err == nil {
        log.Printf("Result: %#v", p)
        log.Printf("keys:%#v, unused:%#v\n", metadata.Keys, metadata.Unused)
    } else {
        log.Println("decode fail:", err)
    }
}

示例

通过一个messageData结构,action会指示最终的data类型。接收到数据后,先解析出atcion,再根据action转换为真实的类型。

因time.Time是一个结构体(json序列化时会转换为时间字符串),mapstructure无法正确处理,所以推荐使用时间戳。

为了能正确解析内嵌的DataBasic,需要标记为squash。

import "github.com/mitchellh/mapstructure"
type DataBasic struct {
    DataId     string `json:"dataId"`
    UpdateTime int64  `json:"updateTime"`
}
type AddedData struct {
    DataBasic `mapstructure:",squash"`
    Tag string `json:"tag"`
    AddParams map[string]any `json:"addParams"`
}
type messageData struct {
    Action    int    `json:"action"`
    SeqId     uint64 `json:"seqId"`
    Data      any    `json:"data"`
}
func decodeData() {
    add := &AddedData{
        DataBasic: DataBasic{
            DataId:     "a2",
            UpdateTime: time.Now().UnixMilli(),
        },
        Tag: "tag",
        AddParams:  map[string]any{"dataId": "c2", "otherId": "t2"},
    }
    data := &messageData{
        Action: 1,
        Data:   add,
    }
    js, err := json.Marshal(data)
    if err != nil {
        log.Printf("marshal fail: %v", err)
        return
    }
    got := &messageData{}
    err = json.Unmarshal(js, got)
    if err != nil {
        log.Printf("unmarshal fail: %v", err)
        return
    }
    param := new(AddedData)
    err = mapstructure.Decode(got.Data, param)
    if err != nil {
        log.Printf("unmarshal fail: %v", err)
        return
    }
    log.Printf("param: %+v", param)
}

到此这篇关于Golang中结构体映射mapstructure库深入详解的文章就介绍到这了,更多相关Go mapstructure内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Golang调用摄像头并进行图像处理

    使用Golang调用摄像头并进行图像处理

    近年来,摄像头成为了我们生活中不可或缺的设备之一,从智能手机到安全监控系统,无处不在的摄像头给我们带来了便利和安全,在开发摄像头相关的应用程序时,选择一种高效和易用的编程语言是非常重要的,本文将介绍如何使用Golang调用摄像头并进行图像处理
    2023-11-11
  • 深度解密 Go 语言中的 sync.map

    深度解密 Go 语言中的 sync.map

    这篇文章主要介绍了深度解密 Go 语言中的 sync.map,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-06-06
  • Golang实现Trie(前缀树)的示例

    Golang实现Trie(前缀树)的示例

    本文主要介绍了Golang实现Trie(前缀树)的示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Go数组与切片轻松掌握

    Go数组与切片轻松掌握

    在Java的核心库中,集合框架可谓鼎鼎大名:Array、List、Set等等,随便拎一个出来都值得开发者好好学习如何使用甚至是背后的设计源码。虽然Go语言没有如此丰富的容器类型,但也有一些基本的容器供开发者使用,接下来让我们认识一下这些容器类型吧
    2022-11-11
  • 深入浅出go依赖注入工具Wire的使用

    深入浅出go依赖注入工具Wire的使用

    但随着项目规模的增长,组件之间的依赖关系变得复杂,手动管理可能会很繁琐,所以本文将深入探讨一个备受欢迎的 Go 语言依赖注入工具—— Wire,感兴趣的可以了解下
    2023-09-09
  • MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法

    MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法

    这篇文章主要介绍了MacOS中 VSCode 安装 GO 插件失败问题的快速解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-05-05
  • Golang中的panic之避免和处理程序中的异常情况

    Golang中的panic之避免和处理程序中的异常情况

    Golang中的panic是一种异常处理机制,可以在程序出现异常情况时终止程序并打印错误信息。为了避免panic对程序的影响,开发者可以采用一系列技巧,如defer+recover、编写可重入的代码、使用错误返回值等。这些技巧可以帮助开发者优雅地处理程序中的异常情况
    2023-04-04
  • go语言中匿名函数的作用域陷阱详解

    go语言中匿名函数的作用域陷阱详解

    GO语言的匿名函数(anonymous function),其实就是闭包.是指不需要定义函数名的一种函数实现方式,下面这篇文章主要给大家介绍了关于go语言中匿名函数作用域陷阱的相关资料,需要的朋友可以参考下
    2022-05-05
  • 代码整洁利器go fmt命令使用详解

    代码整洁利器go fmt命令使用详解

    这篇文章主要为大家介绍了代码整洁利器go fmt命令使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Golang HTTP服务超时控制实现原理分析

    Golang HTTP服务超时控制实现原理分析

    这篇文章主要介绍了Golang HTTP服务超时控制实现原理,HTTP服务的超时控制是保障服务高可用性的重要措施之一,由于HTTP服务可能会遇到网络延迟,资源瓶颈等问题,因此需要对请求进行超时控制,以避免服务雪崩等问题,需要的朋友可以参考下
    2023-05-05

最新评论