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

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

Update Nested fields using Golang struct

问题

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

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

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

  1. currentConfig := `
  2. {
  3. "field_one": "value",
  4. "data": {
  5. "field_two": [
  6. "data1",
  7. "data2"
  8. ],
  9. "field_three": "check",
  10. "field_four": 12
  11. },
  12. "field_five": [
  13. "data1",
  14. "data2"
  15. ],
  16. "data2": {
  17. "field_six": {
  18. "field_seven": 100
  19. }
  20. }
  21. }`
  22. updateRequest := `
  23. {
  24. "data": {
  25. "field_three": "updated check" // 如果当前配置中存在相同的值(field_three存在于当前配置中),则忽略
  26. },
  27. "field_five": ["data3"], // 追加到当前配置
  28. "data2": {
  29. "field_six": {
  30. "field_eight": 300 // 如果当前配置中不存在该值,则添加
  31. }
  32. }
  33. }`
  1. func main() {
  2. config := make(map[string]interface{})
  3. err := json.Unmarshal([]byte(currentConfig), &config)
  4. if err != nil {
  5. panic(err)
  6. }
  7. updateFields := make(map[string]interface{})
  8. err1 := json.Unmarshal([]byte(updateRequest), &updateFields)
  9. if err1 != nil {
  10. panic(err1)
  11. }
  12. fmt.Println(config)
  13. updateFields = ParseJsonMap(updateFields, config)
  14. fmt.Println(updateFields)
  15. }
  16. func ParseJsonMap(aMap map[string]interface{}, finalMap map[string]interface{}) map[string]interface{} {
  17. parseMap("", aMap, &finalMap)
  18. return finalMap
  19. }

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

  1. func parseMap(k string, aMap map[string]interface{}, finalMap *map[string]interface{}) {
  2. if len(aMap) == 0 {
  3. (*finalMap)[k] = nil
  4. return
  5. }
  6. for key, val := range aMap {
  7. if val != nil {
  8. switch concreteVal := val.(type) {
  9. case map[string]interface{}:
  10. if _, ok := (*finalMap)[getKey(k, key)]; ok {
  11. parseMap(getKey(k, key), val.(map[string]interface{}), finalMap)
  12. } else {
  13. (*finalMap)[getKey(k, key)] = val
  14. }
  15. case []interface{}:
  16. res := val.([]interface{})
  17. if arr, ok := (*finalMap)[getKey(k, key)]; ok {
  18. for _, valueIn := range res {
  19. arr = append(arr.([]interface{}), valueIn)
  20. }
  21. (*finalMap)[getKey(k, key)] = arr
  22. } else {
  23. (*finalMap)[getKey(k, key)] = res
  24. }
  25. default:
  26. concreteValType := reflect.TypeOf(concreteVal)
  27. if concreteValType.Kind() == reflect.Map {
  28. parseMap(getKey(k, key), concreteVal.(map[string]interface{}), finalMap)
  29. } else {
  30. if _, ok := (*finalMap)[getKey(k, key)]; !ok {
  31. (*finalMap)[getKey(k, key)] = concreteVal
  32. }
  33. }
  34. }
  35. } else {
  36. (*finalMap)[getKey(k, key)] = nil
  37. }
  38. }
  39. }
  40. func getKey(k string, key string) string {
  41. if k == "" {
  42. return key
  43. }
  44. return k + "." + key
  45. }

