Shell 使用jq基于公共键值对合并两个JSON文件

Shell 使用jq基于公共键值对合并两个JSON文件,shell,jq,jenkins-groovy,Shell,Jq,Jenkins Groovy,我有两个JSON文件,结构如下 File1.json { "Plugins": [ { "Name": "Plugin A", "Installation": [ { "Version": "1.0", "Server" : "abc" }

我有两个JSON文件,结构如下

File1.json

{
  "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”,…}