Programing

jq 또는 대체 명령 줄 도구를 사용하여 JSON 파일 비교

lottogame 2020. 12. 12. 09:57
반응형

jq 또는 대체 명령 줄 도구를 사용하여 JSON 파일 비교


두 개의 JSON 파일이 in-dictionary-key 및 within-list-element 순서에 불변으로 동일한 지 확인하는 데 사용할 수있는 명령 줄 유틸리티가 있습니까?

jq또는 다른 동등한 도구를 사용 하여 수행 할 수 있습니까?

예 :

이 두 JSON 파일은 동일합니다.

A:

{
  "People": ["John", "Bryan"],
  "City": "Boston",
  "State": "MA"
}

B:

{
  "People": ["Bryan", "John"],
  "State": "MA",
  "City": "Boston"
}

하지만이 두 JSON 파일은 다릅니다.

A:

{
  "People": ["John", "Bryan", "Carla"],
  "City": "Boston",
  "State": "MA"
}

C:

{
  "People": ["Bryan", "John"],
  "State": "MA",
  "City": "Boston"
}

다음과 같습니다.

$ some_diff_command A.json B.json

$ some_diff_command A.json C.json
The files are not structurally identical

jq의 비교는 이미 키 순서를 고려하지 않고 객체를 비교하기 때문에 남은 것은 객체 내부의 모든 목록을 비교하기 전에 정렬하는 것입니다. 두 파일의 이름이 a.jsonand 라고 가정 b.json하고 최신 jq에서 야간에 다음을 수행합니다.

jq --argfile a a.json --argfile b b.json -n '($a | (.. | arrays) |= sort) as $a | ($b | (.. | arrays) |= sort) as $b | $a == $b'

이 프로그램은 요청한 동등성의 정의를 사용하여 객체가 동일한 지 여부에 따라 "true"또는 "false"를 반환해야합니다.

편집 : (.. | arrays) |= sort구조가 일부 가장자리의 경우 예상대로 실제로 작동하지 않습니다. 이 GitHub 문제 는 이유를 설명하고 다음과 같은 몇 가지 대안을 제공합니다.

def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (post_recurse | arrays) |= sort

위의 jq 호출에 적용됩니다.

jq --argfile a a.json --argfile b b.json -n 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); ($a | (post_recurse | arrays) |= sort) as $a | ($b | (post_recurse | arrays) |= sort) as $b | $a == $b'

원칙적으로 bash 또는 다른 고급 셸에 액세스 할 수있는 경우 다음과 같이 할 수 있습니다.

cmp <(jq -cS . A.json) <(jq -cS . B.json)

하위 프로세스 사용. 이렇게하면 정렬 된 키와 부동 소수점의 일관된 표현으로 json 형식이 지정됩니다. 이것이 같은 내용의 json이 다르게 인쇄되는 이유에 대해 제가 생각할 수있는 유일한 두 가지 이유입니다. 따라서 나중에 간단한 문자열 비교를 수행하면 적절한 테스트가 수행됩니다. bash를 사용할 수없는 경우 임시 파일로 동일한 결과를 얻을 수 있지만 깨끗하지 않다는 점도 주목할 가치가 있습니다.

방법으로 당신은 당신이 원하는 질문 진술 때문에 매우, 귀하의 질문에 대답하지 않습니다 ["John", "Bryan"]["Bryan", "John"]동일하게 비교할 수 있습니다. json은 집합의 개념이없고 목록 만 있으므로 구별되는 것으로 간주되어야합니다. 목록의 순서는 중요합니다. 균등하게 비교하려면 사용자 지정 비교를 작성해야하며 그렇게하려면 평등의 의미를 정의해야합니다. 순서가 모든 목록에 중요합니까 아니면 일부에만 중요합니까? 중복 요소는 어떻습니까? 또는 집합으로 표시되도록하고 요소가 문자열 인 경우 {"John": null, "Bryan": null}. 평등을 위해 비교할 때 순서는 중요하지 않습니다.

최신 정보

댓글 토론에서 : 왜 json이 동일하지 않은지 더 잘 알고 싶다면

diff <(jq -S . A.json) <(jq -S . B.json)

더 해석 가능한 출력을 생성합니다. vimdiff취향에 따라 비교하는 것이 더 나을 수 있습니다.


옵션 jd과 함께 사용 -set:

출력이 없다는 것은 차이가 없음을 의미합니다.

$ jd -set A.json B.json

차이점은 @ 경로 및 + 또는-로 표시됩니다.

$ jd -set A.json C.json

@ ["People",{}]
+ "Carla"

출력 차이는 -p옵션을 사용하여 패치 파일로 사용할 수도 있습니다 .

