jq - 检查子键而不过滤

jq - check for subkey without filtering

我想在不过滤数据的情况下使用 jq 检查 JSON 文件中是否存在子项(或其类型)。我需要它来从以下 JSON 文件中获取所有实体的列表,如果有不止一对可用,则使用第一对坐标。问题在于部分嵌套结构:“位置”对象仅适用于某些条目,“坐标”元素仅在多个位置可用时才是数组。

{"records":[
{
    "id": 1,
    "location": {
        "coordinates": {
            "lat": 42,
            "lon": -71
        }
    }
},
{
    "id": 2,
    "location": {
        "coordinates": [
            {
                "lat": 40,
                "lon": -73
            },
            {
                "lat": 39,
                "lon": -75
            }
        ]
    }
},
{
    "id": 3,
    "location": null
}]}

所以我尝试了“has”功能,它似乎对子键不起作用。我想象的是这样的:

cat file.json | jq '.records[] | if has("location.coordinates") then [do something] else [do something else] end'

有什么方法可以检查子项吗?由于我需要维护数据集中的所有条目,因此似乎无法通过“select”等方式进行过滤。

澄清我的问题:我希望得到类似于此的 JSON 输出(但我很乐意处理其他格式):

{"records":[
{"id": 1, "lat", xx, "lon": xx}
{"id": 2, "lat", yy, "lon": yy}
{"id": 3, "lat", null, "lon": null}
]}

您可以使用 select|= 运算符代替 has

.records |= map(.id as $id | (.location.coordinates | (if type == "array" then .[0] else . end) as $q | ({ $id, lat: null, lon: null } + $q) ))

这会生成:

{
  "records": [
    {
      "id": 1,
      "lat": 42,
      "lon": -71
    },
    {
      "id": 2,
      "lat": 40,
      "lon": -73
    },
    {
      "id": 3,
      "lat": null,
      "lon": null
    }
  ]
}

正如您可以在此 online demo 中尝试的那样。


所以,以上解释:

  1. 循环 records 并更新它们
    .records |= map()
  2. 保存 ID
    .id as $id
  3. 坐标继续
    .location.coordinates |
  4. 检查一个数组,如果是,就得到第一个对象,否则,就保留它,另存为$q
    (if type == "array" then .[0] else . end) as $q
  5. 创建最终对象,从仅将 idlatlon 设置为 null 的对象开始,然后我们合并 $q从坐标中获取实际值
    ({ $id, lat: null, lon: null } + $q)

这很可能会被简化,所以等待另一个具有相同想法但更优化的答案。

您还可以使用替代运算符 // :

jq '.records[] |= {id}+((.location.coordinates? |
                         if type == "array" then .[0] else . end
                        ) // {}
                       )' input.json

如果前一个案例失败,您可以使用 error suppression operator ? in combination with the alterative operator // 回退到另一个案例:

.records[] |= (.location.coordinates | .[0]? // .) as {$lat,$lon} | {id,$lat,$lon}
{
  "records": [
    {
      "id": 1,
      "lat": 42,
      "lon": -71
    },
    {
      "id": 2,
      "lat": 40,
      "lon": -73
    },
    {
      "id": 3,
      "lat": null,
      "lon": null
    }
  ]
}

Demo

在 1.6 版中,jq 引入了 destructuring alternative operator ?// 其中

provides a concise mechanism for destructuring an input that can take one of several forms

这样一来,您可以使用相同的变量名定义一种情况下使用数组,另一种情况下不使用数组。由于“不匹配”变量(在成功的替代方案中)设置为 null,因此无需明确处理第三种情况。

jq '.records |= map(
  .location as {coordinates: [{$lat, $lon}]} ?// {coordinates: {$lat, $lon}}
  | {id, $lat, $lon}
)'
{
  "records": [
    {
      "id": 1,
      "lat": 42,
      "lon": -71
    },
    {
      "id": 2,
      "lat": 40,
      "lon": -73
    },
    {
      "id": 3,
      "lat": null,
      "lon": null
    }
  ]
}

Demo