【问题标题】:How to update value of two struct efficiently如何有效地更新两个结构的值
【发布时间】:2019-01-19 01:20:25
【问题描述】:

我有以下代码解析 YAML 文件,需要匹配来自一个结构 external 的值并更新 internal 结构的 type 属性。

例如这是yaml文件(为了简单起见翻译成bin)和正确解析的内容

package main

import (
    "fmt"
    "gopkg.in/yaml.v2"
    "log"
)

//internal config model for parsing

type InternalModel struct {
    models []Model2 `yaml:"models"`
}

type Model2 struct {
    Name   string `yaml:"name"`
    Type   string `yaml:"type"`
    Target string `yaml:"target"`
}

var internal_config = []byte(`
 models:
   - name: myapp
     type: app1
     target: ./

   - name: myapp2
     type: app2
     target: ./
`)

type ExternalConfig struct {
    Landscape Zone `yaml:"Landscape"`
}

type Zone struct {
    Zone   string  `yaml:"zone"`
    Models []Model `yaml:"models"`
}

type Model struct {
    AppType     string `yaml:"app-type"`
    ServiceType string `yaml:"service-type"`
}


var external_config = []byte(`
Landscape:
  zone: zone1
  models:
    - app-type: app1
      service-type: GCP
    - app-type: app2
      service-type: AMAZON
  zone: zone2
  models:
    - app-type: app3
      service-type: AZURE
    - app-type: app4Í
      service-type: HEROKU
`)

func main() {

    // This is the internal config which needs updated

    internalConfiglYaml := InternalModel{}

    err := yaml.Unmarshal([]byte(internal_config), &internalConfiglYaml)
    if err != nil {
        log.Fatalf("error in model internalConfiglYaml: %v", err)
    }
    //fmt.Printf("%+v\n", internalConfiglYaml)

    //--------------------------Second config file-----------------------//
    //This is the external config yaml

    extConfigYaml := ExternalConfig{}

    err = yaml.Unmarshal([]byte(external_config), &extConfigYaml)
    if err != nil {
        log.Fatalf("error in model extConfigYaml: %v", err)
    }
    fmt.Printf("%+v\n", extConfigYaml)

    landscape := "zone1"

    modifiedConfig := ConvertTypes(internalConfiglYaml, extConfigYaml, landscape)

    fmt.Printf("%+v\n", modifiedConfig)

}

func ConvertTypes(int_cfg InternalModel, ext_config ExternalConfig, landscape string) (out_cfg InternalModel) {

    for _, module := range int_cfg.models {

        switch module.Type {
        case "app1":
            //here I hard-coded the value "GCP" but it should come from the yaml struct after parsing
            module.Type = "GCP" // should be something like ext_config.models.service-type when the key in the struct
        case "app2":
            //here I hard-coded the value "AMAZON" but it should come from the yaml struct after parsing
            module.Type = "AMAZON"

        }
    }

    return int_cfg
}

//At the end what I need to do is to get the internal yaml file to be changed to the following struct
//The changes are when the type=app-type I need to modify the type in the internal config, here its GCP and ruby

//internal_config_after_changes := []byte(`
//
//
//models:
// - name: myapp
//   type: GCP
//   target: ./
//
// - name: myapp2
//   type: AMAZON
//   target: ./
//
//
//`)

最后我需要做的是将内部yaml文件更改为internal_config_after_changes上面的结构 变化是type=app-type我需要修改internal_config中的type值,这里从app1GCPapp2amazon

问题在于我应该使用第二个循环来迭代 external_config 和匹配值,我不知道如何以有效的方式将它们结合起来......

【问题讨论】:

  • 我认为您“没有很好地解释自己”,因为我重新阅读了您的问题,如果我没有回答,那么我不知道您想知道什么。跨度>
  • @ZanLynx 请看我的更新,现在更清楚了吗?
  • @JhonD 所以实际上你想根据你在开关右边定义的类型更新 intconfig 的值。
  • @Himanshu - 是的,确切地说,它应该像internal_config_after_changes一样在末尾,并且应该从external_config yaml文件内容中获取值,当有像app1这样的值匹配时跨度>
  • @JhonD 在ConvertType 函数中使用切片索引来获取底层数组的变化来检查答案。

