简体   繁体   English

使用 Golang 结构更新嵌套字段

[英]Update Nested fields using Golang struct

I am facing a issue with update struct fields using golang我面临使用 golang 更新结构字段的问题

As the var name suggest some current config fields should be updated with the requested config正如 var 名称所暗示的那样,一些当前的配置字段应该使用请求的配置进行更新

     currentConfig:=`
     {
         "field_one": "value",
         "data": {
             "field_two": [
                 "data1",
                 "data2"
             ],
             "field_three": "check",
             "field_four": 12
         },
        "field_five": [
                 "data1",
                 "data2"
             ],
        "data2": {
            "field_six":{
                "field_seven": 100
            }
        }
     }`
    
     
     updateRequest:=`
     {
         "data": {
             "field_three": "updated check" //ignore if same value exist (field_three exists in current config)
         },
        "field_five": ["data3"],    // append to current config
        "data2": {
            "field_six":{
                "field_eight": 300  // add value if doesnt exist to current
            }
        }
     }`
func main() {
    config := make(map[string]interface{})
    err := json.Unmarshal([]byte(currentConfig), &config)
    if err != nil {
        panic(err)
    }
    updateFields := make(map[string]interface{})
    err1 := json.Unmarshal([]byte(updateRequest), &updateFields)
    if err1 != nil {
        panic(err1)
    } 

    fmt.Println(config)
    updateFields = ParseJsonMap(updateFields, config)
    fmt.Println(updateFields)
}


func ParseJsonMap(aMap map[string]interface{}, finalMap map[string]interface{}) map[string]interface{} {
    parseMap("", aMap, &finalMap)
    return finalMap
}

Traverses the struct and updates the fields遍历结构并更新字段

func parseMap(k string, aMap map[string]interface{}, finalMap *map[string]interface{}) {
    if len(aMap) == 0 {
        (*finalMap)[k] = nil
        return
    }

    for key, val := range aMap {
        if val != nil {
            switch concreteVal := val.(type) {
            case map[string]interface{}:
                if _, ok := (*finalMap)[getKey(k, key)]; ok {
                    parseMap(getKey(k, key), val.(map[string]interface{}), finalMap)
                } else {
                    (*finalMap)[getKey(k, key)] = val
                }
            case []interface{}:
                res := val.([]interface{})
                if arr, ok := (*finalMap)[getKey(k, key)]; ok {
                    for _, valueIn := range res {
                        arr = append(arr.([]interface{}), valueIn)
                    }
                    (*finalMap)[getKey(k, key)] = arr
                } else {
                    (*finalMap)[getKey(k, key)] = res
                }
                
            default:
                concreteValType := reflect.TypeOf(concreteVal)
                if concreteValType.Kind() == reflect.Map {
                    parseMap(getKey(k, key), concreteVal.(map[string]interface{}), finalMap)
                } else {
                    if _, ok := (*finalMap)[getKey(k, key)]; !ok {
                        (*finalMap)[getKey(k, key)] = concreteVal
                    }

                }
            }
        } else {
            (*finalMap)[getKey(k, key)] = nil
        }
    }
}

func getKey(k string, key string) string {
    if k == "" {
        return key
    }

    return k + "." + key
}

Expected Result预期结果

map[data:map[field_four:12 field_three:check field_two:[data1 data2]] data2:map[field_six:map[field_eight:300 field_seven:100]] field_five:[data1 data2 data3] field_one:value]
{
         "field_one": "value",
         "data": {
             "field_two": [
                 "data1",
                 "data2"
             ],
             "field_three": "check",  //since key exist with data not updated
             "field_four": 12
         },
        "field_five": [
                 "data1",
                 "data2",
                 "data3"   //data 3 appended
             ],
        "data2": {
            "field_six":{
                "field_seven": 100,
                "field_eight": 300 //field is added
            }
        }
     }

Result got - created key at top level结果得到 - 在顶层创建密钥

map[data:map[field_four:12 field_three:check field_two:[data1 data2]] data.field_three:check changed data2:map[field_six:map[field_seven:100]] data2.field_six:map[field_eight:300] field_five:[data1 data2 data3] field_one:value]

Just want to know if this is supported, if yes can you help me with it and if any better approaches exist只是想知道这是否受支持,如果是,你能帮我吗,如果有更好的方法存在

It appears you're trying to overlay one map onto another map. This gets complicated if you don't want to apply this kind of update without altering an existing map. So it may be easier to separate the two steps:看来您正在尝试将一个 map 叠加到另一个 map 上。如果您不想在不更改现有 map 的情况下应用此类更新,这会变得很复杂。因此,将这两个步骤分开可能更容易:

  • Copy a map[string]interface{}复制一个map[string]interface{}
  • overlay one map[string]interface{} on top of another将一个map[string]interface{}叠加在另一个之上

func CopyMap(m map[string]interface{}) map[string]interface{} {
    cp := make(map[string]interface{})
    for k, v := range m {
        vm, ok := v.(map[string]interface{})
        if ok {
            cp[k] = CopyMap(vm)
        } else {
            cp[k] = v
        }
    }
    return cp
}

func overlay(dst, src map[string]interface{}) error {
    for k, v := range src {

        if _, ok := dst[k]; !ok {
            dst[k] = v // easy case - dst key does not exist
            continue
        }

        d, ok1 := dst[k].(map[string]interface{})
        s, ok2 := src[k].(map[string]interface{})

        if ok1 && ok2 {
            overlay(d, s) // merge case
        } else if !ok1 && !ok2 {
            dst[k] = v // non-map - so simple assignment/reassignment
        } else {
            return fmt.Errorf("incompatible update types") // map to non-map or vice-versa
        }

    }
    return nil
}

to use:使用:

err := json.Unmarshal([]byte(currentConfig), &config) // check err
err = json.Unmarshal([]byte(updateRequest), &updateFields) // check err
newconfig = CopyMap(config)
err = overlay(newconfig, updateFields) // check err

https://play.golang.org/p/RZPbkv19ChL https://play.golang.org/p/RZPbkv19ChL

Output: Output:

    config : map[data:map[field_four:12 field_three:check field_two:[data1 data2]] data2:map[field_six:map[field_eight:200 field_seven:100]] field_five:[data1 data2] field_one:value]
    update : map[data:map[field_three:check changed] data2:map[field_six:map[field_eight:300]] field_five:[data1]]
 newconfig : map[data:map[field_four:12 field_three:check changed field_two:[data1 data2]] data2:map[field_six:map[field_eight:300 field_seven:100]] field_five:[data1] field_one:value]

UPDATE: to handle appends rather than replacements for JSON arrays:更新:处理 JSON arrays 的追加而不是替换:

func overlay2(dst, src map[string]interface{}) error {
    for k, v := range src {

        if _, ok := dst[k]; !ok {
            dst[k] = v // easy case - dst key does not exist
            continue
        }

        dm, ok1 := dst[k].(map[string]interface{})
        sm, ok2 := src[k].(map[string]interface{})

        if ok1 && ok2 {
            overlay2(dm, sm) // merge case
            continue
        }

        ds, ok1 := dst[k].([]interface{})
        ss, ok2 := src[k].([]interface{})

        if ok1 && ok2 {
            dst[k] = append(ds, ss...) // JSON array case
            continue
        }

        return fmt.Errorf("unhandled type/update")

    }
    return nil
}

https://play.golang.org/p/i-0yXMcqU7Z https://play.golang.org/p/i-0yXMcqU7Z

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM