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
CodePudding user response:
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
}