Shell 使用jq基于公共键值对合并两个JSON文件
我有两个JSON文件,结构如下 File1.jsonShell 使用jq基于公共键值对合并两个JSON文件,shell,jq,jenkins-groovy,Shell,Jq,Jenkins Groovy,我有两个JSON文件,结构如下 File1.json { "Plugins": [ { "Name": "Plugin A", "Installation": [ { "Version": "1.0", "Server" : "abc" }
{
"Plugins": [
{
"Name": "Plugin A",
"Installation": [
{
"Version": "1.0",
"Server" : "abc"
}
]
},
{
"Name": "Plugin B",
"Installation": [
{
"Version": "2.0",
"Server" : "abc"
}
]
},
{
"Name": "Plugin C",
"Installation": [
{
"Version": "2.0",
"Server" : "abc"
}
]
}
]
}
File2.json
{
"Plugins": [
{
"Name": "Plugin A",
"Installation": [
{
"Version": "1.1",
"Server" : "xyz"
}
]
},
{
"Name": "Plugin B",
"Installation": [
{
"Version": "2.0",
"Server" : "xyz"
}
]
},
]
}
我想合并它们,得到这样的输出
{
"Plugins": [
{
"Name": "Plugin A",
"Installation": [
{
"Version": "1.0",
"Server" : "abc"
},
{
"Version": "1.1",
"Server" : "xyz"
}
]
},
{
"Name": "Plugin B",
"Installation": [
{
"Version": "2.0",
"Server" : "abc"
},
{
"Version": "2.0",
"Server" : "xyz"
}
]
},
{
"Name": "Plugin C",
"Installation": [
{
"Version": "2.0",
"Server" : "abc"
}
]
}
]
}
这两个JSON文件具有完全相同的结构,但仅在文件内容方面有所不同。
我主要考虑使用jq实用程序。Shell或jenkins groovy脚本也可以。
任何帮助都将不胜感激 这里有一种方法:
def mergePlugin($plugin):
if .[$plugin.Name]
then .[$plugin.Name].Installation += $plugin.Installation
else .[$plugin.Name] = $plugin
end;
{
"Plugins": (
map(.Plugins)
| add
| reduce .[] as $plugin ({}; mergePlugin($plugin))
| to_entries | map(.value)
)
}
运行此:
jq -s -f mergePlugins.jq File*.json
解释了命令行参数:
--slurp
/-s
:不要为输入中的每个JSON对象运行过滤器,而是将整个输入流读取到一个大数组中,然后只运行一次过滤器
-f文件名
/--从文件名
:从文件而不是从命令行读取筛选器,如awk的-f选项。您还可以使用´#´进行评论
通过提供对象列表作为输入而不是多个对象来模拟-s
以下是解决方案的工作原理:jq-s.”文件*.json
提供了{“插件”:[…]}
对象的列表。由于对[…]
部分感兴趣,jq-s'map(.Plugins)'文件*.json
提供了以下列表(每个文件一个):
我们可以使用jq-s'map(.Plugins)|add'File*.json
折叠一层嵌套列表:
[
{
"Name": "Plugin A",
...
},
{
"Name": "Plugin B",
...
},
...
]
对于下一部分,由于我希望所有的“Name”:“Plugin X”
彼此合并,我认为一个字典/对象的键是“Plugin X”
,这将是一个很好的数据结构,因为对于每个插件,如果我以前遇到或没有遇到它,我都可以进行固定时间的查找
我使用以下方法创建此词典:
{}
是这个对象的初始值,$plugin
是每个{“Name”:“plugin X”,“Installation”:[…]}
值,
是包含键的中间字典/对象,键是“plugin X”
,值是$plugin
类对象
因为if-then-else有点长,所以我将它移动到一个助手过滤器中,mergePlugin
。这种过滤器有两种功能:
和$plugin
这将产生:
{
"Plugin A": {
"Name": "Plugin A",
"Installation": [
{
"Version": "1.0",
"Server": "abc"
},
{
"Version": "1.1",
"Server": "xyz"
}
]
},
...
}
这几乎是最终的结果,除了一个不必要的{“Plugin A”:{…}
包装器现在可以废弃,还有一个缺少的{“Plugins”:[…]}
包装器需要重新添加
改进意见:
- 我很肯定你可以做比这更聪明的事
这是最后一部分,但它完成了任务``` { "Plugins": ( ... | to_entries | map(.value) ) } ```
- 我还认为实际的合并可以比if-then-else更短
- 这里有一种方法:
def mergePlugin($plugin):
if .[$plugin.Name]
then .[$plugin.Name].Installation += $plugin.Installation
else .[$plugin.Name] = $plugin
end;
{
"Plugins": (
map(.Plugins)
| add
| reduce .[] as $plugin ({}; mergePlugin($plugin))
| to_entries | map(.value)
)
}
运行此:
jq -s -f mergePlugins.jq File*.json
解释了命令行参数:
--slurp
/-s
:不要为输入中的每个JSON对象运行过滤器,而是将整个输入流读取到一个大数组中,然后只运行一次过滤器
-f文件名
/--从文件名
:从文件而不是从命令行读取筛选器,如awk的-f选项。您还可以使用´#´进行评论
通过提供对象列表作为输入而不是多个对象来模拟-s
以下是解决方案的工作原理:jq-s.”文件*.json
提供了{“插件”:[…]}
对象的列表。由于对[…]
部分感兴趣,jq-s'map(.Plugins)'文件*.json
提供了以下列表(每个文件一个):
我们可以使用jq-s'map(.Plugins)|add'File*.json
折叠一层嵌套列表:
[
{
"Name": "Plugin A",
...
},
{
"Name": "Plugin B",
...
},
...
]
对于下一部分,由于我希望所有的“Name”:“Plugin X”
彼此合并,我认为一个字典/对象的键是“Plugin X”
,这将是一个很好的数据结构,因为对于每个插件,如果我以前遇到或没有遇到它,我都可以进行固定时间的查找
我使用以下方法创建此词典:
{}
是这个对象的初始值,$plugin
是每个{“Name”:“plugin X”,“Installation”:[…]}
值,
是包含键的中间字典/对象,键是“plugin X”
,值是$plugin
类对象
因为if-then-else有点长,所以我将它移动到一个助手过滤器中,mergePlugin
。这种过滤器有两种功能:
和$plugin
这将产生:
{
"Plugin A": {
"Name": "Plugin A",
"Installation": [
{
"Version": "1.0",
"Server": "abc"
},
{
"Version": "1.1",
"Server": "xyz"
}
]
},
...
}
这几乎是最终的结果,除了一个不必要的{“Plugin A”:{…}
包装器现在可以废弃,还有一个缺少的{“Plugins”:[…]}
包装器需要重新添加
改进意见:
- 我很肯定你可以做比这更聪明的事
这是最后一部分,但它完成了任务``` { "Plugins": ( ... | to_entries | map(.value) ) } ```
- 我还认为实际的合并可以比if-then-else更短
“插件A”
选择{“版本”:“1.0”,…}
。输出在安装阵列下包含两个插件版本。“名称”是公共密钥,“服务器”密钥在文件之间总是不同的。“Version”键可能相同或不同,但输出应该包含两个文件的内容您的示例有一点不明确:当两个文件都有一个版本不同的插件时,它应该总是选择左一个,还是总是选择最小的版本号,还是什么?请详细说明为什么为“插件A”
选择{“版本”:“1.0”,…}