背景
前几天群里的小伙伴问了一个这样的问题:
图片
其实质就是在面对 value 类型不确定的情况下,怎么解析这个 json?
我下意识就想到了 [mapstructure](https://github.com/mitchellh/mapstructure) 这个库,它可以帮助我们类似 PHP 那样去处理弱类型的结构。
介绍
先来介绍一下 mapstructure 这个库主要用来做什么的吧,官网是这么介绍的:
mapstructure 是一个 Go 库,用于将通用映射值解码为结构,反之亦然,同时提供有用的错误处理。
该库在解码数据流(JSON、Gob 等)中的值时最为有用,因为在读取部分数据之前,您并不十分清楚底层数据的结构。因此,您可以读取 map[string]interface{} 并使用此库将其解码为适当的本地 Go 底层结构。
简单来说,它擅长解析一些我们并不十分清楚底层数据结构的数据流到我们定义的结构体中。
下面我们通过几个例子来简单介绍一下 mapstructure 怎么使用。
例子
普通形式
输出:
这个方式应该是我们最经常使用的,非常简单的将 map[string]interface{} 映射到我们的结构体中。
在这里,我们并没有指定每个 field 的 tag,让 mapstructure 自动去映射。
如果我们的 input 是一个 json 字符串,那么我们需要将 json 字符串解析为 map[string]interface{} 之后,再将其映射到我们的结构体中。
输出:
嵌入式结构
mapstructure 允许我们压缩多个嵌入式结构,并通过 squash 标签进行处理。
输出:
在这个例子中, Person 里面有着 Location 和 Family 的嵌入式结构体,通过 squash 标签进行压缩,从而达到平铺的作用。
元数据
输出:
从这个例子我们可以看出,使用 Metadata 可以记录我们结构体以及 map[string]interface{} 的差异,相同的部分会正确映射到对应的字段中,而差异则使用了 Unused 和 Unset 来表达。
- Unused:map 中有着结构体所没有的字段。
- Unset:结构体中有着 map 中所没有的字段。
避免空值的映射
这里的使用其实和内置的 json 库使用方式是一样的,都是借助 omitempty 标签来解决。
输出:
这里我们可以看到 *Family 和 *Location 都被设置了 omitempty,所以在解析过程中会忽略掉空值。而 Age 没有设置,并且 input 中没有对应的 value,所以在解析中使用对应类型的零值来表达,而 int 类型的零值就是 0。
剩余字段
输出:
从代码可以看到 Other 字段被设置了 remain,这意味着 input 中没有正确映射的字段都会被放到 Other 中,从输出可以看到,email 和 gender 已经被正确的放到 Other 中了。
自定义标签
输出:
在 Person 结构中,我们将 person_name 和 person_age 分别映射到 Name 和 Age 中,从而达到在不破坏结构的基础上,去正确的解析。
弱类型解析
正如前面所说,mapstructure 提供了类似 PHP 解析弱类型结构的方法。
输出:
从代码可以看到,input 中的 name、age 和 Person 结构体中的 Name、Age 类型不一致,而 email 更是离谱,一个字符串数组,一个是 map。
但是我们通过自定义 DecoderConfig,将 WeaklyTypedInput 设置成 true 之后,mapstructure 很容易帮助我们解决这类弱类型的解析问题。
但是也不是所有问题都能解决,通过源码我们可以知道有如下限制:
大家使用这种弱类型解析的时候也需要注意。
错误处理
mapstructure 错误提示非常的友好,下面我们来看看遇到错误时,它是怎么提示的。
输出:
这里的错误提示会告诉我们每个字段,字段里的值应该需要怎么表达,我们可以通过这些错误提示,比较快的去修复问题。
总结
从上面这些例子看看到 mapstructure 的强大之处,很好的帮我们解决了实实在在的问题,也在节省我们的开发成本。
但是从源码来看,内部使用了大量的反射,这可能会对一些特殊场景带来性能隐患。所以大家在使用的时候,一定要充分考虑产品逻辑以及场景。
以下贴一小段删减过的源码: