【问题标题】:subtracting one json file from another in jq在jq中从另一个文件中减去一个json文件
【发布时间】:2018-01-25 08:33:11
【问题描述】:

有没有办法比较jq中的两个json文件?具体来说,如果对象出现在另一个 json 文件中,我希望能够从一个 json 文件中删除它们。基本上,从另一个文件中减去一个文件。如果我可以概括这一点,以便我可以定义对象的相等标准,那将是一个奖励,但这并不是绝对必要的,它可以严格基于对象相同。

所以更一般的情况是这样的。假设我有一个如下所示的文件:

[
  {
    "name": "Cynthia",
    "surname": "Craig",
    "isActive": true,
    "balance": "$2,426.88"
  },
  {
    "name": "Elise",
    "surname": "Long",
    "isActive": false,
    "balance": "$1,892.72"
  },
  {
    "name": "Hyde",
    "surname": "Adkins",
    "isActive": true,
    "balance": "$1,769.34"
  },
  {
    "name": "Matthews",
    "surname": "Jefferson",
    "isActive": true,
    "balance": "$1,991.42"
  },
  {
    "name": "Kris",
    "surname": "Norris",
    "isActive": false,
    "balance": "$2,137.11"
  }
]

我还有第二个文件,如下所示:

[
  {
    "name": "Cynthia",
    "surname": "Craig"
  },
  {
    "name": "Kris",
    "surname": "Norris"
  }
] 

我想从第一个文件中删除 name 和 surname 字段与第二个文件的对象匹配的所有对象,因此结果应如下所示:

[
  {
    "name": "Elise",
    "surname": "Long",
    "isActive": false,
    "balance": "$1,892.72"
  },
  {
    "name": "Hyde",
    "surname": "Adkins",
    "isActive": true,
    "balance": "$1,769.34"
  },
  {
    "name": "Matthews",
    "surname": "Jefferson",
    "isActive": true,
    "balance": "$1,991.42"
  }
] 

【问题讨论】:

  • 可以,但具体视具体情况而定,请查看stackoverflow.com/help/mcve
  • 在第二个文件的示例中,您需要删除 surname 键 ("surname": "Norris",) 后的尾随 ,,因为在 JSON 中,与其他语言不同,最后一个键后的尾随逗号一个对象是不合法的。

标签: arrays json object jq subtraction


【解决方案1】:

根据前两个目标,以下解决方案旨在通用、高效且尽可能简单。

通用性

为了通用性,让我们假设 $one 和 $two 是两个数组 JSON 实体,我们希望在 $one 中找到这些项目 $x 这样 ($x|filter) 不会出现在 map($two | filter) 中,其中filter 是任意过滤器。 (在本例中为{surname, name}。)

解决方案使用INDEX/1,在jq官方1.5发布后添加,所以我们先复现它的定义:

def INDEX(stream; idx_expr):
  reduce stream as $row ({};
    .[$row|idx_expr|
      if type != "string" then tojson
      else .
      end] |= $row);
def INDEX(idx_expr): INDEX(.[]; idx_expr);

效率

为了提高效率,我们需要使用 JSON 对象作为字典; 由于键必须是字符串,我们需要确保在转换对象时 到一个字符串,对象被规范化。为此,我们将normalize定义如下:

# Normalize the input with respect to the order of keys in objects
def normalize:
  . as $in
  | if type == "object" then reduce keys[] as $key
         ( {}; . + { ($key):  ($in[$key] | normalize) } ) 
    elif type == "array" then map( normalize )
    else .
    end;

要构造字典,我​​们只需应用 (normalize|tojson):

def todict(filter):
  INDEX(filter| normalize | tojson);

解决办法

解决方案现在很简单:

# select those items from the input stream for which 
# (normalize|tojson) is NOT in dict:
def MINUS(filter; $dict):
 select( $dict[filter | normalize | tojson] | not);

def difference($one; $two; filter):
  ($two | todict(filter)) as $dict
  | $one[] | MINUS( filter; $dict );

difference( $one; $two; {surname, name} )

调用

$ jq -n --argfile one one.json --argfile two two.json -f difference.jq

【讨论】:

    【解决方案2】:

    jq解决办法:

    jq --slurpfile s f2.json '[ .[] | . as $o | if (reduce $s[0][] as $i
         ([]; . + [($o | contains($i))]) | any) then empty else $o end ]' f1.json
    

    输出:

    [
      {
        "name": "Elise",
        "surname": "Long",
        "isActive": false,
        "balance": "$1,892.72"
      },
      {
        "name": "Hyde",
        "surname": "Adkins",
        "isActive": true,
        "balance": "$1,769.34"
      },
      {
        "name": "Matthews",
        "surname": "Jefferson",
        "isActive": true,
        "balance": "$1,991.42"
      }
    ]
    

    【讨论】:

      【解决方案3】:

      这是一个使用来自pull/1062--argfileproject/1 的解决方案

      def project(q):
          . as $in
        | reduce (q | if type == "object" then keys[] else .[] end) as $k (
            {}
            ; . + { ($k) : ($in[$k]) }
          )
      ;
      
        map(
          reduce $arg[] as $a (
              .
            ; select(project($a) != $a)
          )
          | values
        )
      

      如果您将“第二个”文件放在second.json 中,data.json 中的数据和filter.jq 中的上述过滤器可以使用

      jq -M --argfile arg second.json -f filter.jq data.json
      

      生产

      [
        {
          "name": "Elise",
          "surname": "Long",
          "isActive": false,
          "balance": "$1,892.72"
        },
        {
          "name": "Hyde",
          "surname": "Adkins",
          "isActive": true,
          "balance": "$1,769.34"
        },
        {
          "name": "Matthews",
          "surname": "Jefferson",
          "isActive": true,
          "balance": "$1,991.42"
        }
      ]
      

      如果您想修改对象的相等标准,可以将表达式 select(project($a) != $a) 替换为其他内容。

      稍微考虑一下,我们可以通过使用contains 来消除对project/1 的需求。这应该更有效,因为它消除了临时对象的构造。

        map(
          reduce $arg[] as $a (
              .
            ; select(.!=null and contains($a)==false)
          )
          | values
        )
      

      这可以使用any进一步简化:

      map(select(any(.; contains($arg[]))==false))
      

      足够短,可以直接在命令行上使用:

      jq -M --argfile arg second.json 'map(select(any(.; contains($arg[]))==false))' data.json
      

      【讨论】:

        猜你喜欢
        • 2019-07-26
        • 1970-01-01
        • 2021-12-22
        • 1970-01-01
        • 1970-01-01
        • 2016-09-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多