从动态嵌套的YAML中获取已知键的值

huangapple go评论138阅读模式
英文:

Get a value from a known key in a dynamic nested YAML

问题

我对Golang还不太熟悉。我有一个包含动态键的YAML文件,但只有一个键是已知的,并且它不一定是第一个键(在config之后)。在config级别下没有其他键

YAML文件如下:

config:
    foo:bar: baz
    bar:foo: baz
    abs:getit: myvalue

我想从嵌套键config:abs:getit中获取myvalue。这个嵌套键的名称永远不会改变,它始终是config:abs:getit。所有其他键可以是任意的,我们不关心,可以是不同类型的内容(数组、整数、字符串、数组的数组)。

最佳的方法是什么来获取这个值?

我尝试过使用yaml包,但我必须修复结构体中的每个字段才能进行解组,但我不知道可能有多少层嵌套键,所以无法编写一个始终有效的结构体。

我尝试过使用map,但我无法确定要使用哪个map,因为在我搜索的值之前,我可能有一个包含6个嵌套键或3个嵌套键和数组的字段,这将导致失败。

在这种动态环境中,我感到非常迷茫。

理想情况下,我想做类似于cat myFile.yaml | yq '.config."abs:getit"'的操作,但是在Golang中...

有什么想法和最佳实践来实现这个?

英文:

I'm pretty new to Golang. I have a YAML file with dynamic keys but only one is known, and it's not necessarily the first one (after config). There is no other key at the level of config.

The yaml file :

config:
    foo:bar: baz
    bar:foo: baz
    abs:getit: myvalue

I want to retrieve myvalue from the nested key config:abs:getit. This nested key name will never change, it will always be config:abs:getit. All other keys can be whatever, we don't care, with different types of content (arrays, int, strings, array of array).

What is the best way to recover the value ?

I worked with yaml package, but I have to fix every field in a struct to unmarshall it, but I don't know how many nested keys there can be so I cannot write a struct which works all the time.

I worked with a map, but I can figure out which map I have to use, because if I can have a field with 6 nested keys or 3 nested keys with array in it before the value I'm searching and it will fails.

I am pretty lost with those kind of things in a dynamic context.

Ideally, I want to do a cat myFile.yaml | yq '.config."abs:getit"', but in Golang...

Any ideas and best practices to do that ?

答案1

得分: 1

你可以这样做:

func main() {
	var obj struct {
		Config struct {
			AbsGetit string `yaml:"abs:getit"`
		} `yaml:"config"`
	}
	err := yaml.Unmarshal(data, &obj)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%q\n", obj.Config.AbsGetit)
}

链接:https://go.dev/play/p/KJ_lzZxaZBy

英文:

You can do:

func main() {
	var obj struct {
		Config struct {
			AbsGetit string `yaml:"abs:getit"`
		} `yaml:"config"`
	}
	err := yaml.Unmarshal(data, &obj)
	if err != nil {
		panic(err)
	}
	fmt.Printf("%q\n", obj.Config.AbsGetit)
}

https://go.dev/play/p/KJ_lzZxaZBy

答案2

得分: 1

我认为你需要这段代码。只需将正确的路径放入你的"myFile.yaml"文件中。在main()函数中,你将看到两个不同的示例,根据你的需求来使用代码。

getConfVal函数可以在N维深度的YAML树中找到一个带有任意序列的节点。如果节点不存在,值将为nil。

以下是myFile.yaml的内容:

config:
    foo:bar: baz
    bar:foo: baz
    abs:getit: myvalue
    foo:
      bar: "conf-foo-bar"
    bar:
      foo: "conf-bar-foo"
    abs:
      getit: "conf-abs-getit"
one:
  two:
    three:
      four:
        five: 5
        five2: [4, 7]

以下是代码:

package main

import (
	"fmt"
	"os"

	yaml "gopkg.in/yaml.v3"
)

func main() {
	if err := readTConf("./path/to/myFile.yaml", &cfg); err != nil {
		fmt.Printf("Read YAML Conf: %v\n", err)
		return
	}

	// 例如:
	getConfVal(cfg, []string{"foo:bar"})
	// getConfVal(cfg, []string{"foo", "bar"})
	// getConfVal(cfg, []string{"four", "five"})
	// getConfVal(cfg, []string{"four", "five2"})

	fmt.Printf("\n这是你要找的结果。(%v)\n", needleRes)
}

