【问题标题】:jq - reformat the JSON and put values as keys into new array and objects?jq - 重新格式化 JSON 并将值作为键放入新数组和对象中?
【发布时间】:2020-09-15 20:56:55
【问题描述】:

如何重新格式化 JSON,根据选择条件创建一个新数组并将值作为键?

这是 json 文件的一个片段:

[
    {
        "Header": {
            "Tenant": "Tenant1"
        },
        "Body": {
            "values": [
                {
                    "id": "ca95",
                    "name": "Property1"
                },
                {
                    "id": "b126",
                    "name": "Property2"
                },
                {
                    "id": "aec0",
                    "name": "Property3"
                },
                {
                    "id": "a62b",
                    "name": "Property4"
                }
            ]
        }
    },
    {
        "Header": {
            "Tenant": "Tenant2"
        },
        "Body": {
            "values": [
                {
                    "id": "ca95",
                    "name": "Property1"
                },
                {
                    "id": "b4cd",
                    "name": "Property4"
                }
            ]
        }
    }
]

Header.TenantBody.values[].name 的值是唯一的,但 Body.values[].id 不是 - 对于 Property4 对应的来自同一对象的 "id"a62b 用于 Tenant1b4cd 用于 Tenant2

我需要找出特定的"name/Property" 键/值存在多少个不同的对应"id""Tenant",然后按"id" 的值对它们进行分组。

结果应如下所示:

[
    {
        "name": "Property1",
        "ca95": [
            "Tenant1",
            "Tenant2"
        ]
    },
    {
        "name": "Property2",
        "b126": [
            "Tenant1"
        ]
    },
    {
        "name": "Property3",
        "aec0": [
            "Tenant1"
        ]
    },
    {
        "name": "Property4",
        "a62b": [
            "Tenant1"
        ]
    },
    {
        "name": "Property4",
        "b4cd": [
            "Tenant2"
        ]
    }
]

如何用 jq 做到这一点?我想我应该使用 group_by 和/或 reduce?

谢谢

【问题讨论】:

    标签: arrays json sorting group-by jq


    【解决方案1】:

    这是一个比使用group_by 更有效的解决方案。它使用GROUPS_BY,这是group_by的面向流的泛化:

    
    # Emit a stream of arrays, each corresponding to a group
    # defined by f, which can be any jq filter
    # that maps every item in the stream to a single value.
    def GROUPS_BY(stream; f): 
       reduce stream as $x ({};
         ($x|f) as $s
         | ($s|type) as $t
         | (if $t == "string" then $s else ($s|tojson) end) as $y
         | .[$t][$y] += [$x] )
       | select(.)
       | .[][] ;
    

    现在可以直接编写解决方案,无需进一步求助于reduce

    [ GROUPS_BY(.[]
                | .Header.Tenant as $tenant
                | .Body.values[] | {name, id, tenant: $tenant};
                {name,id}) 
      | (.[0] | { name }) + { (.[0].id): map(.tenant) } ]
    

    【讨论】:

    • 你是个天才。是的,你的解决方案远远高于我的工资等级。一个后续问题 - 我应该如何在 shell 脚本中以最安全/最优雅的方式使用上述内容?嗯……单行100个字符长。我应该将它分配给shell var,然后将该var分配给jq var吗?还是将其作为 .jq 过滤器放在单独的文件中并调用它?
    • 我能想到三种合理的方法:(1)在 shell 脚本的多行中使用 jq '....'; (2) 使用 -f 选项; (3)使用jq对模块的支持。最后一个选项是最棘手的,但是当一个具有“标准库”函数(例如 GROUPS_BY)时,它可能是值得的。
    【解决方案2】:

    我想我应该使用 group_by 和/或 reduce?

    只需group_bymap 即可轻松完成:

    map(.Header.Tenant as $tenant
        | .Body.values[] | {name, id, tenant: $tenant})
    | group_by(.name)
    | map(group_by(.id))
    | map( .[] |  (.[0] | { name }) + { (.[0].id): map(.tenant) })
    
    

    理解这一点的一种方法是运行程序的前两行,然后运行前三行,然后从那里逐步进行。您还可以在此过程中添加debug 语句。

    【讨论】:

    • 我已经尝试了上述方法,但在Property4 的结果数组中,它将Tenan1Tenant2 组合在一起,而它们应该是单独的数组,因为id 是不同的。跨度>
    • 完美!效果也很好,比第一个解决方案短。
    • 一个后续评论 - 如果我想在新对象arrays 下放置数组,那么你如何修改 ``map( .[] | (.[0] | { name }) + { (.[0].id): map(.tenant) })`` 语句?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-29
    • 2021-03-11
    • 1970-01-01
    • 1970-01-01
    • 2022-12-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多