期望的结果:

  1. 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]
  1. {
  2. "field_one": "value",
  3. "data": {
  4. "field_two": [
  5. "data1",
  6. "data2"
  7. ],
  8. "field_three": "check", // 由于键已存在且数据未更新,因此保持不变
  9. "field_four": 12
  10. },
  11. "field_five": [
  12. "data1",
  13. "data2",
  14. "data3" // 追加了data3
  15. ],
  16. "data2": {
  17. "field_six": {
  18. "field_seven": 100,
  19. "field_eight": 300 // 添加了字段
  20. }
  21. }
  22. }

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

  1. 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

  1. currentConfig:=`
  2. {
  3. "field_one": "value",
  4. "data": {
  5. "field_two": [
  6. "data1",
  7. "data2"
  8. ],
  9. "field_three": "check",
  10. "field_four": 12
  11. },
  12. "field_five": [
  13. "data1",
  14. "data2"
  15. ],
  16. "data2": {
  17. "field_six":{
  18. "field_seven": 100
  19. }
  20. }
  21. }`
  22. updateRequest:=`
  23. {
  24. "data": {
  25. "field_three": "updated check" //ignore if same value exist (field_three exists in current config)
  26. },
  27. "field_five": ["data3"], // append to current config
  28. "data2": {
  29. "field_six":{
  30. "field_eight": 300 // add value if doesnt exist to current
  31. }
  32. }
  33. }`
  1. func main() {
  2. config := make(map[string]interface{})
  3. err := json.Unmarshal([]byte(currentConfig), &config)
  4. if err != nil {
  5. panic(err)
  6. }
  7. updateFields := make(map[string]interface{})
  8. err1 := json.Unmarshal([]byte(updateRequest), &updateFields)
  9. if err1 != nil {
  10. panic(err1)
  11. }
  12. fmt.Println(config)
  13. updateFields = ParseJsonMap(updateFields, config)
  14. fmt.Println(updateFields)
  15. }
  16. func ParseJsonMap(aMap map[string]interface{}, finalMap map[string]interface{}) map[string]interface{} {
  17. parseMap("", aMap, &finalMap)
  18. return finalMap
  19. }

Traverses the struct and updates the fields

  1. func parseMap(k string, aMap map[string]interface{}, finalMap *map[string]interface{}) {
  2. if len(aMap) == 0 {
  3. (*finalMap)[k] = nil
  4. return
  5. }
  6. for key, val := range aMap {
  7. if val != nil {
  8. switch concreteVal := val.(type) {
  9. case map[string]interface{}:
  10. if _, ok := (*finalMap)[getKey(k, key)]; ok {
  11. parseMap(getKey(k, key), val.(map[string]interface{}), finalMap)
  12. } else {
  13. (*finalMap)[getKey(k, key)] = val
  14. }
  15. case []interface{}:
  16. res := val.([]interface{})
  17. if arr, ok := (*finalMap)[getKey(k, key)]; ok {
  18. for _, valueIn := range res {
  19. arr = append(arr.([]interface{}), valueIn)
  20. }
  21. (*finalMap)[getKey(k, key)] = arr
  22. } else {
  23. (*finalMap)[getKey(k, key)] = res
  24. }
  25. default:
  26. concreteValType := reflect.TypeOf(concreteVal)
  27. if concreteValType.Kind() == reflect.Map {
  28. parseMap(getKey(k, key), concreteVal.(map[string]interface{}), finalMap)
  29. } else {
  30. if _, ok := (*finalMap)[getKey(k, key)]; !ok {
  31. (*finalMap)[getKey(k, key)] = concreteVal
  32. }
  33. }
  34. }
  35. } else {
  36. (*finalMap)[getKey(k, key)] = nil
  37. }
  38. }
  39. }
  40. func getKey(k string, key string) string {
  41. if k == "" {
  42. return key
  43. }
  44. return k + "." + key
  45. }

Expected Result

  1. 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]
  1. {
  2. "field_one": "value",
  3. "data": {
  4. "field_two": [
  5. "data1",
  6. "data2"
  7. ],
  8. "field_three": "check", //since key exist with data not updated
  9. "field_four": 12
  10. },
  11. "field_five": [
  12. "data1",
  13. "data2",
  14. "data3" //data 3 appended
  15. ],
  16. "data2": {
  17. "field_six":{
  18. "field_seven": 100,
  19. "field_eight": 300 //field is added
  20. }
  21. }
  22. }

