使用jq将JSON文档展开

32

我在考虑以下的JSON对象数组:

[
  {
    "index": "index1",
    "type": "type1",
    "id": "id1",
    "fields": {
      "deviceOs": [
        "Android"
      ],
      "deviceID": [
        "deviceID1"
      ],
      "type": [
        "type"
      ],
      "country": [
        "DE"
      ]
    }
  },
  {
    "index": "index2",
    "type": "type2",
    "id": "id2",
    "fields": {
      "deviceOs": [
        "Android"
      ],
      "deviceID": [
        "deviceID2"
      ],
      "type": [
        "type"
      ],
      "country": [
        "US"
      ]
    }
  }
]

我希望能将其压平,得到如下结果:

[
  {
    "index": "index1",
    "type": "type",
    "id": "id1",
    "deviceOs": "Android",
    "deviceID": "deviceID1",
    "country": "DE"
  },
  {
    "index": "index2",
    "type": "type",
    "id": "id2",
    "deviceOs": "Android",
    "deviceID": "deviceID2",
    "country": "US"
  }
]

我正在尝试使用 jq,但是我未能展开"fields"。我该怎么做?目前我对命令行工具感兴趣,但我也乐意听取其他建议。


5
我正在为这个关于“将元素取消数组化到特定深度”的问题点赞,但标题不够清晰,可能会让你误打误撞地来到这里,而你实际上是想“使用jq真正展平一个JSON文档”;如果你和我一样,那么请查看paths并考虑使用[paths(scalars) as $path | { ($path | map(tostring) | join("_")): getpath($path) } ][]的结果。 - nik.shornikov
4
@nik.shornikov,稍作改进,将结尾的[]替换为| add,可以得到一个包含所有扁平化路径/值对的对象:[paths(scalars) as $path | { ($path | map(tostring) | join("_")): getpath($path) } ] | add - aRestless
@nik.shornikov 这正是我在寻找的。对于未来的搜索者,上述代码片段将解除对象中任意深度的所有键的嵌套,并使用连接字符作为分隔符。 - knite
5个回答

45

这个有点难以制作。

map
(
    with_entries(select(.key != "fields"))
    +
    (.fields | with_entries(.value = .value[0]))
)

让我们分解并解释其中的部分

  1. 对于数组中的每个项目...

map(...)
创建一个新对象,其中包含除fields属性以外的所有值。
with_entries(select(.key != "fields"))
  • 结合...

  • +
    
    每个字段将每个值投射到每个数组的第一项。
    (.fields | with_entries(.value = .value[0]))
    

    2
    很好的回答。我想补充一点,对于连接(join)操作,硬编码列表索引会让我担心可能会遗漏某些内容。with_entries(.value = (.value| join(","))) https://jqplay.org/s/2mY6yus7Mz - Mike D
    with_entries(select(.key != "fields")) 可以被 del(.fields) 替换,从而缩短程序。 - markusk

    26

    有一个称为gron的工具,您可以将JSON输入其中,以获得类似以下内容的东西。

    $ gron document.json
    json = [];
    json[0] = {};
    json[0].fields = {};
    json[0].fields.country = [];
    json[0].fields.country[0] = "DE";
    json[0].fields.deviceID = [];
    json[0].fields.deviceID[0] = "deviceID1";
    json[0].fields.deviceOs = [];
    json[0].fields.deviceOs[0] = "Android";
    json[0].fields.type = [];
    json[0].fields.type[0] = "type";
    json[0].id = "id1";
    json[0].index = "index1";
    json[0].type = "type1";
    json[1] = {};
    json[1].fields = {};
    json[1].fields.country = [];
    json[1].fields.country[0] = "US";
    json[1].fields.deviceID = [];
    json[1].fields.deviceID[0] = "deviceID2";
    json[1].fields.deviceOs = [];
    json[1].fields.deviceOs[0] = "Android";
    json[1].fields.type = [];
    json[1].fields.type[0] = "type";
    json[1].id = "id2";
    json[1].index = "index2";
    json[1].type = "type2";
    
    

    4
    太棒了!这个答案对我来说更有效,因为它可以递归到任意层。例如,在这里,deviceID[.] = \"(.+)\";$变成了grep表达式,可以在深度不同的整个输出树中找到"叶子"。谢谢。 - IcarusNM

    11
    您可以使用此过滤器:
    [.[] | {index: .index, type: .type, id: .id, deviceOs: .fields.deviceOs[],deviceID: .fields.deviceID[],country: .fields.country[]}]
    

    你可以在这里测试 https://jqplay.org

    5

    以下是一些变化,它们通过使用 + 将 .fields 合并到包含对象中,然后展开数组元素。首先我们处理 .fields,代码如下:

      .[]
    | . + .fields
    | del(.fields)
    

    这使我们所剩的对象看起来像:
    {
      "index": "index1",
      "type": [
        "type"
      ],
      "id": "id1",
      "deviceOs": [
        "Android"
      ],
      "deviceID": [
        "deviceID1"
      ],
      "country": [
        "DE"
      ]
    }
    

    我们可以使用多种方法来展开键。一种方法是使用with_entries

    | with_entries( .value = if .value|type == "array" then .value[0] else .value end )
    

    另一种方法是使用 reducesetpath
    | . as $v
    | reduce keys[] as $k (
        {};
        setpath([$k]; if $v[$k]|type != "array" then $v[$k] else $v[$k][0] end)
      )
    

    2
    与 @jq170727 的答案类似:
    jq 'map(. + (.fields | with_entries(.value |= .[])) | del(.fields))'
    

    (假设.fields内部没有叫做.fields的字段。) |with_entries(.value|=.[]) 部分用于展开.fields中的值数组 - 注意只有第一个项目被保留。可以使用.value|=join(", ")将多个字符串值连接成一个字符串。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接