有没有更好的方法来解析此Map?

Fairly new to Go, essentially in the actual code I'm writing I plan to read from a file which will contain environment variables, i.e. API_KEY=XYZ. Means I can keep them out of Version control. The below solution 'works' but I feel like there is probably a better way of doing it.

The end goal is to be able to access the elements from the file like so m["API_KEY"] and that would print XYZ. This may even already exist and I'm re-inventing the wheel, I saw Go has environment variables but it didn't seem to be what I was after specifically.

So any help is appreciated.

Playground

Code:

package main

import (
    "fmt"
    "strings"
)

var m = make(map[string]string)

func main() {

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

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

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

    fmt.Println(m)

}

First, I would recommend to read this related question: How to handle configuration in Go

Next, I would really consider storing your configuration in another format. Because what you propose isn't a standard. It's close to Java's property file format (.properties), but even property files may contain Unicode sequences and thus your code is not a valid .properties format parser as it doesn't handle Unicode sequences at all.

Instead I would recommend to use JSON, so you can easily parse it with Go or with any other language, and there are many tools to edit JSON texts, and still it is human-friendly.

Going with the JSON format, decoding it into a map is just one function call: json.Unmarshal(). It could look like this:

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)

Output (try it on the Go Playground):

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

The json package will handle formatting and escaping for you, so you don't have to worry about any of those. It will also detect and report errors for you. Also JSON is more flexible, your config may contain numbers, texts, arrays, etc. All those come for "free" just because you chose the JSON format.

Another popular format for configuration is YAML, but the Go standard library does not include a YAML parser. See Go implementation github.com/go-yaml/yaml.

If you don't want to change your format, then I would just use the code you posted, because it does exactly what you want it to do: process input line-by-line, and parse a name = value pair from each line. And it does it in a clear and obvious way. Using a CSV or any other reader for this purpose is bad because they hide what's under the hood (they intentionally and rightfully hide format specific details and transformations). A CSV reader is a CSV reader first; even if you change the tabulator / comma symbol: it will interpret certain escape sequences and might give you different data than what you see in a plain text editor. This is an unintended behavior from your point of view, but hey, your input is not in CSV format and yet you asked a reader to interpret it as CSV!

One improvement I would add to your solution is the use of bufio.Scanner. It can be used to read an input line-by-line, and it handles different styles of newline sequences. It could look like this:

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)

Output is the same. Try it on the Go Playground.

Using bufio.Scanner has another advantage: bufio.NewScanner() accepts an io.Reader, the general interface for "all things being a source of bytes". This means if your config is stored in a file, you don't even have to read all the config into the memory, you can just open the file e.g. with os.Open() which returns a value of *os.File which also implements io.Reader, so you may directly pass the *os.File value to bufio.NewScanner() (and so the bufio.Scanner will read from the file and not from an in-memory buffer like in the example above).

1- You may read all with just one function call r.ReadAll() using csv.NewReader from encoding/csv with:

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

And result is [][]string, and input order is preserved, Try it on 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)
}

output:

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

2- You may fine-tune csv.ReadAll() to convert the output to the map, but the order is not preserved, try it on 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]
    }
}

output:

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