【问题标题】:overwrite a json file with objects from another json file using jq使用 jq 用另一个 json 文件中的对象覆盖 json 文件
【发布时间】:2017-10-30 19:29:35
【问题描述】:

修改:

您好,我在合并 2 个文件时遇到问题,基本上我有 2 个具有这种结构的 json 文件:

[
  {
    "uri": "some/url.feature",
    "id": "safety-tests",
    "keyword": "Feature",
    "name": "Safety Tests",
    "description": "Some description",
    "line": 2,
    "tags": [
      {
        "name": "@sometag",
        "line": 1
      }
    ],
    "elements": [
      {
        "id": "some-element-id",
        "keyword": "Scenario Outline",
        "name": ": Some scenario name",
        "description": "",
        "line": 46,
        "type": "scenario",
        "tags": [
          {
            "name": "@sometag",
            "line": 1
          },
          {
            "name": "@someothertag",
            "line": 31
          }
        ],
        "before": [
          {
            "match": {
              "location": "some/test/file.rb:201"
            },
            "result": {
              "status": "passed",
              "duration": 15000
            }
          },
          {
            "match": {
              "location": "some/other/file.rb:5"
            },
            "result": {
              "status": "passed",
              "duration": 1722192000
            }
          }
        ],
        "steps": [
          {
            "keyword": "Given ",
            "name": "Some step name",
            "line": 46,
            "output": [
              "Some output"
            ],
            "match": {
              "location": "some/other/path/to/other/file.rb:137"
            },
            "result": {
              "status": "passed",
              "duration": 989158000
            }
          },
          {
            "keyword": "When ",
            "name": "some other step",
            "line": 46,
            "output": [
              "WARNING: static wait for 1 seconds."
            ],
            "match": {
              "location": "some/other/path/to/other/file.rb:80"
            },
            "result": {
              "status": "passed",
              "duration": 2700052000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:38"
            },
            "result": {
              "status": "passed",
              "duration": 954225000
            }
          },
          {
            "keyword": "Then ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 38792000
            }
          },
          {
            "keyword": "And ",
            "name": "And again some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 39268000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 55637000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 38375000
            }
          },
          {
            "keyword": "When ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:12"
            },
            "result": {
              "status": "passed",
              "duration": 751416000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 28043000
            }
          },
          {
            "keyword": "Then ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:20"
            },
            "result": {
              "status": "passed",
              "duration": 5204000
            }
          }
        ],
        "after": [
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:91"
            },
            "result": {
              "status": "passed",
              "duration": 20000
            }
          },
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:52"
            },
            "result": {
              "status": "passed",
              "duration": 5585000
            }
          },
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:27"
            },
            "result": {
              "status": "passed",
              "duration": 168146000
            }
          },
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:428"
            },
            "result": {
              "status": "passed",
              "duration": 62000
            }
          }
        ]
      },
      {
        "id": "some-element-id",
        "keyword": "Scenario Outline",
        "name": ": Some scenario name",
        "description": "",
        "line": 46,
        "type": "scenario",
        "tags": [
          {
            "name": "@sometag",
            "line": 1
          },
          {
            "name": "@someothertag",
            "line": 31
          }
        ],
        "before": [
          {
            "match": {
              "location": "some/test/file.rb:201"
            },
            "result": {
              "status": "passed",
              "duration": 15000
            }
          },
          {
            "match": {
              "location": "some/other/file.rb:5"
            },
            "result": {
              "status": "passed",
              "duration": 1722192000
            }
          }
        ],
        "steps": [
          {
            "keyword": "Given ",
            "name": "Some step name",
            "line": 46,
            "output": [
              "Some output"
            ],
            "match": {
              "location": "some/other/path/to/other/file.rb:137"
            },
            "result": {
              "status": "passed",
              "duration": 989158000
            }
          },
          {
            "keyword": "When ",
            "name": "some other step",
            "line": 46,
            "output": [
              "WARNING: static wait for 1 seconds."
            ],
            "match": {
              "location": "some/other/path/to/other/file.rb:80"
            },
            "result": {
              "status": "passed",
              "duration": 2700052000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:38"
            },
            "result": {
              "status": "passed",
              "duration": 954225000
            }
          },
          {
            "keyword": "Then ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 38792000
            }
          },
          {
            "keyword": "And ",
            "name": "And again some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 39268000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 55637000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 38375000
            }
          },
          {
            "keyword": "When ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:12"
            },
            "result": {
              "status": "passed",
              "duration": 751416000
            }
          },
          {
            "keyword": "And ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:2"
            },
            "result": {
              "status": "passed",
              "duration": 28043000
            }
          },
          {
            "keyword": "Then ",
            "name": "Some other step name",
            "line": 46,
            "match": {
              "location": "some/other/path/to/other/file.rb:20"
            },
            "result": {
              "status": "passed",
              "duration": 5204000
            }
          }
        ],
        "after": [
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:91"
            },
            "result": {
              "status": "passed",
              "duration": 20000
            }
          },
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:52"
            },
            "result": {
              "status": "passed",
              "duration": 5585000
            }
          },
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:27"
            },
            "result": {
              "status": "passed",
              "duration": 168146000
            }
          },
          {
            "match": {
              "location": "some/other/path/to/other/file.rb:428"
            },
            "result": {
              "status": "passed",
              "duration": 62000
            }
          }
        ]
      }
    ]
  }
]

