So I have 2 configs, one you can say its kinda like default config and another based on request the attributes needs to be either updated or inserted.
Example of both:
var (
currentConfig = `{
"config": {
"type": "func1",
"properties": {
"param1": "10",
"param2": "10"
},
"connected": [
{
"type": "func2",
"properties": {
"param1": "20",
"param2": "20"
},
"connected": [
{
"type": "func3",
"properties": {},
"connected": [
{}
]
}
]
}
]
}
}`
updateRequest = `{
"config": {
"type": "func1",
"properties": {},
"connected": [
{
"type": "func2",
"properties": {
"param1": "30",
},
"connected": [
{
"type": "func3",
"properties": {
"param1": "20",
"param2": "20"
},
"connected": [
{}
]
}
]
}
]
}
}`
)
Im able to iterate through one map but was wondering how to pass both configs/maps and check if attribute exists or not. Any help would be appreciated.
func checkkeyPairExists(value interface{}) {
switch value.(type) {
case []interface{}:
for _, v := range value.([]interface{}) {
checkkeyPairExists(v)
}
case map[string]interface{}:
for k, v := range value.(map[string]interface{}) {
fmt.Println(k, v)
checkkeyPairExists(v)
}
}
}
The desired output:
{
"config": {
"type": "func1",
"properties": {
"param1": "10",
"param2": "10"
},
"connected": [
{
"type": "func2",
"properties": {
"param1": "20",
"param2": "30"
},
"connected": [
{
"type": "func3",
"properties": {
"param1": "20",
"param2": "20"
},
"connected": [
{}
]
}
]
}
]
}
}
CodePudding user response:
You have to recursively descend both of them:
func checkkeyPairExists(value1, value2 interface{}) {
switch value1.(type) {
case []interface{}:
v2, ok:=value2.([]interface{})
if !ok {
// error, or ignore and return
}
// merge value1 and v2
case map[string]interface{}:
m2, ok:=value2.(map[string]interface{})
if !ok {
// error, or ignore and return
}
for k, v := range value1 {
if v2, exists:=m2[k]; exists {
// k exists in value2
} else {
// k is not in value2
}
}
}
}
CodePudding user response:
It's tough to recommend a full solution without knowing more details about the safe assumptions about your data/schema but I can take a shot with what you have here.
Limiting your concepts a bit from interface to a struct representing your nodes will probably make things a bit easier here.
type ConfigNode struct {
Type string
Properties map[string]string
Connected []*ConfigNode
}
func (n *ConfigNode) PatchProperties(patch *ConfigNode) {
for k, v := range patch.Properties {
n.Properties[k] = v
}
}
func (n ConfigNode) ShallowClone() ConfigNode {
clone := ConfigNode{
Type: n.Type,
Properties: make(map[string]string),
Connected: make([]*ConfigNode, 0),
}
clone.PatchProperties(&n)
return clone
}
func (n *ConfigNode) PrintTree() string {
builder := strings.Builder{}
n.appendToTreePrint(&builder, 0)
return builder.String()
}
func (n *ConfigNode) appendToTreePrint(builder *strings.Builder, depth int) {
isRoot := builder == nil
tab := strings.Repeat("\t", depth)
if isRoot {
builder = &strings.Builder{}
}
builder.WriteString(tab)
builder.WriteString(n.Type)
builder.WriteRune('\n')
for k, v := range n.Properties {
builder.WriteString(fmt.Sprintf("%s - %s => %s\n", tab, k, v))
}
for _, c := range n.Connected {
c.appendToTreePrint(builder, depth 1)
}
}
func mergeNodes(base []*ConfigNode, patch []*ConfigNode) []*ConfigNode {
merged := make([]*ConfigNode, 0)
if patch == nil {
// No patch is being applied, just deep copy the base nodes.
for _, node := range base {
clone := node.ShallowClone()
clone.Connected = mergeNodes(clone.Connected, nil)
merged = append(merged, &clone)
}
return merged
}
baseTypes := make(map[string]*ConfigNode)
patchTypes := make(map[string]*ConfigNode)
// Key the nodes by their Type so we can match them.
for _, node := range base {
baseTypes[node.Type] = node
}
for _, node := range patch {
patchTypes[node.Type] = node
}
for k, v := range baseTypes {
mergedNode := v.ShallowClone()
if patchNode, ok := patchTypes[k]; ok {
// A patch node was found with the Type matching the base, combine the two.
mergedNode.PatchProperties(patchNode)
// Remove the patch node so we don't iterate through it later.
delete(patchTypes, k)
// Recurse in and merge child nodes.
mergedNode.Connected = mergeNodes(v.Connected, patchNode.Connected)
} else {
// There is no patch so we can just deep copy the children.
mergedNode.Connected = mergeNodes(v.Connected, nil)
}
merged = append(merged, &mergedNode)
}
// Any unmatched patch nodes can be deep copied into the output.
for _, v := range patchTypes {
mergedNode := v.ShallowClone()
mergedNode.Connected = mergeNodes(v.Connected, nil)
merged = append(merged, &mergedNode)
}
return merged
}
func printConfig(name string, config []*ConfigNode) {
fmt.Println(name ":")
for _, v := range config {
fmt.Println(v.PrintTree())
}
}
func initTestNodes() (base []*ConfigNode, patch []*ConfigNode) {
var node1Base ConfigNode
var node2Base ConfigNode
var node3Base ConfigNode
var node1Patch ConfigNode
var node3Patch ConfigNode
var node4Patch ConfigNode
node1Base = ConfigNode{
Type: "func1",
Properties: map[string]string{
"params1": "orig1",
"params2": "orig1",
},
Connected: []*ConfigNode{&node2Base},
}
node2Base = ConfigNode{
Type: "func2",
Properties: map[string]string{
"params1": "orig2",
"params2": "orig2",
},
Connected: []*ConfigNode{&node3Base},
}
node3Base = ConfigNode{
Type: "func3",
Properties: map[string]string{
"params1": "orig3",
"params2": "orig3",
},
Connected: []*ConfigNode{},
}
node1Patch = ConfigNode{
Type: "func1",
Properties: map[string]string{
"params1": "up1",
},
Connected: []*ConfigNode{&node4Patch},
}
node3Patch = ConfigNode{
Type: "func3",
Properties: map[string]string{
"params1": "up3",
},
Connected: []*ConfigNode{},
}
node4Patch = ConfigNode{
Type: "func4",
Properties: map[string]string{
"params1": "up4",
},
Connected: []*ConfigNode{&node3Patch},
}
return []*ConfigNode{&node1Base}, []*ConfigNode{&node1Patch}
}
func main() {
baseConfig, patchConfig := initTestNodes()
merged := mergeNodes(baseConfig, patchConfig)
printConfig("Base Config", baseConfig)
printConfig("Patch Config", patchConfig)
printConfig("Merged Config", merged)
}