英文:
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)
}
答案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...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论