在Go中解析和生成YAML

947 阅读7分钟

在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-yamlmapstructure 文档;这两个包都有不同的方法或接口,可以使特定的使用情况更容易,而本文的代码将是 "硬道理"。

希望你觉得这篇文章对你有用!