其中elements 可以在任一文件中包含任意数量的对象。这些是来自黄瓜的测试结果,因此通常文件 A 包含的元素比文件 B 多,因为文件 B 是文件 A 中失败测试的重新运行。

例如。如果在第一遍我们运行了 100 个测试,文件 A elements 数组将包含 100 个具有上述格式的对象。但是,如果这 100 个测试中有 50 个失败,则文件 B elements 数组将包含 50 个对象。我想要做的是用文件 B 覆盖文件 A elements 数组,只需添加在两者中重复的元素。像

如果文件 A 有

"elements":[{a:1, b:2, c:3, d:2, e:9, f:4}]

和文件 B 有

"elements":[{d:5}]

我希望新文件有

"elements":[{a:1, b:2, c:3, d:5, e:9, f:4}]

到现在为止

jq '.[].elements' path/to/file/b > path/to/new/file
jq --argfile file path/to/new/file '.[].elements += $file' path/to/file/b

这将文件 B 包含在文件 A 中的 elements 数组中的 elements 数组中的任何内容放在一起,但不会删除其中的重复对象。

我尝试使用unique,但不知道如何使用它。有什么想法吗?

在这里我得到了一些回应后

jq --argfile b ~/Desktop/cucumber-rerun.json '.[0].elements[4] *= $b[0].elements[0]' ~/Desktop/cucumber.json

因为在我的实际示例中,我知道文件 A 中的元素 4 是我想用文件 B 中唯一的元素 1 覆盖的元素。但这对我不起作用,因为这两个文件都是自动生成的并且对象的顺序是未知的。

我想要一个命令来查看两个文件的比较,并自动检测来自 A 和 B 的重复对象,并用 B 中的对象覆盖 A 中的对象