Result got - created key at top level

  1. 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{} 叠加在另一个上面
  1. func CopyMap(m map[string]interface{}) map[string]interface{} {
  2. cp := make(map[string]interface{})
  3. for k, v := range m {
  4. vm, ok := v.(map[string]interface{})
  5. if ok {
  6. cp[k] = CopyMap(vm)
  7. } else {
  8. cp[k] = v
  9. }
  10. }
  11. return cp
  12. }
  1. func overlay(dst, src map[string]interface{}) error {
  2. for k, v := range src {
  3. if _, ok := dst[k]; !ok {
  4. dst[k] = v // 简单情况 - 目标键不存在
  5. continue
  6. }
  7. d, ok1 := dst[k].(map[string]interface{})
  8. s, ok2 := src[k].(map[string]interface{})
  9. if ok1 && ok2 {
  10. overlay(d, s) // 合并情况
  11. } else if !ok1 && !ok2 {
  12. dst[k] = v // 非地图 - 简单赋值/重新赋值
  13. } else {
  14. return fmt.Errorf("不兼容的更新类型") // 地图到非地图或反之
  15. }
  16. }
  17. return nil
  18. }

使用方法:

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

输出:

  1. 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]
  2. update : map[data:map[field_three:check changed] data2:map[field_six:map[field_eight:300]] field_five:[data1]]
  3. 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 数组的追加而不是替换:

  1. func overlay2(dst, src map[string]interface{}) error {
  2. for k, v := range src {
  3. if _, ok := dst[k]; !ok {
  4. dst[k] = v // 简单情况 - 目标键不存在
  5. continue
  6. }
  7. dm, ok1 := dst[k].(map[string]interface{})
  8. sm, ok2 := src[k].(map[string]interface{})
  9. if ok1 && ok2 {
  10. overlay2(dm, sm) // 合并情况
  11. continue
  12. }
  13. ds, ok1 := dst[k].([]interface{})
  14. ss, ok2 := src[k].([]interface{})
  15. if ok1 && ok2 {
  16. dst[k] = append(ds, ss...) // JSON 数组情况
  17. continue
  18. }
  19. return fmt.Errorf("未处理的类型/更新")
  20. }
  21. return nil
  22. }

链接: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

  1. func CopyMap(m map[string]interface{}) map[string]interface{} {
  2. cp := make(map[string]interface{})
  3. for k, v := range m {
  4. vm, ok := v.(map[string]interface{})
  5. if ok {
  6. cp[k] = CopyMap(vm)
  7. } else {
  8. cp[k] = v
  9. }
  10. }
  11. return cp
  12. }

  1. func overlay(dst, src map[string]interface{}) error {
  2. for k, v := range src {
  3. if _, ok := dst[k]; !ok {
  4. dst[k] = v // easy case - dst key does not exist
  5. continue
  6. }
  7. d, ok1 := dst[k].(map[string]interface{})
  8. s, ok2 := src[k].(map[string]interface{})
  9. if ok1 && ok2 {
  10. overlay(d, s) // merge case
  11. } else if !ok1 && !ok2 {
  12. dst[k] = v // non-map - so simple assignment/reassignment
  13. } else {
  14. return fmt.Errorf("incompatible update types") // map to non-map or vice-versa
  15. }
  16. }
  17. return nil
  18. }

to use:

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

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

Output:

  1. 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]
  2. update : map[data:map[field_three:check changed] data2:map[field_six:map[field_eight:300]] field_five:[data1]]
  3. 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:

  1. func overlay2(dst, src map[string]interface{}) error {
  2. for k, v := range src {
  3. if _, ok := dst[k]; !ok {
  4. dst[k] = v // easy case - dst key does not exist
  5. continue
  6. }
  7. dm, ok1 := dst[k].(map[string]interface{})
  8. sm, ok2 := src[k].(map[string]interface{})
  9. if ok1 && ok2 {
  10. overlay2(dm, sm) // merge case
  11. continue
  12. }
  13. ds, ok1 := dst[k].([]interface{})
  14. ss, ok2 := src[k].([]interface{})
  15. if ok1 && ok2 {
  16. dst[k] = append(ds, ss...) // JSON array case
  17. continue
  18. }
  19. return fmt.Errorf("unhandled type/update")
  20. }
  21. return nil
  22. }

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:

确定