标签: loops go recursion struct yaml


【解决方案1】:

Golang FAQ 描述了关于指向映射和切片的指针:

Map 和 slice 值的行为类似于指针:它们是描述符 包含指向底层映射或切片数据的指针。复制地图或 切片值不会复制它指向的数据。复制界面 value 复制存储在接口 value 中的事物。如果 接口值包含一个结构,复制接口值会生成一个 结构的副本。如果接口值持有一个指针,则复制 接口值复制了指针,但又不是 它指向的数据。

在遍历ConvertType 中的模型切片时,实际上是在创建[]Models 切片的副本,其 value.Type 由于这个原因不会改变原始结构的值。

for _, module := range int_cfg.models{}

上面的代码 sn-p 正在创建int_cfg.models{} 的副本。

索引切片模型以指向切片模型的确切底层数组以将值更改为:

package main

import (
    "fmt"
    "log"
    "strings"

    "gopkg.in/yaml.v2"
)

//internal config model for parsing

type InternalModel struct {
    Models []Model2 `yaml:"models"`
}

type Model2 struct {
    Name   string `yaml:"name"`
    Type   string `yaml:"type"`
    Target string `yaml:"target"`
}

var internal_config = []byte(`
  models:
    - name: myapp
      type: app1
      target: ./

    - name: myapp2
      type: app2
      target: ./
`)

type ExternalConfig struct {
    Landscape []Zone `yaml:"Landscape"`
}

type Zone struct {
    Zone   string  `yaml:"zone"`
    Models []Model `yaml:"models"`
}

type Model struct {
    AppType     string `yaml:"app-type"`
    ServiceType string `yaml:"service-type"`
}

var external_config = []byte(`
Landscape:
  - zone: zone1
    models:
     - app-type: app1
       service-type: GCP
     - app-type: app2
       service-type: AMAZON
  - zone: zone2
    models:
     - app-type: app3
       service-type: AZURE
     - app-type: app4Í
       service-type: HEROKU
`)

func main() {

    //This is the internal config which needs to be update

    internalConfiglYaml := InternalModel{}

    err := yaml.Unmarshal(internal_config, &internalConfiglYaml)
    if err != nil {
        log.Fatalf("error in model internalConfiglYaml: %v", err)
    }
    fmt.Printf("%+v\n", internalConfiglYaml)

    //--------------------------Second config file-----------------------//
    //This is the external config yaml

    extConfigYaml := ExternalConfig{}
    // var response interface{}

    err = yaml.Unmarshal(external_config, &extConfigYaml)
    if err != nil {
        log.Fatalf("error in model extConfigYaml: %v", err)
    }
    fmt.Printf("%+v\n", extConfigYaml)

    landscape := "zone1"

    modifiedConfig := ConvertTypes(&internalConfiglYaml, extConfigYaml, landscape)

    fmt.Printf("%+v\n", modifiedConfig)

}

// ConvertTypes for changing the intConfig struct types
func ConvertTypes(int_cfg *InternalModel, ext_config ExternalConfig, landscape string) (out_cfg *InternalModel) {

    for _, module := range ext_config.Landscape {
        if module.Zone == landscape {
            for i, value := range module.Models {
                switch strings.Compare(value.AppType, int_cfg.Models[i].Type) {
                case 0:
                    //here I hard-coded the value "GCP" but it should come from the yaml struct after parsing
                    int_cfg.Models[i].Type = value.ServiceType // should be something like ext_config.models.service-type when the key in the struct
                default:
                }
            }
        }
    }

    return int_cfg
}

如果你检查上面的代码sn-p,你也会发现我已经改变了结构。

type InternalModel struct {
    models []Model2 `yaml:"models"`
}

将第一个字母大写以使其可导出为:

type InternalModel struct {
    Models []Model2 `yaml:"models"`
}

