Go 中的 Map[string]interface{}:用户手册

2021-07-27 02:49:46

Go 中的 map[string]interface{} 是什么,为什么它如此有用?我们如何在程序中处理字符串到接口的映射{}?接口{}到底是什么?让我们与著名的围棋老师约翰·阿伦德尔(John Arundel,又名 @bitfield)一起找出答案,他可能是最友好和最有帮助的围棋入门系列丛书 For the Love of Go 的作者。这是 Golang 地图教程系列的第 6 部分。查看该系列中的其他帖子:我们在本系列中讨论了许多不同类型的 Go 地图,但这里有一个您会经常遇到的:地图[字符串]接口{}。让我们来看看它的作用以及如何使用它。遵循我们这些教程的晚餐主题,或者可能引导 Ron Swanson,这里有一个 map[string]interface{} 文字示例:foods := map[string]interface{}{ "bacon": "delicious", "eggs" : struct { source string price float64 }{"chicken", 1.75}, "steak": true,} 如果您已阅读本系列地图类型的早期教程,您将立即知道如何阅读此代码。上面例子中foods变量的类型是一个map,键是字符串,值是interface{}类型。那是什么? Go 接口本身就值得一个教程系列,尽管它是那些看起来比实际复杂得多的主题之一;刚开始我们大多数人都有些陌生。这里只要说接口是一种引用值而不指定其类型的方式就足够了。相反,接口指定了它有哪些方法;例如,广泛使用的 io.Reader 接口类型告诉您该类型的值具有带有特定签名的 Read() 方法。

那么什么是接口{}?发音为“空接口”,它是根本不指定任何方法的接口! (请注意,这并不意味着 interface{} 值必须没有方法;它根本没有说明它们可能有或可能没有的方法。用 Go 谚语的话来说,interface{} 什么也没说.) 那么,如果 interface{} 没有告诉我们有关值的任何信息,它的意义何在?嗯,这正是它有用的原因:它可以指代任何东西!类型 interface{} 适用于任何值。声明为 interface{} 的变量可以保存一个字符串值、一个整数、任何类型的结构、一个指向 os.File 的指针,或者你能想到的任何东西。假设我们需要编写一个函数来打印传递给它的值,但我们事先不知道这个值是什么类型。这是 interface{} 的工作:同样,如果我们想要任何类型的事物的集合,每个事物都由一个字符串标识,这是一种组织任意数据的便捷方式,我们可以使用 map[string] 接口来实现{}。事实上,我们只是描述了 JSON 对象的模式,例如。拿这个原始的 JSON 数据: 暂时忽略明显是虚构的时代,我们可以看到这是一个由字符串键标识的事物的集合,但是什么样的事物呢?我们有一个字符串、一个整数和一个字符串数组。假设我们需要将其转换为 Go 结构体值,我们可以定义这样的类型:Great。但这需要我们提前知道对象的模式。如果有人给了我们任意的 JSON 数据,而我们需要将其解组为 Go 值怎么办?鉴于我们所知道的是它是字符串到任何类型对象的映射,我们怎么可能做到这一点?

假设我们有关于我的传记上有问题的 JSON 数据存储在一个名为 data 的变量中。我们如何将其解组为 Go 变量,以便我们可以开始查看它?该变量需要是什么类型?如果没有错误,p 变量现在包含我们的任意数据。成功!但是,鉴于我们对地图中每个值的类型一无所知,我们可以用它做什么?我们可以做的一件事是使用类型开关根据值的类型来做不同的事情。举个例子: for k, v := range p { switch c := v.(type) { case string: fmt.Printf("Item %q is a string, contains %q\n", k, c) case float64: fmt.Printf("看起来 item %q 是一个数字,特别是 %f\n", k, c) default: fmt.Printf("不确定 item %q 是什么类型,但我认为它可能是 % T\n", k, c) }} 特殊语法 switch c := v.(type) 告诉我们这是一个类型 switch,这意味着 Go 将尝试将 v 的类型与 switch 语句中的每个 case 匹配.例如,如果 v 是字符串,将执行第一种情况:在每种情况下,变量 c 接收 v 的值,但转换为相关类型。所以在字符串的情况下,c 将是字符串类型。您可能对整数值 29 被解组为 float64 感到困惑,但这是正常的。所有 JSON 数字都被 json.Unmarshal 视为 float64。它是 Go 中最通用的数字类型。

不确定项目“爱好”是什么类型,但我认为它可能是 []interface {} (格式说明符 %T 到 fmt.Printf 打印其值的类型,这有时很方便。在这种情况下我们可以看到“hobbies”的值是任意数据的切片,这是有道理的。)正如我们所见,当我们需要处理来自 Go 外部的数据时,“字符串到空接口的映射”类型非常有用世界;例如,未知模式的任意 JSON 数据。例如,许多 Web API 返回这样的数据。在编写 Terraform 提供程序时也非常常见,这是有道理的; Terraform 资源本质上也是字符串到任意数据的映射。它也是递归的; “任意数据”通常也是字符串到更多任意数据的映射。它一直是 map[string]interface{}!配置文件也通常具有这种模式。您可以将 YAML 或 CUE 文件视为字符串到空接口的映射,就像 JSON 一样。所以当我们处理任何类型的结构化数据时,我们会经常在 Go 程序中使用这种类型。 map[string]interface{} 就像那些通用旅行适配器之一,可以插入任何类型的插座并在任何电压下工作。您可以使用它来保护自己的易受攻击的程序免受奇怪的外来数据造成的损害。当不需要处理任意输入数据时,您是否应该在自己的程序中使用 map[string]interface{} 值?不,你不应该。虽然不必显式定义对象的模式看起来很方便,但这可能会导致各种问题。

一方面,众所周知 interface{} 什么也没说,每当我们处理这种类型的值时,我们必须使用保护性类型断言来防止恐慌:换句话说,编写安全、可靠的程序来操作要困难得多在这样的地图上。如果您的图书馆产生此类数据,则用户几乎不会喜欢您。相反,只需使用一个普通的旧结构,它启用编译时类型检查并且更方便处理。就像旅行适配器一样,当您在家时,map[string]interface{} 使用起来有点不稳定和笨拙,您可以依靠所有具有预期电压和引脚架构的插座。但是,当您与其他世界接触时,在 Go 类型系统温暖、安全的茧之外,map[string]interface{} 可能是终极旅行配件。好好利用吧!为了结束这个系列,我们将看看一堆关于 Go 地图的常见问题。如果你觉得这篇文章有用,并且你想了解更多关于 Go 的知识,那么我可以提供帮助!我在 Go 开发中提供一对一或小组指导,从初学者到经验丰富的 Gophers。查看我的通过指导页面学习 Golang 以了解更多信息,或发送电子邮件至 [email protected]。我还写了一系列名为 For the Love of Go 的友好小电子书,现在可以购买。