在Go中解析和生成YAML
速成课程
让我们来搞搞YAML吧!
本文以一个非常基本的程序为例,提供了一个在Go中解析和创建YAML的速成课程。
我不知道你怎么想的,但是当我听到 "marshal "和 "unmarshal "这样的术语时,我想到的最后一件事就是把YAML转换为结构体。这就是我们生活的世界;命名是困难的,只要有机会,我们总是会让事情听起来比实际情况更特别。
好消息是,有一套(相对)小的工具可以帮助你解析现有的YAML文件(unmarshal)或将现有的Go结构(或任何类型,真的)转换成YAML(marshal)。而另一个好消息是,当解释为使其简单时,它是简单的。
从Go结构到YAML
本文假设您已经在您的机器上安装并设置了Go。如果您在这方面需要帮助,请前往本指南,之后再来。
我们来创建一个基本项目。
mkdir converttoyamlcd converttoyamlgo mod init converttoyaml/v1
接下来,让我们创建一个名为main.go的新文件并添加以下内容。
一段简单的代码;创建一个结构,添加一些数据,然后将其打印出来。一切顺利,对吗?
现在,让我们假设这是一个关键的信息,我们希望以YAML格式保存。为了实现这一目标,我们将使用go-yaml包。
首先,我们需要安装它。
go get gopkg.in/yaml.v3
接下来,我们需要更新我们的代码,以导入该包,然后将其用于工作。我们将更新我们的导入行从。
import “fmt”
到(如果我们有错误,我们将使用log.fatal)。
import ( "fmt" "log" "gopkg.in/yaml.v3")
然后我们将更新我们的主函数。
在这里,我们对go-yaml包的要求是将我们的结构转化为YAML输出。请注意,这里的输出是以*[]字节的形式返回的;我们需要在fmt*打印结果之前将其转换为字符串。
因此,更新你的文件,重新运行该代码,你会看到如下的输出。
topspeed: 60name: Mirthmobilecool: truepassengers:- garth- wayne
漂亮的YAML!
从YAML到Go结构
现在,Mirthmobile的秘密已经被记录在YAML中,我们需要将这些数据以结构体的形式装回Go中。首先,让我们把程序的输出写入一个文件。
go run ./main.go > themirth.yaml
为了将YAML文件的内容转换回我们的汽车结构,我们需要用go-yaml包的一些说明来注释我们结构的定义。为此,我们需要为每个结构字段指定输入键名。
这些标签使我们能够定义YAML中的键名与结构中相应字段名的映射。当YAML文件被 "解密"(即转换为结构)时,这些标签被用来正确填充结构。解除marshal文件的代码是这样的(同样是对我们主函数的更新)。
来吧,复制并粘贴这些内容然后重新运行程序,你会得到这样的输出。
{TopSpeed:60 Name:Mirthmobile Cool:true Passengers:[garth wayne]}
不错的结构!
在YAML到Go结构中处理多种数据格式
在一个完美的世界里,你只有一种特定格式的输入数据,结构字段与YAML键的静态映射就能正常工作。但由于这是一个真实的世界,这种情况并不总是会发生。幸运的是,go-yaml 包允许我们 "自带 "UnmarshalYAML 函数。
我将使用的场景是passengers 领域。有时加思和韦恩一起骑马,但当两人为是否允许 "吸割 "的创作者回来参加节目而争吵时,他们可能不想花时间在一起。在这种情况下,Garth不希望必须提供一个数组的名字,他希望使用一个字符串。
即,这个
topspeed: 60name: Mirthmobilecool: truepassengers: garth
而不是这个
topspeed: 60name: Mirthmobilecool: truepassengers:- garth
来吧,用原样的代码试试;你会得到一个不错的回应。
unmarshal errors:line 4: cannot unmarshal !!str `garth` into []string
是的。这是有道理的;现在的数据类型已经完全改变了。然而,我们想支持这一点,一个自定义的UnmarshalYAML 函数是一个答案。
我们要在我们的导入中添加一个新的项目(错误)。
import ( "errors" "fmt" "log" "os"
接下来,我们将添加我们的自定义UnmarshalYAML 函数。
好的,那么这里发生了什么。首先,当向我们的结构添加UnmarshalYAML方法时,我们告诉go-yaml 包跳过它的内部魔法,调用我们的函数来解析数据--所以我们必须比以前更努力工作。
该函数中第一个有趣的部分是unmarshal函数本身。这个函数将我们的YAML转换为一个map[string]interface{} 。 然后我们可以将这个新值的内容(存储在carDetails变量中)解包。
对于我们想要设置的每个字段,我们需要测试该字段是否实际存在,然后如果它存在,我们要获得该值。现在,每个值都是*interface{}*类型的,所以我们必须将其转换为结构中要求的数据类型。
一个简单的警告,在Go中进行转换时,你绝对需要进行错误处理。任何没有投到正确数据类型的失败都会导致恐慌。为了简洁起见,我在这里省略了这一点。
一旦我们检查了我们的特殊乘客的字段并验证了我们有数据;下一步是使用一个开关来确定我们所提供的数据类型。我们在这里的目标是取一个字符串列表或一个单一的字符串,并填充结构上的Passengers 字段--这是一个字符串的片断。
在示例代码中,如果提供的数据是任何类型的片断,我们遍历这些数据,将其转换为字符串,并将其作为乘客字段的成员。如果我们只被传递了一个字符串,我们会给乘客字段分配一个新的字符串片断,其中只有我们的单一值。这样做,如果Garth只传入一个单一的字符串值或一个字符串列表(你知道,当Wayne在身边时)并不重要--我们的代码可以解析它并给我们正确的值。
继续并重新运行程序;事情看起来好多了,是吗?
在YAML到Go结构中处理多种数据格式(更简单的方法)
所以,在上一个例子中,你想:"这可真够多的"--你是对的。幸运的是,在另一个包的帮助下,我们可以更容易地做到这一点;mapstructure。除了帮助我们带来像Terraform这样美妙的东西之外,mitchellh还为我们带来了mapstructure 。mapstructure包是一个更灵活的解析器,它可以处理我们之前所做的强制行为,而不需要太多的额外代码。
首先,让我们安装mapstructure 。
go get github.com/mitchellh/mapstructure
现在,在main.go文件中,我们可以删除整个UnmarshalYAML 函数;我们不再需要它了。我们确实需要稍微更新我们的汽车定义(将标签从yaml 改为mapstructure )。
最后,我们将有一个最后版本的main 函数。
让我们回顾一下这里发生了什么。
- 我们不是直接将我们的输入数据解密到
Car,而是解密到一个interface{} (variable raw)。这意味着我们还没有尝试转换所有的数据类型,这样做的结果只是 "掩盖 "变量raw是一个。map[string]interface{} - 我们正在创建一个新的
mapstructure解码器,设置它的配置以将结果发送到我们的c 变量(类型为Car),我们还通知mapstructure使用'WeaklyTypedInput'。这是关键;启用它将自动完成从单个字符串到字符串片断的转换,我们必须在我们的自定义解读函数中完成这一转换 - 我们在新的解码器上调用
Decode方法,它将我们的原始输入转换为具有正确数据类型的Car结构。
来吧,重新运行你的代码!
mapstructure 包能够做的工作比这里所涉及的要多得多,但即使在这个简单的例子中,它也能使处理输入的YAML(或任何格式)变得更加简单
结语
我们在这里讲了很多,希望你对如何在YAML和Go类型之间来回移动有了基本的了解。值得回顾一下go-yaml 和mapstructure 文档;这两个包都有不同的方法或接口,可以使特定的使用情况更容易,而本文的代码将是 "硬道理"。
希望你觉得这篇文章对你有用!