由于结构 InternalModel 是不可导出的字段,model 无法解析提供的 internal_config yaml,这导致解组 yaml 后的 []slice 数据为空。

我注意到的另一件事是您再次将字节转换为字节。没有必要。

err := yaml.Unmarshal([]byte(internal_config), &internalConfiglYaml)

所以我把它改成了:

err := yaml.Unmarshal(internal_config, &internalConfiglYaml)

由于internal_config 已在全局变量中使用[]byte 声明为字节。

【讨论】:

  • 对不起,我不明白你是怎么解决这个问题的?需要修改的属性值仍然是硬编码的,如GCPamazon,当匹配时应该来自外部配置
  • 查看我的 cmets 在 switch 中,我已经硬编码了这个值,但是当有匹配时它应该来自extrnal_config yaml 文件
  • @JhonD 我已经解决了您无法更改 internalConfig 结构值的问题。从 app1 到 GCP
  • @JhonD 我正在通过循环外部配置来更新我的代码。稍等片刻。
  • 当然这是真正的问题 :),慢慢来,我需要离开办公室并在几个小时后再次连接,谢谢。请它应该尽可能高效
【解决方案2】:

首先要考虑的是您正在使用在运行时定义的两个结构,因此您将无法使用单个循环来匹配它们。接下来要考虑的是,您的 InternalConfig 是最不可能更改的结构,因此应该是第一个要解析的结构,在 ExternalConfig 被解析并与之匹配之后。

从阅读您的问题、cmets 和提供的答案来看,董事会似乎也有许多假设,但很少有人提到。如果可能,我会尝试解决这些问题。

让我们试一试吧:

package main

import (
    // import standard library packages first (alphabetically)
    "fmt"
    "log"

    // import third party packages next (also alphabetically)
    "gopkg.in/yaml.v2"

    // finally import your own packages here
)

// InternalConfig
//   - Let's call it InternalConfig so as to ensure consistent naming standards
type InternalConfig struct {
    Models []Imodel `yaml:"models"`
    // Exported to ensure access to external packages (such as yaml)
}

// Imodel - The internal model struct
type Imodel struct {
    Name   string `yaml:"name"`
    Type   string `yaml:"type"`
    Target string `yaml:"target"`
}

var iCfgb = []byte(`
models:
   - name: myapp
     type: app1
     target: ./

   - name: myapp2
     type: app2
     target: ./
`)

type ExternalConfig struct {
    Landscape Zone `yaml:"Landscape"`
}

type Zone struct {
    Zone   string  `yaml:"zone"`
    Models []Emodel `yaml:"models"`
}

type Emodel struct {
    AppType     string `yaml:"app-type"`
    ServiceType string `yaml:"service-type"`
}


var eCfgb = []byte(`
Landscape:
  zone: zone1
  models:
    - app-type: app1
      service-type: GCP
    - app-type: app2
      service-type: AMAZON
  zone: zone2
  models:
    - app-type: app3
      service-type: AZURE
    - app-type: app4Í
      service-type: HEROKU
`)

function main() {
    iCfgYaml := InternalConfig{}
    // don't need []byte(iCfgb) as iCfgb is already a byte slice
    // We are also using a shorthand if statement as we will only use err in the
    // if scope, this is because our config is passed by reference and not value
    if err := yaml.Unmarshal(iCfgb, &iCfgYaml); err != nil {
        log.Fatalf("error in model InternalConfig: %v", err)
    }

    eCfgYaml := ExternalConfig{}
    if err := yaml.Unmarshal(eCfgb, &eCfgYaml); err != nil {
        log.Fatalf("error in model ExternalConfig: %v", err)
    }

    // From here there are clearly some assumptions made, here is my take:
    //   1. I do not see any purpose in the landscape variable, the value "zone1"
    //      can be obtained from eCfgYaml.Landscape.Zone;
    //   2. From the question it seems you want to update the InternalConfig
    //      values with values from the ExternalConfig. However, from the code you
    //      created a modified copy. I will assume this was for debugging purposes
    //      and skip the creating of another copy;

    // I have renamed ConvertTypes to UpdateInternalState to accurately reflect
    // the behavior of the function. It now returns an error instead of a copy of
    // InternalConfig (any state altering functions should most likely return an
    // error). It receives a pointer of InternalConfig and a value copy of
    // ExternalConfig. This is because we will be altering the state of
    // InternalConfig
    if err := UpdateInternalState(&iCfgYaml, eCfgConfig); err != nil {
        log.Fatalf("Error updating internal config: %v", err)
    }

    // Assume the print is for debugging, thus I denote it with DBG. Makes it easy
    // to find and remove from production code.
    fmt.Printf("DBG: %+v\n", iCfgYaml)
}

