使用 mapstructure 解析 json

介绍

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

mapstructure 是一个 Go 库,用于将通用映射值解码为结构,反之亦然,同时提供有用的错误处理。
该库在解码数据流(JSON、Gob 等)中的值时最为有用,因为在读取部分数据之前,您并不十分清楚底层数据的结构。因此,您可以读取 map[string]interface{} 并使用此库将其解码为适当的本地 Go 底层结构。

简单来说,它擅长解析一些我们并不十分清楚底层数据结构的数据流到我们定义的结构体中。
下面我们通过几个例子来简单介绍一下 mapstructure 怎么使用。

例子

普通形式

func normalDecode() {type Person struct {Name   stringAge    intEmails []stringExtra  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 Personerr := 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   stringAge    intGender string}m := make(map[string]interface{})err := json.Unmarshal([]byte(jsonStr), &m)if err != nil {panic(err)}var result Personerr = 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 Personerr := 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   stringAge    intGender string}// 此输入可以来自任何地方,但通常来自诸如解码 JSON 之类的东西,我们最初不太确定结构。input := map[string]interface{}{"name":  "Tim","age":   31,"email": "one@gmail.com",}// 对于元数据,我们制作了一个更高级的 DecoderConfig,以便我们可以更细致地配置所使用的解码器。在这种情况下,我们只是告诉解码器我们想要跟踪元数据。var md mapstructure.Metadatavar result Personconfig := &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 来表达。

Unused:map 中有着结构体所没有的字段。
Unset:结构体中有着 map 中所没有的字段。

避免空值的映射

这里的使用其实和内置的 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       intFirstName 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  stringAge   intOther map[string]interface{} `mapstructure:",remain"`}input := map[string]interface{}{"name":   "Tim","age":    31,"email":  "one@gmail.com","gender": "male",}var result Personerr := 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 Personerr := 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   stringAge    intEmails []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 Personconfig := &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   stringAge    intEmails []stringExtra  map[string]string}input := map[string]interface{}{"name":   123,"age":    "bad value","emails": []int{1, 2, 3},}var result Personerr := 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 erroroutputKind := getKind(outVal)addMetaKey := trueswitch 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 itreturn 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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/743857.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信名【无感】的同学,你还好吗?

今天遇到个选择了微信一对一服务的同学,问Python问题,问题比较简单。 回答完问题,我就说了一句:问题比较简单,随意打赏一个红包就行了。 然后我就被拉黑了,然后我的解答问题,收到了一堆投诉&…

Java算法-力扣leetcode-380. O(1) 时间插入、删除和获取随机元素

380. O(1) 时间插入、删除和获取随机元素 实现RandomizedSet 类: RandomizedSet() 初始化 RandomizedSet 对象bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。bool rem…

深入解析Java中锁机制以及底层原理

一、概述 1.1 背景 概念:锁是多线程编程中的机制,用于控制对共享资源的访问。可以防止多个线程同时修改或读取共享资源,从而保证线程安全。 作用:锁用于实现线程间的互斥和协调,确保在多线程环境下对共享资源的访问顺…

Flutter开发入门——Widget和常用组件

1.什么是Widget? 在Flutter中几乎所有的对象都是一个 widget 。与原生开发中“控件”不同的是,Flutter 中的 widget 的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetecto…

spring中事务失效的场景有哪些?

异常捕获处理 在方法中已经将异常捕获处理掉并没有抛出。 事务只有捕捉到了抛出的异常才可以进行处理,如果有异常业务中直接捕获处理掉没有抛出,事务是无法感知到的。 解决:在catch块throw抛出异常。 抛出检查异常 spring默认只会回滚非检…

对象的组合复用学习笔记

简单说,就是不同类的多个对象之间彼此调用对方的方法和变量,可能会多次调用,所以叫复用(重复),所有对象本身具有独立的功能(方法),共同完成一项任务的一部分,或者多个类的对象协助一…

ChatGPT浪潮来袭!谁先掌握,谁将领先!

任正非在接受采访时说 今后职场上只有两种人, 一种是熟练使用AI的人, 另一种是创造AI工具的人。 虽然这个现实听起来有些夸张的残酷, 但这就是我们必须面对的事实 📆 对于我们普通人来说,我们需要努力成为能够掌握…

基于STM32的智慧农业管理系统设计与实现

文章目录 一、前言1.1 项目介绍【1】项目功能【2】设计实现的功能【3】项目硬件模块组成 1.2 设计思路1.3 传感器功能介绍1.4 开发工具的选择 二、EMQX开源MQTT服务器框架三、购买ECS云服务器3.1 登录官网3.2 购买ECS服务器3.3 配置安全组3.4 安装FinalShell3.5 远程登录到云服…

