使用jq解析两个列表中存在的键(即使其中一个列表中可能不存在)

时间:2017-10-10 16:27:49

标签: json bash jq

(很难想出一个总结问题的标题,所以随时改进它。)

我有一个包含以下内容的JSON文件:

{
    "Items": [
        {
            "ID": {
                "S": "ID_Complete"
            }, 
            "oldProperties": {
                "L": [
                    {
                        "S": "[property_A : value_A_old]"
                    }, 
                    {
                        "S": "[property_B : value_B_old]"
                    }
                ]
            },
            "newProperties": {
                "L": [
                    {
                        "S": "[property_A : value_A_new]"
                    }, 
                    {
                        "S": "[property_B : value_B_new]"
                    }
                ]
            }
        }, 
        {
            "ID": {
                "S": "ID_Incomplete"
            }, 
            "oldProperties": {
                "L": [
                    {
                        "S": "[property_B : value_B_old]"
                    }
                ]
            },
            "newProperties": {
                "L": [
                    {
                        "S": "[property_A : value_A_new]"
                    }, 
                    {
                        "S": "[property_B : value_B_new]"
                    }
                ]
            }
        }
    ]
}

我想使用jq操纵数据,以便Items[]中每个具有 值的项目 property_A (在 newProperties 列表下)生成一个输出,其中包含 id old new (请参阅下面的所需输出)字段,无论该属性在 oldProperties 列表中的值如何。此外,如果 oldProperties 中不存在 property_A ,我仍然需要填充字段null(或任何固定的字符串,用于它的价值)。

期望的输出:

{
  "id": "id_Complete",
  "old": "[property_A : value_A_old]",
  "new": "[property_A : value_A_new]"
}
{
  "id": "ID_Incomplete",
  "old": null,
  "new": "[property_A : value_A_new]"
}

注意:即使 oldProperties 列表中不存在 property_A ,其他属性也可能(和将存在。

我面临的问题是,当 oldProperties 列表中不存在所需的属性时,我无法获得输出。我当前的jq命令如下所示:

jq -r '.Items[] | 
    { id:.ID.S, 
      old:.oldProperties.L[].S | select(. | contains("property_A")),
      new:.newProperties.L[].S | select(. | contains("property_A")) }'

只渲染 ID_Complete 的情况,而我也需要另一个。

使用此工具有没有办法实现这一目标?

提前致谢。

2 个答案:

答案 0 :(得分:2)

您的属性列表似乎是某个对象的值。您可以将它们映射到对象中,然后对对象进行差异,然后报告结果。

你可以这样做:

def make_object_from_properties:
      [.L[].S | capture("\\[(?<key>\\w+) : (?<value>\\w+)\\]")]
    | from_entries
    ;
def diff_objects($old; $new):
      def _prop($key): select(has($key))[$key];
      ([($old | keys[]), ($new | keys[])] | unique) as $keys
    | [   $keys[] as $k
        | ({ value: $old | _prop($k) } // { none: true }) as $o
        | ({ value: $new | _prop($k) } // { none: true }) as $n
        | (if   $o.none                 then "add"
          elif  $n.none                 then "remove"
          elif  $o.value != $n.value    then "change"
                                        else "same"
          end) as $s
        | { key: $k, status: $s, old: $o.value, new: $n.value }
      ]
  ;
def diff_properties:
      (.oldProperties | make_object_from_properties) as $old
    | (.newProperties | make_object_from_properties) as $new
    | diff_objects($old; $new) as $diff
    | foreach $diff[] as $d ({ id: .ID.S };
          select($d.status != "same")
        | .old = ((select(any("remove", "change"; . == $d.status)) | "[\($d.key) : \($d.old)]") // null)
        | .new = ((select(any("add", "change";    . == $d.status)) | "[\($d.key) : \($d.new)]") // null)
      )
    ;
[.Items[] | diff_properties]

这会产生以下输出:

[
  {
    "id": "ID_Complete",
    "old": "[property_A : value_A_old]",
    "new": "[property_A : value_A_new]"
  },
  {
    "id": "ID_Complete",
    "old": "[property_B : value_B_old]",
    "new": "[property_B : value_B_new]"
  },
  {
    "id": "ID_Incomplete",
    "old": null,
    "new": "[property_A : value_A_new]"
  },
  {
    "id": "ID_Incomplete",
    "old": "[property_B : value_B_old]",
    "new": "[property_B : value_B_new]"
  }
]

您的数据似乎也是某种编码格式。对于更强大的解决方案,您应该考虑定义一些函数来解码它们。考虑找到here的方法,了解如何做到这一点。

答案 1 :(得分:1)

此过滤器产生所需的输出。

def parse: capture("(?<key>\\w+)\\s*:\\s*(?<value>\\w+)") ;
def print: "[\(.key) : \(.value)]";
def norm:   [.[][][] | parse | select(.key=="property_A") | print][0];

  .Items
| map({id:.ID.S, old:.oldProperties|norm, new:.newProperties|norm})[]

示例运行(假定filter.jq中的过滤器和data.json中的数据)

$ jq -M -f filter.jq data.json
{
  "id": "ID_Complete",
  "old": "[property_A : value_A_old]",
  "new": "[property_A : value_A_new]"
}
{
  "id": "ID_Incomplete",
  "old": null,
  "new": "[property_A : value_A_new]"
}

Try it online!