var needleRes interface{}
var cfg map[string]interface{}

func getConfVal(o map[string]interface{}, ns []string) (map[string]interface{}, bool) {
	nsCnt := len(ns)
	for kn, vn := range ns {
		for ko, vo := range o {
			if fmt.Sprintf("%T", vo) == "map[string]interface {}" {
				res, ok := getConfVal(vo.(map[string]interface{}), ns)
				if ok {
					return res, true
					break
				}
			}

			if fmt.Sprintf("%T", vo) == "string" {
				if ko == vn {
					if kn+1 == nsCnt {
						needleRes = vo
						return map[string]interface{}{}, true
					}
				}
			}
		}
	}

	return map[string]interface{}{}, false
}

func readTConf(f string, c *map[string]interface{}) error {
	yamlFile, err := os.ReadFile(f)
	if err != nil {
		return err
	}

	if err := yaml.Unmarshal([]byte(yamlFile), &c); err != nil {
		return err
	}

	return nil
}

希望对你有帮助!

英文:

I think you need this code. Just put the correct path to your "myFile.yaml" file. In main() function, you will see two different examples of how to use the code according to your needs.

getConfVal finds a node of a YAML tree with an arbitrary sequence in N-dimensional depth. If the node does not exist, the value will be nil.

myFile.yaml

config:
foo:bar: baz
bar:foo: baz
abs:getit: myvalue
foo:
bar: "conf-foo-bar"
bar:
foo: "conf-bar-foo"
abs:
getit: "conf-abs-getit"
one:
two:
three:
four:
five: 5
five2: [4, 7]

package main
import (
"fmt"
"os"
yaml "gopkg.in/yaml.v3"
)
func main() {
if err := readTConf("./path/to/myFile.yaml", &cfg); err != nil {
fmt.Printf("Read YAML Conf: %v\n", err)
return
}
// e.g.
getConfVal(cfg, []string{"foo:bar"})
// getConfVal(cfg, []string{"foo", "bar"})
// getConfVal(cfg, []string{"four", "five"})
// getConfVal(cfg, []string{"four", "five2"})
fmt.Printf("\nThis is the result you are looking for. (%v)\n", needleRes)
}
var needleRes interface{}
var cfg map[string]interface{}
func getConfVal(o map[string]interface{}, ns []string) (map[string]interface{}, bool) {
nsCnt := len(ns)
for kn, vn := range ns {
for ko, vo := range o {
if fmt.Sprintf("%T", vo) == "map[string]interface {}" {
res, ok := getConfVal(vo.(map[string]interface{}), ns)
if ok {
return res, true
break
}
}
if fmt.Sprintf("%T", vo) == "string" {
if ko == vn {
if kn+1 == nsCnt {
needleRes = vo
return map[string]interface{}{}, true
}
}
}
}
}
return map[string]interface{}{}, false
}
func readTConf(f string, c *map[string]interface{}) error {
yamlFile, err := os.ReadFile(f)
if err != nil {
return err
}
if err := yaml.Unmarshal([]byte(yamlFile), &c); err != nil {
return err
}
return nil
}

答案3

得分: 0

非常感谢您的准确回答。很抱歉,问题中有一个错误,对此我深感抱歉。

这不是一个单一的流量标量Yaml,而是一个映射,因为在值之前有一个空格:

config:
    foo:bar: baz
    bar:foo: baz
    abs:getit: myvalue

上面的代码逻辑上会返回一个转换错误,如下所示:

panic: yaml: unmarshal errors:
line 2: cannot unmarshal !!map into string

我的整个代码在这里。我读取的文件是一个Pulumi配置Yaml,对于所有项目来说都是不同的,除了一个共同的键("abs:getit:"),只有值不同。

原始问题文件已经被修改。真的很抱歉...

英文:

Thank you for your precise answer. I'm sorry but there is an error in the question, and I apologize for the mistake.

It's not a single flow scalar Yaml but a map, since there is space before the value :

config:
    foo:bar: baz
    bar:foo: baz
    abs:getit: myvalue

The code above logically returns a conversion error like this :

panic: yaml: unmarshal errors:
line 2: cannot unmarshal !!map into string

My whole code is here. The file I read is a Pulumi config Yaml, which will be different for all projects, except for one common key ("abs:getit:"), only the value is different.

The original question file has been modified. Really sorry for that...

huangapple
  • 本文由 发表于 2023年1月30日 18:42:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75283258.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定