Perform upsert on maps in golang

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

Perform upsert on maps in golang

问题

所以我有两个配置,一个可以说是默认配置,另一个是基于请求,属性需要更新或插入。

两个示例:

{
    "config": {
        "type": "func1",
        "config": {
            "param1": "10",
            "param2": "10"
        },
        "connected": [
            {
                "type": "func2",
                "config": {
                    "param1": "20",
                    "param2": "20"
                }
            }
        ]
    }
}
{
    "config": {
        "type": "func1",
        "config": {},
        "connected": [
            {
                "type": "func2",
                "config": {
                    "param1": "30"
                }
            }
        ]
    }
}

我能够遍历一个映射,但想知道如何传递两个配置/映射并检查属性是否存在。任何帮助将不胜感激。

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)
        }
    }
}

期望的输出:

{
    "config": {
        "type": "func1",
        "config": {
            "param1": "10",
            "param2": "10"
        },
        "connected": [
            {
                "type": "func2",
                "config": {
                    "param1": "20",
                    "param2": "30"
                }
            }
        ]
    }
}
英文:

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:

{
    "config": {
        "type": "func1",
        "config": {
            "param1": "10",
            "param2": "10"
        },
        "connected": [
            {
                "type": "func2",
                "config": {
                    "param1": "20",
                    "param2": "20"
                },
            }
        ]
    }
}
{
    "config": {
        "type": "func1",
        "config": {},
        "connected": [
            {
                "type": "func2",
                "config": {
                    "param1": "30",
                },  
            }
        ]
    }
}

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",
        "config": {
            "param1": "10",
            "param2": "10"
        },
        "connected": [
            {
                "type": "func2",
                "config": {
                    "param1": "20",
                    "param2": "30"
                },
            }
        ]
    }
}

答案1

得分: 2

不了解有关数据/模式的更多详细信息很难推荐一个完整的解决方案,但我可以根据你提供的代码尝试一下。

将你的概念从接口限制为表示节点的结构体可能会使事情变得更容易一些。

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 {
        // 没有应用补丁,只需深度复制基本节点。
        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)

    // 将节点按其类型进行键控,以便进行匹配。
    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 {
            // 找到了与基本类型匹配的补丁节点,将两者合并。
            mergedNode.PatchProperties(patchNode)
            // 删除补丁节点,以免后面迭代它。
            delete(patchTypes, k)

            // 递归并合并子节点。
            mergedNode.Connected = mergeNodes(v.Connected, patchNode.Connected)
        } else {
            // 没有补丁,因此我们可以直接深度复制子节点。
            mergedNode.Connected = mergeNodes(v.Connected, nil)
        }

        merged = append(merged, &mergedNode)
    }

    // 任何未匹配的补丁节点都可以深度复制到输出中。
    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)
}

希望这可以帮助到你!

英文:

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)
}

答案2

得分: 1

递归地遍历它们,并在properties映射内的属性改变时更新值。

如果有任何改进这个函数的地方,请告诉我。

func checkkeyPairExists(value1, value2 interface{}) {
	switch value1.(type) {
	case []interface{}:
		for k, v := range value1.([]interface{}) {
			fmt.Println("SLICE ", k, v)
			v2 := value2.([]interface{})
			checkkeyPairExists(v, v2[k])
		}
	case map[string]interface{}:
		for k, v := range value1.(map[string]interface{}) {
			fmt.Println("MAP", k, v)
			v2 := value2.(map[string]interface{})
			if k != "config" && k != "type" && k != "connected" {
				fmt.Println("UPDATED", k, v)
				v2[k] = v 
				continue
			}
			checkkeyPairExists(v, v2[k])
		}

	}
}
英文:

Recursively descended both of them and updating values as only attributes inside properties map will change.

Let me know if I can improve this function any further.

func checkkeyPairExists(value1, value2 interface{}) {
	switch value1.(type) {
	case []interface{}:
		for k, v := range value1.([]interface{}) {
			fmt.Println("SLICE ", k, v)
			v2 := value2.([]interface{})
			checkkeyPairExists(v, v2[k])
		}
	case map[string]interface{}:
		for k, v := range value1.(map[string]interface{}) {
			fmt.Println("MAP", k, v)
			v2 := value2.(map[string]interface{})
			if k != "config" && k != "type" && k != "connected" {
				fmt.Println("UPDATED", k, v)
				v2[k] = v 
				continue
			}
			checkkeyPairExists(v, v2[k])
		}

	}
}

答案3

得分: 0

你必须递归地遍历它们两个:

func checkkeyPairExists(value1, value2 interface{}) {
    switch value1.(type) {
    case []interface{}:
        v2, ok := value2.([]interface{})
        if !ok {
            // 错误,或者忽略并返回
        }
        // 合并 value1 和 v2
    case map[string]interface{}:
        m2, ok := value2.(map[string]interface{})
        if !ok {
            // 错误,或者忽略并返回
        }
        for k, v := range value1 {
            if v2, exists := m2[k]; exists {
                // k 在 value2 中存在
            } else {
                // k 不在 value2 中
            }
        }

    }
}

希望对你有帮助!

英文:

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
            }
        }

    }
}

huangapple
  • 本文由 发表于 2022年5月24日 02:05:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/72353020.html
匿名

发表评论

匿名网友

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

确定