func UpdateInternalState(iCfg *InternalConfig, eCfg ExternalConfig) error {
    // Firstly, we need to track the internal config models. We will create an
    // index lookup map (ilm) for it.
    ilm := make(map[string]int)

    // Now we iterate through our internal models and update the index:
    for i, v := range iCfg.Models {
        ilm[v] = i
    }

    // Now we iterate through the external config and update the internal config
    // state accordingly:
    for _, em := range eCfg.Landscape.Models {
        // We find the index of the specified app type in the index lookup
        // We use a shorthand if statement as we will only use i and ok within
        // the scope of the if (if you need it outside you can't use shorthand)
        // If there is no match nothing happens, but if found it will update to
        // the corresponding specified value in the external config.
        if i, ok := ilm[em.AppType]; ok {
            iCfg.Models[i].Type = em.ServiceType
        }
    }

    // At any point within this function you are able to do more robust error
    // checking, such as ensuring the index lookup map must have a corresponding
    // entry, and return any errors encountered.
    return nil
}

【讨论】:

    【解决方案3】:

    如果您正在寻找如何从 ExternalConfig 应用 app-typeservice-type 映射,我建议将数组转换为映射并使用它来查找映射:

    func ConvertTypes(intCfg *InternalModel, extCfg ExternalConfig, landscape string) {
        // Make an app-type to service-type map.
        appToSvc := make(map[string]string, len(extCfg.Landscape.Models))
        for _, m := range extCfg.Landscape.Models {
            appToSvc[m.AppType] = m.ServiceType
        }
    
        // For each InternalModel model, check if there's an app-type to service-type
        // mapping. If so, use the service-type instead of the app-type.
        for i, model := range intCfg.Models {
            if t, ok := appToSvc[model.Name]; ok {
                intCfg.Models[i].Type = t
            }
        }
    }
    

    我还修复了 @Himanshu 和 @Zan Lynx 建议的传递指针和范围循环分配问题。

    【讨论】:

    • 您好,您能详细说明一下fixed the pass-by-pointer and range loop assignment problems,吗?为什么像@Himanshu 建议的那样使用它不好
    【解决方案4】:

    这个问题肯定是重复的,但我现在找不到链接。

    您的问题是 for 循环正在创建每个结构的副本。写入element,这只是xtr[i] 的副本,永远不会更改原始xtr[i]。修复它的方法是改用切片索引。

    改变

    for _, element := range xtr {
        switch element.vType {
        case "app1":
            //user1 value is coming from the &y parsed data of the yaml
            element.vType = "user1"
        case "app2":
             //user2 value is coming from the &y parsed data
            element.vType = "user2"
    
        }
    }
    

    进入

    for i := range xtr {
        switch xtr[i].vType {
        case "app1":
            //user1 value is coming from the &y parsed data of the yaml
            xtr[i].vType = "user1"
        case "app2":
             //user2 value is coming from the &y parsed data
            xtr[i].vType = "user2"
    
        }
    }
    

    【讨论】:

    • 谢谢!我有两个结构,如何将 yaml parsed (byte) 的值匹配到 xtr 结构? user1 & user2 应该来自 &y 解析的数据,并且应该在 app1 的值在 both 结构中匹配时填充
    猜你喜欢
    • 1970-01-01
    • 2016-11-02
    • 2011-10-26
    • 1970-01-01
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    • 2022-01-07
    • 1970-01-01
    相关资源
    最近更新 更多