【问题讨论】:

    标签: json file merge jq


    【解决方案1】:

    这是一个使用Object Multiplication 的解决方案。假设您的数据位于A.jsonB.json

    $ jq -M --argfile b B.json '.[0].elements[0] *= $b[0].elements[0]' A.json
    

    生产

    [
      {
        "uri": "https://someurl.com",
        "id": "some-id",
        "keyword": "SomeKeyword",
        "name": "Some Name",
        "description": "Some description for that test result",
        "line": 2,
        "tags": [
          {
            "name": "@sometag",
            "line": 1
          }
        ],
        "elements": [
          {
            "a": 5,
            "b": 2
          }
        ]
      }
    ]
    

    如果您的数组包含更多数据,则此方法很容易推广,但您需要了解应如何识别相应的元素。


    关于修改后的问题,这里有一个过滤器,它使用具有相同.idB.json 的相应对象更新A.json 的对象:

    def INDEX(stream; idx_expr):
      reduce stream as $row ({};
        .[$row|idx_expr| if type != "string" then tojson else . end] |= $row);
    
    def merge_by_id(a;b):
      if b then INDEX(a[];.id) * INDEX(b[];.id) | map(.) else a end;
    
      INDEX($b[];.id) as $i
    | map( .elements = merge_by_id(.elements; $i[.id].elements) )
    

    例如如果上面的过滤器在filter.jq中,A.json包含修改后的样本数据,B.json包含

    [
      {
        "id": "safety-tests",
        "elements": [
          {
            "id": "some-element-id",
            "description": "updated description"
          }
        ]
      }
    ]
    

    命令

    $ jq -M --argfile b B.json -f filter.jq A.json
    

    产生结果

    [
      {
        "uri": "some/url.feature",
        "id": "safety-tests",                      <------ top level .id
        ...
        "elements": [
          {
            "id": "some-element-id",               <------ element .id
            "keyword": "Scenario Outline",
            "name": ": Some scenario name",
            "description": "updated description",  <------ updated value
            "line": 46,
            "type": "scenario",
            ...
    

    请注意,上述解决方案假定A.json 中的元素.id 是唯一的,否则merge_by_id 将不会产生所需的输出。在这种情况下,以下过滤器就足够了:

    def INDEX(stream; idx_expr):
      reduce stream as $row ({};
        .[$row|idx_expr| if type != "string" then tojson else . end] |= $row);
    
      (INDEX($b[];.id) | map_values(INDEX(.elements[];.id))) as $i
    | map( $i[.id] as $o | if $o then .elements |= map($o[.id]//.) else . end )
    

    此过滤器只要求 B.json 中的对象的 .id 是唯一的。如果A.jsonB.json 中可能存在非唯一元素,则需要更复杂的映射。

    这是一个带有 cmets 的过滤器版本:

    def INDEX(stream; idx_expr):
      reduce stream as $row ({};
        .[$row|idx_expr| if type != "string" then tojson else . end] |= $row);
    
      # first create a lookup table for elements from B.json
      (                                         #       [{id:x, elements:[{id:y, ...}]}]
          INDEX($b[];.id)                       # -> {x: {id:x, elements:[{id:y, ...}]}..}
        | map_values(INDEX(.elements[];.id))    # -> {x: {y: {id:y, ...}}}
      ) as $i
    
      # update A.json objects
    | map(                                      # for each object in A.json
        $i[.id] as $o                           # do we have updated values from B.json ?
      | if $o then .elements |= map($o[.id]//.) # if so then replace corresponding elements
        else . end                              # otherwise leave object unchanged
      )
    

    【讨论】:

    • 这似乎很接近,但似乎用 B 的第一个元素替换了 A 的第一个元素......尽管在我的情况下,对象可能不在同一个位置,我希望它能够识别它们并覆盖正确的。
    • 是的,这就是我的意思,如果您的数据比您提供的样本更复杂,您需要了解对应关系。正如你的问题没有说明应该如何处理。如果您使用更多详细信息修改您的问题以涵盖各种情况,例如当 A.json 或 B.json 的顶级数组包含多个元素或 .elements 数组包含多个元素时,我可以轻松修改答案元素,但目前你让事情变得模棱两可。最好有一个更完整的例子。
    • @Gus 给出的解决方案已经解决了 A:[{"a":1, "b":2, "c":3, "d":2, "e":9, "f":4}] 和 B:[{"d":5}]"elements" 情况,因为这些数组中的每一个都包含一个对象。你没有描述的是"elements"案例[][{}][{"a":1},{"a":2},{"a":3}][{"a":1},{"b":2,"a":1},{"b":1}]等等。如果 A 或 B 有任何 "elements": ,你会期待什么?例如。如果 A 有 "elements":[{"a":1},{"b":2,"a":1},{"b":1}] 而 B 有 "elements":[{"c":1},{"b":0,"c":2,"a":9}] 最终结果应该是什么?
    • 我想我希望最后的事情是"elements":[{"a":1},{"b":0,"c":2,"a":9},{"b":1}],但我现在不确定......从我编辑的问题中,我想使用"elements": [{"id": "some-element-id",...该ID作为比较点
    • 如果 A 的 ith 元素用 B 的 ith 元素更新,我认为结果将是 [{"a":1,"c":1},{"b":0,"c":2","a":9},{"b":1}]。回到您修改后的问题,您的意思是如果 A 元素是 [{"id":1,"a":1},{"id":2,"b":1}] 而 B 元素是 [{"id":2,"a":2},{"id":3,"a":1}],结果会是 [{"id":1,"a":1},"{"id":2,"a":2,"b":2},{"id":3,"a":3}] 吗?
    【解决方案2】:

    简明扼要地解决您的问题需要两个关键见解:

    • 如果 A 和 B 是两个对象,那么您可以将它们组合起来,并通过编写:A + B

    • 将优先级赋予 B
    • 要“就地”更新对象,我们可以根据需要使用 |= 或 +=。

    在您的情况下,我们可以通过以下方式应用这些见解:

    .[0].elements[0] += $B[0].elements[0]
    

    假设调用如下:

    jq --argfile B B.json -f combine.jq A.json 
    

    输出

    根据您的输入,输出包括:

    "elements": [
      {
        "a": 5,
        "b": 2
      }
    ]
    

    根据要求。

    补充说明

    要使用上述 jq 调用产生的输出覆盖 A.json,您可能需要使用 sponge,但请注意风险。

    如果您出于某种原因不想使用--argfile,则可以改用--slurpfile,但是您必须写$B[0][0]

    【讨论】:

    • (1) 程序生成的“元素”输出与您指定的完全一样。 (2) 你的新要求不清楚。也许你应该问一个新的 SO 问题?
    • this jq --argfile b ~/Desktop/cucumber-rerun.json '.[0].elements[4] *= $b[0].elements[0]' ~/Desktop/cucumber.json 解决了这个特殊情况。但是我不会总是知道元素的顺序。我想让它自动检测哪一个是重复的并替换它
    • “自动检测”是什么意思?您应该开始一个新的 SO 问题,指定您的要求并给出简洁的示例。
    • @Gus - 据我所知,这里给出的解决方案符合您的要求。您应该想出一个显示预期输入和预期输出的 MINIMAL COMPLETE 示例(请参阅stackoverflow.com/help/mcve)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    相关资源
    最近更新 更多