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