【问题标题】:Is there a better way to parse this Map?有没有更好的方法来解析这个地图?
【发布时间】:2016-10-05 02:16:04
【问题描述】:

对 Go 来说还是很陌生,基本上在我正在编写的实际代码中,我计划从一个包含环境变量的文件中读取,即API_KEY=XYZ。意味着我可以让它们不受版本控制。以下解决方案“有效”,但我觉得可能有更好的方法。

最终目标是能够像这样访问文件中的元素 m["API_KEY"] 会打印出XYZ。这甚至可能已经存在,我正在重新发明轮子,我看到 Go 有环境变量,但它似乎不是我专门追求的。

所以任何帮助表示赞赏。

Playground

代码:

package main

import (
    "fmt"
    "strings"
)

var m = make(map[string]string)

func main() {

    text := `Var1=Value1
    Var2=Value2
    Var3=Value3`

    arr := strings.Split(text, "\n")

    for _, value := range arr {
        tmp := strings.Split(value, "=")
        m[strings.TrimSpace(tmp[0])] = strings.TrimSpace(tmp[1])
    }

    fmt.Println(m)

}

【问题讨论】:

  • 如果它完成了这项工作,我就不用担心了。 Go 不是关于优雅的代码,而是关于工作代码......
  • 也就是说,我会在访问它之前测试if len(tmp) >= 2 (tmp[0]),因为这可能会失败。
  • @RickyA 非常好的一点,我实际上并没有真正在那里进行任何错误检查。将其添加到我的工作中,谢谢。

标签: dictionary go config


【解决方案1】:

首先,我建议阅读这个相关问题:How to handle configuration in Go

接下来,我真的会考虑以另一种格式存储您的配置。因为你提出的不是标准。它接近 Java's property file format (.properties),但即使是属性文件也可能包含 Unicode 序列,因此您的代码不是有效的 .properties 格式解析器,因为它根本不处理 Unicode 序列。

相反,我建议使用JSON,这样您就可以轻松地使用 Go 或任何其他语言对其进行解析,并且有许多工具可以编辑 JSON 文本,而且它仍然对人类友好。

使用 JSON 格式,将其解码为 map 只是一个函数调用:json.Unmarshal()。它可能看起来像这样:

text := `{"Var1":"Value1", "Var2":"Value2", "Var3":"Value3"}`

var m map[string]string
if err := json.Unmarshal([]byte(text), &m); err != nil {
    fmt.Println("Invalid config file:", err)
    return
}

fmt.Println(m)

输出(在Go Playground上试试):

map[Var1:Value1 Var2:Value2 Var3:Value3]

json 包将为您处理格式化和转义,因此您不必担心这些。它还将为您检测和报告错误。 JSON 更灵活,您的配置可能包含数字、文本、数组等。所有这些都是“免费”的,因为您选择了 JSON 格式。

另一种流行的配置格式是YAML,但 Go 标准库不包含 YAML 解析器。请参阅 Go 实现 github.com/go-yaml/yaml

如果您不想更改格式,那么我会使用您发布的代码,因为它完全按照您的要求执行:逐行处理输入,并解析 name = value 对从每一行。 它以一种清晰明了的方式做到了。为此目的使用 CSV 或任何其他阅读器是不好的,因为它们隐藏了引擎盖下的内容(它们有意且正确地隐藏了格式特定的细节和转换)。 CSV 阅读器 CSV 阅读器首先;即使您更改了制表符/逗号符号:它解释某些转义序列,并且可能会为您提供与您在纯文本编辑器中看到的不同的数据。从您的角度来看,这是一种无意的行为,但是,您的输入 不是 CSV 格式,但您要求读者将其解释为 CSV!

我要为您的解决方案添加的一项改进是使用bufio.Scanner。它可用于逐行读取输入,并处理不同样式的换行符序列。它可能看起来像这样:

text := `Var1=Value1
Var2=Value2
Var3=Value3`

scanner := bufio.NewScanner(strings.NewReader(text))

m := map[string]string{}
for scanner.Scan() {
    parts := strings.Split(scanner.Text(), "=")
    if len(parts) == 2 {
        m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
    }
}
if err := scanner.Err(); err != nil {
    fmt.Println("Error encountered:", err)
}

fmt.Println(m)

输出是一样的。在Go Playground 上试试吧。

使用bufio.Scanner 有另一个优势:bufio.NewScanner() 接受io.Reader,即“所有事物都是字节源”的通用接口。这意味着如果您的配置存储在一个文件中,您甚至不必将所有配置读入内存,您只需打开该文件,例如使用os.Open(),它返回一个值*os.File,它也实现io.Reader,所以你可以直接将*os.File值传递给bufio.NewScanner()(所以bufio.Scanner将从文件中读取,而不是从in -内存缓冲区,如上例所示)。

【讨论】:

  • 无法反驳,绝妙的答案。我选择明文格式的唯一原因主要是因为我看到它在其他语言中的处理方式。在我完全消化了您的答案后,将阅读您提到的另一个问题。不过谢谢:D
  • @Pigeon 进行了一些修改,使其更加有用/信息丰富。
【解决方案2】:

1- 您可以使用encoding/csv 中的csv.NewReader 只需一个函数调用r.ReadAll() 即可阅读所有内容:

r.Comma = '='
r.TrimLeadingSpace = true

结果是[][]string输入顺序被保留,试试The Go Playground

package main

import (
    "encoding/csv"
    "fmt"
    "strings"
)

func main() {
    text := `Var1=Value1
    Var2=Value2
    Var3=Value3`

    r := csv.NewReader(strings.NewReader(text))
    r.Comma = '='
    r.TrimLeadingSpace = true

    all, err := r.ReadAll()
    if err != nil {
        panic(err)
    }
    fmt.Println(all)
}

输出:

[[Var1 Value1] [Var2 Value2] [Var3 Value3]]

2- 您可以微调csv.ReadAll() 以将输出转换为地图,但不保留顺序,请在The Go Playground 上尝试:

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "strings"
)

func main() {
    text := `Var1=Value1
    Var2=Value2
    Var3=Value3`
    r := csv.NewReader(strings.NewReader(text))
    r.Comma = '='
    r.TrimLeadingSpace = true
    all, err := ReadAll(r)
    if err != nil {
        panic(err)
    }
    fmt.Println(all)
}

func ReadAll(r *csv.Reader) (map[string]string, error) {
    m := make(map[string]string)
    for {
        tmp, err := r.Read()
        if err == io.EOF {
            return m, nil
        }
        if err != nil {
            return nil, err
        }
        m[tmp[0]] = tmp[1]
    }
}

输出:

map[Var2:Value2 Var3:Value3 Var1:Value1]

【讨论】:

  • 这是一种有趣的方法,如果我还想用strings.TrimSpace() 修剪任何空白,是循环遍历元素并更新它们,还是有办法应用它到 csv 阅读器?这样我就可以避免[ Var2 Value2]
  • 看起来很有趣,看看有没有其他人有什么建议。如果不是,我稍后会接受这个答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-23
  • 2011-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多