【问题标题】:Read and merge two Yaml files in go languagego语言读取并合并两个yaml文件
【发布时间】:2019-01-27 14:16:31
【问题描述】:

假设我们有两个 yaml 文件

master.yaml

someProperty: "someVaue"
anotherProperty: "anotherValue"

override.yaml

someProperty: "overriddenVaue"

是否可以解组、合并,然后将这些更改写入文件,而不必为 yaml 文件中的每个属性定义 struct

主文件中有超过 500 个属性,这些属性在执行时对服务来说根本不重要,所以理想情况下,我可以解组到地图中,进行合并并再次在 yaml 中写出但我比较新,所以想发表一些意见。

我有一些代码可以将 yaml 读入 interface,但我不确定将两者合并的最佳方法。

var masterYaml interface{}
yamlBytes, _ := ioutil.ReadFile("master.yaml")
yaml.Unmarshal(yamlBytes, &masterYaml)

var overrideYaml interface{}
yamlBytes, _ = ioutil.ReadFile("override.yaml")
yaml.Unmarshal(yamlBytes, &overrideYaml)

我已经研究过 mergo 这样的库,但我不确定这是否是正确的方法。

我希望在大师之后我能够写出带有属性的文件

someProperty: "overriddenVaue"
anotherProperty: "anotherValue"

【问题讨论】:

  • 我相信这是可能的。你试过什么?你遇到了什么问题?
  • 不定义结构是可能的,但我无法理解你真正想要合并哪些字段以及如何合并。
  • 你为什么不只使用你需要的字段的结构并跳过你不需要的字段,这将节省大量使用接口的工作。
  • 我添加了一些额外的信息。我不确定哪些字段可以被覆盖,它们是可以随意传递给我们的字段,这就是为什么我希望我们能够对此进行超级通用和抽象。
  • 这是一个非常有趣的问题,我已经发布了深度合并的答案stackoverflow.com/questions/51947879/…

标签: go merge yaml marshalling unmarshalling


【解决方案1】:

假设你只想在顶层合并,你可以解组成map[string]interface{}类型的maps,如下:

主包

import (
    "io/ioutil"

    "gopkg.in/yaml.v2"
)

func main() {
    var master map[string]interface{}
    bs, err := ioutil.ReadFile("master.yaml")
    if err != nil {
        panic(err)
    }
    if err := yaml.Unmarshal(bs, &master); err != nil {
        panic(err)
    }

    var override map[string]interface{}
    bs, err = ioutil.ReadFile("override.yaml")
    if err != nil {
        panic(err)
    }
    if err := yaml.Unmarshal(bs, &override); err != nil {
        panic(err)
    }

    for k, v := range override {
        master[k] = v
    }

    bs, err = yaml.Marshal(master)
    if err != nil {
        panic(err)
    }
    if err := ioutil.WriteFile("merged.yaml", bs, 0644); err != nil {
        panic(err)
    }
}

【讨论】:

  • 哦,快,map[string]interface{},然后作为地图进行迭代是一个完美的解决方案。干杯。
  • 这看起来有多深?在 Go 中是否推荐像 if reflect.ValueOf(masterYaml[k]).Kind() == reflect.Map 这样的方法?
  • 我不推荐反射,但你可以做if m, ok := v.(map[interface{}]interface{}); ok { ...之类的事情,并特别对待地图的情况。
  • 这不处理嵌套合并
【解决方案2】:

对于更广泛的解决方案(具有 n 个输入文件),您可以使用此功能。我已经使用@robox 回答来解决我的问题:

func ReadValues(filenames ...string) (string, error) {
    if len(filenames) <= 0 {
        return "", errors.New("You must provide at least one filename for reading Values")
    }
    var resultValues map[string]interface{}
    for _, filename := range filenames {

        var override map[string]interface{}
        bs, err := ioutil.ReadFile(filename)
        if err != nil {
            log.Info(err)
            continue
        }
        if err := yaml.Unmarshal(bs, &override); err != nil {
            log.Info(err)
            continue
        }

        //check if is nil. This will only happen for the first filename
        if resultValues == nil {
            resultValues = override
        } else {
            for k, v := range override {
                resultValues[k] = v
            }
        }

    }
    bs, err := yaml.Marshal(resultValues)
    if err != nil {
        log.Info(err)
        return "", err
    }

    return string(bs), nil
}

所以对于这个例子,你应该用这个顺序调用它:

result, _ := ReadValues("master.yaml", "overwrite.yaml")

如果你有一个额外的文件newFile.yaml,你也可以使用这个函数:

result, _ := ReadValues("master.yaml", "overwrite.yaml", "newFile.yaml")

【讨论】:

    【解决方案3】:

    深度合并两个 YAML 文件

    package main
    
    import (
        "fmt"
        "io/ioutil"
        "sigs.k8s.io/yaml"
    )
    
    func main() {
    
        // declare two map to hold the yaml content
        base := map[string]interface{}{}
        currentMap := map[string]interface{}{}
    
        // read one yaml file 
        data, _ := ioutil.ReadFile("conf.yaml")
        if err := yaml.Unmarshal(data, &base); err != nil {
    
        }
    
        // read another yaml file
        data1, _ := ioutil.ReadFile("conf1.yaml")
        if err := yaml.Unmarshal(data1, &currentMap); err != nil {
    
        }
    
        // merge both yaml data recursively
        base = mergeMaps(base, currentMap)
    
        // print merged map
        fmt.Println(base)
    
    }
    
    func mergeMaps(a, b map[string]interface{}) map[string]interface{} {
        out := make(map[string]interface{}, len(a))
        for k, v := range a {
            out[k] = v
        }
        for k, v := range b {
            if v, ok := v.(map[string]interface{}); ok {
                if bv, ok := out[k]; ok {
                    if bv, ok := bv.(map[string]interface{}); ok {
                        out[k] = mergeMaps(bv, v)
                        continue
                    }
                }
            }
            out[k] = v
        }
        return out
    }
    

    【讨论】:

      猜你喜欢
      • 2021-04-22
      • 2018-05-05
      • 1970-01-01
      • 1970-01-01
      • 2016-06-13
      • 1970-01-01
      • 1970-01-01
      • 2022-12-31
      • 1970-01-01
      相关资源
      最近更新 更多