xsslabs靶场通关(持续更新)

文章目录 前言一、level1思路实现 二、levle2思路 三、level3思路实现 四、level4思路实现 五、level5思路实现 六、level6思路实现 七、level7思路实现 八、level8思路实现 九、level9思路实现 前言 本篇文章将介绍在xsslabs这个靶场(在不知道源码的前提下&#x…

Linux从0到1——Linux环境基础开发工具的使用(上)

Linux从0到1——Linux环境基础开发工具的使用(上) 1. Linux软件包管理器yum1.1 yum介绍1.2 用yum来下载软件1.3 更新yum源 2. Linux编辑器:vi/vim2.1 vim的基本概念2.2 vim的基本操作2.3 vim正常模式命令集2.4 vim底行模式命令集2.5 视图模式…

Java初阶数据结构队列的实现

1.队列的概念 1.队列就是相当于排队打饭 2.在排队的时候就有一个队头一个队尾。 3.从队尾进对头出 4.所以他的特点就是先进先出 所以我们可以用链表来实现 单链表实现要队尾进队头出{要有last 尾插头删} 双向链表实现效率高:不管从哪个地方当作队列都是可以的&…

OpenMP 编程模型

OpenMP 内存模型 共享内存模型: OpenMP 专为多处理器/核心、共享内存机器设计,底层架构可以是共享内存UMA或NUM OpenMP 执行模型 基于线程的并行: OpenMP 程序基于多线程来实现并行, 线程是操作系统可以调度的最小执行单元。 …

Database Connection Pool 数据库连接池-01-概览及简单手写实现

拓展阅读 第一节 从零开始手写 mybatis(一)MVP 版本。 第二节 从零开始手写 mybatis(二)mybatis interceptor 插件机制详解 第三节 从零开始手写 mybatis(三)jdbc pool 从零实现数据库连接池 第四节 从…

react 综合题-旧版

一、组件基础 1. React 事件机制 javascript 复制代码<div onClick{this.handleClick.bind(this)}>点我</div> React并不是将click事件绑定到了div的真实DOM上&#xff0c;而是在document处监听了所有的事件&#xff0c;当事件发生并且冒泡到document处的时候&a…

print(response, flush=True),这里面的flush是什么意思

print(response, flushTrue),这里面的flush是什么意思 在Python中&#xff0c;flushTrue 参数用于强制立即刷新输出缓冲区。当你打印&#xff08;使用 print 函数&#xff09;内容到控制台时&#xff0c;Python 通常会将输出缓存起来&#xff0c;并在缓冲区满或者遇到换行符时…

淘宝怎么把好评改为差评

在淘宝购物时&#xff0c;我们有时会因为某些原因想要将原来的好评改为差评。本文将为您介绍如何将淘宝的好评改为差评。 一、登录淘宝账号 首先&#xff0c;您需要使用淘宝账号登录到淘宝平台。确保您已经登录到自己的账号。 二、找到订单并进入评价管理页面 1. 进入“我的…

Facebook:连接世界的社交巨人

在当今数字化时代&#xff0c;Facebook作为全球最大的社交媒体平台之一&#xff0c;扮演着连接世界的重要角色。它不仅仅是一个社交网站&#xff0c;更是一个数字化的社交生态系统&#xff0c;影响着全球数十亿用户的生活和交流方式。本文将深入探讨Facebook的起源、用户规模和…

FreeRTOS操作系统学习——资源管理

资源管理 在实际的项目中&#xff0c;我们总是会遇到一些临界资源的访问&#xff0c;虽然可以用互斥量实现对资源的互斥访问&#xff0c;但是如果要实现安全的独占式单线程地访问临界资源&#xff0c;可以通过屏蔽/使能中断、暂停/恢复调度器等方法实现。 屏蔽中断 屏蔽中断…

uniapp——第1篇:基于vue语法的、比原生开发屌的小程序开发

前提&#xff0c;建议先学会前端几大基础&#xff1a;HTML、CSS、JS、Ajax&#xff0c;还有一定要会Vue!&#xff08;Vue2\Vue3&#xff09;都要会&#xff01;&#xff01;&#xff01;不然不好懂 博主作为大二前端小白&#xff0c;刚刚接触前端微信小程序开发时选择的是基于“…

electron + vtkjs加载模型异常,界面显示类似于图片加载失败的图标

electron vtkjs加载模型显示异常&#xff0c;类似于图片加载失败的效果&#xff0c;如上图。 electron开发版本&#xff1a;13。 解决方法&#xff1a;升级electron版本号。 注意&#xff1a;win7最高兼容electron 22版本。