【问题标题】:How to parse json with jq where the structure is non-linear如何在结构非线性的情况下使用 jq 解析 json
【发布时间】:2020-05-06 10:54:53
【问题描述】:

我希望我已经清楚地表达了我的问题。 需要帮助查询,他们使用 JQ 解析多个 json 文件,其中每个文件中的结构是非线性的。该应用程序生成类似于此示例的配置数据。每个文件可以有零个或多个 DualEndPoint 或 Local 对象。我需要能够在“用户”属性中查询特定用户并插入新密码以重新提交回 api。对于 DualEndPoints,嵌套对象名称是可变的,因此无法在查找“用户”属性时对这些值进行编码。

如果找到特定用户的匹配项,则返回仅插入该用户的新密码的整个结构。在示例中,查询 user1 将返回整个 PROFILE1 和 PROFILE2,但不返回 PROFILE3,因为它不包含 user1 凭据。

{
  "PROFILE1": {
    "Type": "ConnectionProfile:FileTransfer:DualEndPoint",
    "WorkloadAutomationUsers": [
      "*"
    ],
    "VerifyBytes": true,
    "TargetAgent": "sqlrptvmjhbpr01",
    "TargetCTM": "Production",
    "Endpoint:Src:Local_0": {
      "Type": "Endpoint:Src:Local",
      "User": "user1",
      "Port": "0",
      "OsType": "Windows",
      "HostName": "Local",
      "Password": "*****",
      "HomeDirectory": "/user1homedir"
    },
    "Endpoint:Dest:SFTP_1": {
      "Type": "Endpoint:Dest:SFTP",
      "User": "user2",
      "HostName": "server2",
      "Password": "*****",
      "HomeDirectory": "/user2homedir"
    }
  },
  "PROFILE2": {
    "Type": "ConnectionProfile:FileTransfer:Local",
    "WorkloadAutomationUsers": [
      "*"
    ],
    "VerifyBytes": true,
    "User": "user1",
    "VerifyDestination": true,
    "OsType": "Windows",
    "HostName": "Local",
    "Password": "*****",
    "TargetAgent": "server1",
    "TargetCTM": "Production"
  },
  "PROFILE3": {
    "Type": "ConnectionProfile:FileTransfer:Local",
    "WorkloadAutomationUsers": [
      "*"
    ],
    "VerifyBytes": true,
    "User": "user3",
    "OsType": "Windows",
    "HostName": "Local",
    "Password": "*****",
    "HomeDirectory": "/user3hoemdir",
    "TargetAgent": "server2",
    "TargetCTM": "Production"
  }
}

【问题讨论】:

  • 抱歉 - 修复了上面原始帖子中的格式。
  • 用户的新密码如何输入?
  • 普通 jq --arg 将在最终调用中添加到命令行: jq --arg PWD "actualpw" '.....' 应该替换指定用户的密码属性。让我发疯的部分是如何在未知对象名称下处理结构中不同位置的用户属性
  • 是的,user 属性不能保证始终在Endpoint:Dest: 对象内?
  • 它会一直存在,但会根据.Type 的值出现在不同的位置。其中Type="ConnectionProfile:FileTransfer:Local"(如在PROFILE3 中)User 保证在顶层作为普通对象存在。在Type="ConnectionProfile:FileTransfer:DualEndPoint" 的位置,还有两个描述源和目标的对象,每个对象在第二级都有自己的User 对象。

标签: json jq non-linear


【解决方案1】:

使用jq 1.6,您可以使用以下内容:

jq --arg newPwd "newPassword" \
     'walk(if type == "object" and .User == "user1" then .password |= $newPwd else . end)  
        | map_values(select(.. | select(type == "object") and .User == "user1"))' 

这将递归您的 JSON 输入,并将具有 User : "user1" 键/值对的对象的 password 字段设置为您想要的值。

你可以try it here

在早期版本中,您可以使用这个等价物:

jq --arg newPwd "newPassword" \
   'def rec :
      if type == "object" and .User == "user1" then 
        .password = $newPwd
      elif type == "object" then
        map_values(rec)
      elif type == "array" then
        map(rec)
      else
        .
      end
    ; 
    rec  | map_values(select(.. | select(type == "object") and .User == "user1"))'

你可以try it here

【讨论】:

  • 但是一定要添加添加walk()作为函数的方式,以防OP或未来的读者无法升级到最新版本
  • @Inian 你的意思是有一个walk/1 "polyfill" 吗?无论如何我添加了一个可以在旧版本中使用的版本
  • 步行很棒,满足了部分要求。请求的主要部分是过滤不包含对 user1 的任何引用的对象(最终将是一个 arg),如上面的 PROFILE3 中所示。因为 PROFILE3 的对象没有引用 user1,所以应该从输出中省略整个结构。感谢我使用 jq 1.5 的前版
  • @BrandonH 对不起,我错过了这个要求,我会更新我的答案并通知你
  • @BrandonH 对延迟感到抱歉,但如果您仍然感兴趣,我终于来更新我的答案。它现在删除不包含对特定用户的任何引用的顶级对象
【解决方案2】:

在以下对所述问题的解决方案中,有两个步骤。第一步使用with_entries 选择相关的“PROFILE”对象,第二步使用walk 更新密码,前提是有密码。参数化一切都很容易,所以为了简单起见,让我们假设(如在 Q 中)用户是“user1”:

with_entries(select( .value
    | any(paths(. == "user1");
          .[-1] == "User" )))
| walk( if type == "object" and .User == "user1" and has("Password")
        then .Password = "newpassword"
        else .end)

这里使用any 会稍微复杂一些,但是是为了提高效率。

注意walk/1

如果你的 jq 没有 walk/1,那么现在是更新你的 jq 的好时机,但如果这不是一个选项,只需在谷歌上搜索它的 def(搜索词:jq def walk builtin.jq)并复制你的 jq 程序开头的 def。

【讨论】:

    【解决方案3】:

    欢迎来到 StackOverflow!

    完整的示例不是有效的 JSON 对象,并且没有尝试解决问题,或者期望的结果应该是什么样的示例。因此,以下答案带有一些猜测。如果将示例缩减为 minimal, reproducible example 并消除所有混乱,这可能会大大增加获得好答案的几率。

    例如,"TargetAgent": "sqlrptvmjhbpr01" 似乎是不相关的信息。这条线的唯一作用是增加读者的认知负担,试图破译它是否与问题任务相关,而事实并非如此。

    每个文件可以有零个或多个 DualEndPoint 或 Local 对象。

    你没有确切地说出 DualEndPointLocal 对象是什么。

    由于文本DualEndPoint只出现在上下文中

    "Type": "ConnectionProfile:FileTransfer:DualEndPoint"
    

    我假设 DualEndPoint 对象是包含格式为键值对的对象

    "Type": "...:DualEndPoint"
    

    并且Local 对象是相同的,但DualEndPoint 替换为Local。如果该解释是正确的,那么您的第一个代码 sn-p 中将有三个 Local 对象示例,位于两个不同的嵌套级别(我理解为“非线性”部分)。

    Local 对象的一个​​示例是:

    {
      "Type": "Endpoint:Src:Local",
      "User": "user1",
      "Port": "0",
      "OsType": "Windows",
      "HostName": "Local",
      "Password": "newpassword",
      "HomeDirectory": "/user1homedir"
    }
    

    没有任何类似对象的示例,尽管包含“用户”属性,但不应更新。那么看来,回答这个问题,这些类型的对象之间的区别也是完全没有必要的?

    我需要能够在“用户”属性中查询特定用户并插入新密码以重新提交回 api。

    因此,如果User 是正确的,则主要问题的一个子问题可能是使用新密码更新对象。假设您已将对象范围缩小到这样的对象,程序的子部分可能如下所示:

    $ jq 'if .User == "user1" then .Password = "derp" else . end' local1.json
    {
      "Type": "Endpoint:Src:Local",
      "User": "user1",
      "Port": "0",
      "OsType": "Windows",
      "HostName": "Local",
      "Password": "derp",
      "HomeDirectory": "/user1homedir"
    }
    

    对于 DualEndPoints,嵌套对象名称是可变的,因此无法在查找“用户”属性时对这些值进行编码。

    所以听起来您想任意递归查找具有“用户”属性的对象。 jq 的一些递归组合器是..,更通用的recurse 以及在这种情况下似乎更合适的,walk

    $ jq 'walk(if type == "object" and .User == "user1"
               then .Password = "derp"
               else . end)' full.json
    

    (这也是 Aaron 发布的内容,除了他使用 |= 而我使用 =。)

    查看他的 jqplay 示例或this jqplay example

    如果找到特定用户的匹配项,则返回仅插入该用户的新密码的整个结构。在示例中,查询 user1 将返回整个 PROFILE1 和 PROFILE2,但不返回 PROFILE3,因为它不包含 user1 凭据。

    这听起来像是我们所使用的表达式中的一个额外条件:

    $ jq 'walk(if type == "object" and has("User")
               then (if .User == "user1"
                     then .Password = "derp"
                     else null end)
               else . end)' full.json
    

    这似乎几乎可以工作(请参阅this jqplay example),除了它留下"foo": null 值作为行走的结果。这是已经递归到包含“用户”属性的对象的副产品,这使得很难表达应该删除父键值对。

    解决这个问题,我们需要在walk/1 的过滤器中向前看,或者创建一个占位符并以父对象的视角再次行走。此处演示了这两种策略中的后者:

    $ jq 'walk(if type == "object" and has("User")
          then (if .User == "user1" then .Password = "derp" else "wat" end)
          else . end)
          | walk(if type == "object"
                 then with_entries(select(.value != "wat"))
                 else . end)' full.json
    

    这似乎有效。见this jqplay example

    【讨论】:

    • 谢谢 - 这是很大的进步。两个问题:1)如果 Type 是 DualEndpoint,子对象的两个部分仍然必须出现 - 即:Src 和 Dest 2)您能否重新格式化没有 walk 谓词的 jq 版本 1.5。
    • @BrandonH:我不确定我是否愿意这样做。 :-) 你能改变你的期望吗? walk/1 是用 jq 编写的,因此您可以通过添加 this def walk(f): ...; 从 1.6 移植它。
    • 谢谢你 - 你非常有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-07
    • 1970-01-01
    • 2022-01-25
    • 2012-03-23
    相关资源
    最近更新 更多