$ jd -set -o patch A.json C.json; jd -set -p patch B.json

{"City":"Boston","People":["John","Carla","Bryan"],"State":"MA"}

https://github.com/josephburnett/jd#command-line-usage


다음은 일반적인 함수 walk / 1을 사용하는 솔루션입니다 .

# Apply f to composite entities recursively, and to atoms
def walk(f):
  . as $in
  | if type == "object" then
      reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
  elif type == "array" then map( walk(f) ) | f
  else f
  end;

def normalize: walk(if type == "array" then sort else . end);

# Test whether the input and argument are equivalent
# in the sense that ordering within lists is immaterial:
def equiv(x): normalize == (x | normalize);

예:

{"a":[1,2,[3,4]]} | equiv( {"a": [[4,3], 2,1]} )

생성 :

true

그리고 bash 스크립트로 마무리했습니다.

#!/bin/bash

JQ=/usr/local/bin/jq
BN=$(basename $0)

function help {
  cat <<EOF

Syntax: $0 file1 file2

The two files are assumed each to contain one JSON entity.  This
script reports whether the two entities are equivalent in the sense
that their normalized values are equal, where normalization of all
component arrays is achieved by recursively sorting them, innermost first.

This script assumes that the jq of interest is $JQ if it exists and
otherwise that it is on the PATH.

EOF
  exit
}

if [ ! -x "$JQ" ] ; then JQ=jq ; fi

function die     { echo "$BN: $@" >&2 ; exit 1 ; }

if [ $# != 2 -o "$1" = -h  -o "$1" = --help ] ; then help ; exit ; fi

test -f "$1" || die "unable to find $1"
test -f "$2" || die "unable to find $2"

$JQ -r -n --argfile A "$1" --argfile B "$2" -f <(cat<<"EOF"
# Apply f to composite entities recursively, and to atoms
def walk(f):
  . as $in
  | if type == "object" then
      reduce keys[] as $key
        ( {}; . + { ($key):  ($in[$key] | walk(f)) } ) | f
  elif type == "array" then map( walk(f) ) | f
  else f
  end;

def normalize: walk(if type == "array" then sort else . end);

# Test whether the input and argument are equivalent
# in the sense that ordering within lists is immaterial:
def equiv(x): normalize == (x | normalize);

if $A | equiv($B) then empty else "\($A) is not equivalent to \($B)" end

EOF
)

POSTSCRIPT : walk / 1은 jq> 1.5 버전에 내장되어 있으므로 jq에 포함 된 경우 생략 할 수 있지만 jq 스크립트에 중복으로 포함해도 아무런 문제가 없습니다.

POST-POSTSCRIPT: The builtin version of walk has recently been changed so that it no longer sorts the keys within an object. Specifically, it uses keys_unsorted. For the task at hand, the version using keys should be used.


Perhaps you could use this sort and diff tool: http://novicelab.org/jsonsortdiff/ which first sorts the objects semantically and then compares it. It is based on https://www.npmjs.com/package/jsonabc


If you also want to see the differences, using @Erik's answer as inspiration and js-beautify:

$ echo '[{"name": "John", "age": 56}, {"name": "Mary", "age": 67}]' > file1.json
$ echo '[{"age": 56, "name": "John"}, {"name": "Mary", "age": 61}]' > file2.json

$ diff -u --color \
        <(jq -cS . file1.json | js-beautify -f -) \
        <(jq -cS . file2.json | js-beautify -f -)
--- /dev/fd/63  2016-10-18 13:03:59.397451598 +0200
+++ /dev/fd/62  2016-10-18 13:03:59.397451598 +0200
@@ -2,6 +2,6 @@
     "age": 56,
     "name": "John Smith"
 }, {
-    "age": 67,
+    "age": 61,
     "name": "Mary Stuart"
 }]

Pulling in the best from the top two answers to get a jq based json diff:

diff \
  <(jq -S 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (. | (post_recurse | arrays) |= sort)' "$original_json") \
  <(jq -S 'def post_recurse(f): def r: (f | select(. != null) | r), .; r; def post_recurse: post_recurse(.[]?); (. | (post_recurse | arrays) |= sort)' "$changed_json")

This takes the elegant array sorting solution from https://stackoverflow.com/a/31933234/538507 (which allows us to treat arrays as sets) and the clean bash redirection into diff from https://stackoverflow.com/a/37175540/538507 This addresses the case where you want a diff of two json files and the order of the array contents is not relevant.

참고URL : https://stackoverflow.com/questions/31930041/using-jq-or-alternative-command-line-tools-to-compare-json-files

반응형