使用Golang结构体更新嵌套字段

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

Update Nested fields using Golang struct

问题

我正在面临一个使用golang更新结构字段的问题。

根据变量名,一些当前配置字段应该使用请求的配置进行更新。

以下是要翻译的代码部分:

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" // 如果当前配置中存在相同的值(field_three存在于当前配置中),则忽略
    },
    "field_five": ["data3"],    // 追加到当前配置
    "data2": {
        "field_six": {
            "field_eight": 300  // 如果当前配置中不存在该值,则添加
        }
    }
}`
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
}

遍历结构并更新字段的函数:

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
}

期望的结果:

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",  // 由于键已存在且数据未更新,因此保持不变
        "field_four": 12
    },
    "field_five": [
        "data1",
        "data2",
        "data3"   // 追加了data3
    ],
    "data2": {
        "field_six": {
            "field_seven": 100,
            "field_eight": 300 // 添加了字段
        }
    }
}

得到的结果 - 在顶层创建了一个键:

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]

我只想知道是否支持这种操作,如果支持的话,你能帮我解决问题吗?如果有更好的方法,请告诉我。

英文:

I am facing a issue with update struct fields using golang

As the var name suggest some current config fields should be updated with the requested config

	 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

答案1

得分: 4

似乎你正在尝试将一个地图叠加到另一个地图上。如果你不想在不改变现有地图的情况下应用这种更新,这会变得复杂。所以分为两个步骤可能更容易:

  • 复制一个 map[string]interface{}
  • 将一个 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 // 简单情况 - 目标键不存在
            continue
        }

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

        if ok1 && ok2 {
            overlay(d, s) // 合并情况
        } else if !ok1 && !ok2 {
            dst[k] = v // 非地图 - 简单赋值/重新赋值
        } else {
            return fmt.Errorf("不兼容的更新类型") // 地图到非地图或反之
        }

    }
    return nil
}

使用方法:

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

输出:

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]

更新:处理 JSON 数组的追加而不是替换:

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

        if _, ok := dst[k]; !ok {
            dst[k] = v // 简单情况 - 目标键不存在
            continue
        }

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

        if ok1 && ok2 {
            overlay2(dm, sm) // 合并情况
            continue
        }

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

        if ok1 && ok2 {
            dst[k] = append(ds, ss...) // JSON 数组情况
            continue
        }

        return fmt.Errorf("未处理的类型/更新")

    }
    return nil
}

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

英文:

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:

  • Copy a map[string]interface{}
  • overlay one map[string]interface{} on top of another

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

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:

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

huangapple
  • 本文由 发表于 2021年10月6日 23:41:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/69468726.html
匿名

发表评论

匿名网友

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

确定