首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >根据特定条件选择具有关联键的嵌套对象的一种优雅方法

根据特定条件选择具有关联键的嵌套对象的一种优雅方法
EN

Stack Overflow用户
提问于 2018-02-20 22:55:26
回答 2查看 339关注 0票数 2

给出一个JSON格式的示例文档,如下所示:

代码语言:javascript
复制
{
  "id": "post-1",
  "type": "blog-post",
  "tags": [
    {
      "id": "tag-1",
      "name": "Tag 1"
    },
    {
      "id": "tag-2",
      "name": "Tag 2"
    }
  ],
  "heading": "Post 1",
  "body": "this is my first blog post",
  "links": [
    {
      "id": "post-2",
      "heading": "Post 2",
      "tags": [
        {
          "id": "tag-1",
          "name": "Tag 1"
        },
        {
          "id": "tag-3",
          "name": "Tag 3"
        }
      ]
    }
  ],
  "metadata": {
    "user": {
      "social": [
        {
          "id": "twitter",
          "handle": "@user"
        },
        {
          "id": "facebook",
          "handle": "123456"
        },
        {
          "id": "youtube",
          "handle": "ABC123xyz"
        }
      ]
    },
    "categories": [
      {
        "name": "Category 1"
      },
      {
        "name": "Category 2"
      }
    ]
  }
}

我希望选取具有属性"id"以及父对象的属性名称的任何对象(无论深度如何)。上面的例子应该就是这样,一个例子。我不能自由分享的实际数据可以有任何深度,几乎可以有任何结构。可以在任何时候引入和删除属性。使用Blog Post风格只是因为它在示例中非常流行,而我的想象力非常有限。

属性表示域中的特定类型,该类型也可能(但不一定)编码到属性值中。

如果一个对象没有"id"属性,那么它就没有意义,不应该被选中。

一个非常重要的特殊情况是,当属性的值是一个对象数组时,在这种情况下,我需要保留属性名,并将其与数组中的每个元素相关联。

所需输出的示例如下:

代码语言:javascript
复制
[
  {
    "type": "tags",
    "node": {
      "id": "tag-1",
      "name": "Tag 1"
    }
  },
  {
    "type": "tags",
    "node": {
      "id": "tag-2",
      "name": "Tag 2"
    }
  },
  {
    "type": "links",
    "node": {
      "id": "post-2",
      "heading": "Post 2",
      "tags": [
        {
          "id": "tag-1",
          "name": "Tag 1"
        },
        {
          "id": "tag-3",
          "name": "Tag 3"
        }
      ]
    }
  },
  {
    "type": "tags",
    "node": {
      "id": "tag-1",
      "name": "Tag 1"
    }
  },
  {
    "type": "tags",
    "node": {
      "id": "tag-3",
      "name": "Tag 3"
    }
  },
  {
    "type": "social",
    "node": {
      "id": "twitter",
      "handle": "@user"
    }
  },
  {
    "type": "social",
    "node": {
      "id": "facebook",
      "handle": "123456"
    }
  },
  {
    "type": "social",
    "node": {
      "id": "youtube",
      "handle": "ABC123xyz"
    }
  }
]

输出没有严格的要求是相同的,例如,顺序与我的用例无关-它也可以分组。因为顶层对象有一个属性"id",所以可以用一个特殊的名称包括它,但我更希望它根本不包括在内。

我尝试过使用walkreducerecurse,但都无济于事,恐怕我的jq技能太有限了。但我认为一个好的解决方案应该至少使用其中的一个。

我想要一个类似这样的表达式

代码语言:javascript
复制
to_entries[] | .value | .. | select(has("id")?)

它可以选择正确的对象,但使用..时,我不能再保留相关的属性名称。

我想出的最好的办法是

代码语言:javascript
复制
. as $document
| [paths | if length > 1 and .[-1] == "id" then .[0:-1] else empty end] 
| map(. as $path 
      | $document 
      | { "type": [$path[] | if type == "string" then . else empty end][-1],
           "node": getpath($path) })

这是可行的,但感觉相当复杂,首先提取所有路径,忽略所有不具有"id"作为最后一个元素的路径,然后删除"id"段以获得实际对象的路径,并存储(到目前为止)作为字符串的段,它对应于包含感兴趣的对象的父对象属性。最后,通过getpath选择实际的对象。

有没有更优雅,或者至少更短的方式来表达这一点?

我应该注意到,我希望使用jq是为了方便地绑定到其他语言,以及能够在命令行上运行程序。

对于这个问题的范围,我真的对jq的替代方案不感兴趣,因为我可以想象如何使用其他工具以不同的方式解决这个问题,但我真的很想“只是”使用jq

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-02-21 01:12:58

由于实际需求对我来说并不清楚,我将假设给定的实现定义了功能需求,并提出了一个更简短、更时尚的版本:

代码语言:javascript
复制
. as $document
| paths
| select(length > 1 and .[-1] == "id")
| .[0:-1] as $path
| { "type": last($path[] | strings),
    "node": $document | getpath($path) }

这会产生一个流,所以如果你想要一个数组,你可以简单地将上面的内容括在方括号中。

如果流为空,则last(stream)将发出null,这与.[-1]的行为一致。

票数 1
EN

Stack Overflow用户

发布于 2018-02-21 01:06:00

这是可行的:

代码语言:javascript
复制
[
    foreach (paths | select(.[-1] == "id" and length > 1)[:-1]) as $path ({i:.};
        .o = {
            type: last($path[] | strings),
            node: (.i | getpath($path))
        };
        .o
    )
]

诀窍是要知道路径中的任何数字都表示该值是数组的一部分。您必须调整路径以获取父名称。但是使用带有字符串过滤器的last/1会让它变得更简单。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48888057

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档