jq를 사용하여 특정 연결 키를 기반으로 매우 큰 JSON 파일을 분할합니다.

jq를 사용하여 특정 연결 키를 기반으로 매우 큰 JSON 파일을 분할합니다.

이러한 키(및 기타 여러 키)가 포함된 수백만 개의 항목이 포함된 배열을 포함하는 매우 큰 JSON 파일이 있다고 가정해 보겠습니다.

 {
        "name": "assets/fUCcxWczWT0",
        "displayName": "The House",
        "authorName": "John Smith",
        "resources": {"house" : "address","car":"bla"},
 }

jq특정 이름 키 앞에 있는 콘텐츠를 슬라이싱(제거)하는 방법은 무엇입니까 ? 예를 들어 고유하게 식별되는 요소 이전의 모든 것입니다 "name": "assets/fUCcxWczWT0".

위의 내용은 문서의 대표 요소를 단순화한 것입니다. 최상위 배열의 실제 요소는 다음과 같습니다.

{
  "name": "assets/4vds6twPsb7",
  "displayName": "pim",
  "authorName": "Erwin Braak",
  "createTime": "2017-12-06T11:52:40.557236Z",
  "updateTime": "2020-10-07T09:49:08.752848Z",
  "formats": [
    {
      "root": {
        "relativePath": "Pim.gltf",
        "url": "https://poly.googleapis.com/downloads/fp/1602064148752848/4vds6twPsb7/c8Ksvo0_VjG/Pim.gltf",
        "contentType": "model/gltf+json"
      },
      "resources": [
        {
          "relativePath": "Pim.bin",
          "url": "https://poly.googleapis.com/downloads/fp/1602064148752848/4vds6twPsb7/c8Ksvo0_VjG/Pim.bin",
          "contentType": "application/octet-stream"
        },
        {
          "relativePath": "C:/Users/PC/Desktop/pom/pimSurface_Color.png",
          "url": "https://poly.googleapis.com/downloads/fp/1602064148752848/4vds6twPsb7/c8Ksvo0_VjG/C:/Users/PC/Desktop/pom/pimSurface_Color.png",
          "contentType": "image/png"
        }
      ],
      "formatComplexity": {
        "triangleCount": "586149"
      },
      "formatType": "GLTF2"
    },
    {
      "root": {
        "relativePath": "Pim.obj",
        "url": "https://poly.googleapis.com/downloads/fp/1602064148752848/4vds6twPsb7/5QVGpvfauVK/Pim.obj",
        "contentType": "text/plain"
      },
      "resources": [
        {
          "relativePath": "Pim.mtl",
          "url": "https://poly.googleapis.com/downloads/fp/1602064148752848/4vds6twPsb7/5QVGpvfauVK/Pim.mtl",
          "contentType": "text/plain"
        },
        {
          "relativePath": "C:/Users/PC/Desktop/pom/pimSurface_Color.png",
          "url": "https://poly.googleapis.com/downloads/fp/1602064148752848/4vds6twPsb7/5QVGpvfauVK/C:/Users/PC/Desktop/pom/pimSurface_Color.png",
          "contentType": "image/png"
        }
      ],
      "formatComplexity": {
        "triangleCount": "586149"
      },
      "formatType": "OBJ"
    }
  ],
  "thumbnail": {
    "relativePath": "4vds6twPsb7.png",
    "url": "https://lh3.googleusercontent.com/C_il4QubLWYMAyrnGPMjXWx4E7MVYLZAoX_Hf-qr4WyHfebvf2y3lndh71A350g",
    "contentType": "image/png"
  },
  "license": "CREATIVE_COMMONS_BY",
  "visibility": "PUBLIC",
  "isCurated": true,
  "presentationParams": {
    "orientingRotation": {
      "w": 1
    },
    "colorSpace": "LINEAR",
    "backgroundColor": "#eeeeee"
  }
}

답변1

파일 크기 문제 외에도 효과적인 솔루션을 방해하는 주요 장애물은 다음과 같습니다 jq.모두객체, 이는 배열도 처리됨을 의미합니다.누구나전체적으로또는아무것도 없습니다. 따라서 요소를 포함하는 주요 큰 배열을 제거해야 후자를 원래 입력에서 (훨씬 더 작은) 개체의 스트림으로 처리할 수 있습니다.포함하다, 최종 출력.

이를 달성하려면 jq최종 출력 배열에도 배열 처리 기능이 사용되지 않도록 일부 텍스트 조작이 필요합니다. 몇 가지 변형을 생각해 볼 수 있는데, 각 변형에는 텍스트 조작을 통해 수행해야 하는 어느 정도의 작업이 있지만 이 조작을 배열 구조 구문 최종 출력만 추가하는 최소한의 작업으로 제한하기로 선택했습니다.

이 텍스트 조작은 매우 최소화되어 이 목적을 위해 자체 텍스트 구성을 사용할 수 있습니다 jq. 이를 통해 다른 도구에 의지하지 않고 순수한 솔루션을 가질 수 있을 뿐만 아니라 jq필요한 텍스트 비트스트림 흐름을 따라가면서 추가할 수도 있습니다. 예상된 들여쓰기/줄 바꿈 또는 기타 형식 가정에 의존하지 않으며, 이는 모두 "수동으로" 최종 출력 배열을 구축하는 보다 강력한 방법을 용이하게 합니다. 이러한 장점의 대가는 sed.

예제 입력의 "줄"은 다음과 같습니다.

jq -rn --stream --arg f name --arg q 'assets/fUCcxWczWT0' '"[", foreach fromstream(1|truncate_stream(inputs)) as $o ([null,null]; if .[1] or $o[$f]? == $q then [.[1], ","] else . end; .[0]//empty, if .[1] then $o else empty end), "]"'

사용된 옵션 jq. 올바른 결과를 얻으려면 처음 세 개가 필요합니다.

  • -r임의의 텍스트를 출력할 때 원시(예: JSON으로 변환되지 않음) 출력
  • -n내장된 소비를 통한 inputs입력 데이터 의 경우오직jq그렇지 않으면 입력의 첫 번째 전체 개체가 다음의 일반 루프에 의해 먹혀집니다.
  • --streamjq자체 스트리밍 모드를 사용하여 입력을 소비하는 데 사용됩니다.
  • --arg f name찾고 있는 값이 포함된 필드의 이름
  • --arg q 'assets/fUCcxWczWT0'각 객체의 필드에서 찾고 있는 값

jq확장 기능과 설명을 포함하여 스크립트를 개별적으로 살펴보세요 .

# Firstly, the initial bit of arbitrary text: the opening bracket for the final output,
# as required for a valid JSON array syntax

"[",

# Then a foreach statement, looping over the objects provided singularly, one by one,
# by the streamlined input
# NOTE 1: jq's streaming mode is used for this solution primarily so that we
#         can use `1 | truncate_stream()` here, which courteously (and natively)
#         strips the first (1 | ...) structure of the original input along the course of
#         the streamlined operation.
#         The first structure is obviously the main huge array containing the
#         objects, hence we receive these latter singularly in a truly streamlined
#         fashion, freed by the containment of the array
# NOTE 2: using `inputs | tostream` here in place of `--stream`, although functioning,
#         would not obtain the streaming goal, because it would first take the entire
#         input as a whole rather than streaming it from the start
    foreach fromstream( 1|truncate_stream(inputs) ) as $o (
        # the initial state of the loop: we use 2 values as a "shifting 2-value state-machine"
        # for the comma to be output (as text) along with all the elements except the
        # first. We use the second value as an overall state too
        [null, null];
        # here we look for the wanted value in the wanted field unless already found
        # previously according to the overall state, and we update the loop state "shifting"
        # the 2-state for the comma as soon as the wanted value is found
        if .[1] or $o[$f]? == $q then [.[1], ","] else . end;
        # here we print the comma text (if need be according to its 2-state)
        # as required for separating elements in a JSON array syntax, then we print
        # the object element itself if the overall state says so
        .[0]//empty, if .[1] then $o else empty end
    ),

# Lastly, the final bit of arbitrary text: the closing bracket for the output,
# as required for a valid JSON array syntax

"]"

몇 가지 최종 참고사항:

  • 분명히 이 접근 방식은 원본 입력이 거대하기는 하지만 기본적으로 단순하고 단순하다고 가정합니다(실제로 이 사실을 활용합니다). 내부 값과 외부 값 사이의 상호 참조가 있는 더 복잡한 전체 구조라면 스크립트에 쉽게 필요할 수 있습니다. 똑같이 더 복잡하고 난해해지다
  • 요소를 출력하고 싶다면에 따라대신 수배범에게에서 시작하다원하는 경우 스트리밍 논리는 더 간단할 수 있으며 상태가 전혀 필요하지 않을 수도 있습니다. 원칙적으로 halt원하는 객체가 발견될 때 스크립트만 작성할 수 있으므로 부작용으로 속도도 더 빨라질 수 있습니다(완전 스트리밍 작업).

답변2

문제를 단순화하면 다음과 같습니다. 전체 문서를 읽지 않고 배열의 처음 몇 요소를 제거하는 방법입니다.

[7,10,8,6,5,4,0,9,3,2,1]

...예를 들어 다음을 얻을 수 있습니다.

[4,0,9,3,2,1]

... 4솔루션에 대한 입력 쿼리는 어디에 있나요?

답변: 데이터가 메모리로 읽혀지는지 여부에 신경 쓰지 않고 먼저 입력 배열을 개별 숫자 집합으로 "폭발"한 다음 쿼리에 해당하는 첫 번째 숫자를 찾는 방법으로 이 문제를 더 간단하게 해결할 수 있습니다. 일단 발견되면, 발견된 요소와 모든 후속 숫자를 배열에 넣습니다. jq파이프라인에서 이를 두 번 호출하여 이를 수행합니다. 여기서 첫 번째 호출 jq은 한 줄에 하나씩 숫자 스트림을 생성합니다.

$ jq '.[]' file | jq -c --argjson query 4 'select(. == $query) as $elem | [$elem, inputs]'
[4,0,9,3,2,1]

문제는 입력 및 출력 데이터가 메모리에 저장되어야 한다는 것입니다. 입력은 jq첫 번째 프로세스에 의해 완전히 구문 분석되는 반면 출력은 두 번째 프로세스에 의해 메모리에 수집됩니다.

입력 데이터를 메모리에 저장하지 않는 방법을 찾을 수 있다면 좋을 것입니다. 이를 위해 스트리밍 기능을 사용해 볼 수 있습니다 jq. --stream를 사용하면 jq표현식은 전체 입력을 구문 분석하지 않고 입력 스트림의 현재 상태를 나타내는 배열 스트림을 수신합니다(참조:"스트림 미디어"매뉴얼 섹션 jq).

$ jq -c --stream --argjson query 4 'fromstream(select(.[1] == $query) as $elem | $elem, inputs | .[0][0] -= $elem[0][0])' file
[4,0,9,3,2,1]

jq명령은 배열의 스트림 표현을 읽고 쿼리에 해당하는 첫 번째 요소를 찾습니다. 전체 입력을 메모리에 저장하지 않고 이 작업을 수행합니다.

이 표현식은 배열 요소의 인덱스에 해당하는 .[1]배열 요소의 값으로 평가됩니다 . 일반적 으로 데이터는.[0][0].[0]원래 비스트리밍 데이터 세트의 값). 출력으로 받은 각 요소에 대해 발견된 요소의 인덱스로 오프셋하여 해당 요소의 인덱스를 다시 계산해야 합니다. 이렇게 하면 저장이 방지됩니다.입력하다그러나 비스트리밍 형식으로 변환하려면 출력을 메모리에 저장해야 할 수도 있습니다.

원시 데이터에 사용할 수 있는 명령에 위의 명령을 적용하는 것은 매우 간단합니다. 또한 키 값을 비교하기 전에 객체의 올바른 키를 테스트해야 합니다. 요소의 키는 에 저장됩니다 .[0][1].그러나 우리가 받은 이후로 주의하십시오불완전한 객체 스트림,켜다충분히name키를 기반으로 한 올바른 요소 name는 다음과 같습니다.첫 번째각 요소의 핵심. 두 번째 또는 세 번째 키인 경우 객체의 초기 부분을 삭제합니다.

$ cat file
[
   {
      "name": "assets/1",
      "authorName": "John Smith",
      "displayName": "The House",
      "resources": { "car": "bla", "house": "address" }
   },
   {
      "name": "assets/6",
      "authorName": "John Smith",
      "displayName": "The House",
      "resources": { "car": "bla", "house": "address" }
   },
   {
      "name": "assets/0",
      "authorName": "John Smith",
      "displayName": "The House",
      "resources": { "car": "bla", "house": "address" }
   },
   {
      "name": "assets/2",
      "authorName": "John Smith",
      "displayName": "The House",
      "resources": { "car": "bla", "house": "address" }
   }
]

assets/0다음은 값 으로 이 요소 앞에 있는 모든 요소를 ​​제거합니다 name.

$ jq --stream --arg query 'assets/0' 'fromstream(select(.[0][1] == "name" and .[1] == $query) as $elem | $elem, inputs | .[0][0] -= $elem[0][0])' file
[
  {
    "name": "assets/0",
    "authorName": "John Smith",
    "displayName": "The House",
    "resources": {
      "car": "bla",
      "house": "address"
    }
  },
  {
    "name": "assets/2",
    "authorName": "John Smith",
    "displayName": "The House",
    "resources": {
      "car": "bla",
      "house": "address"
    }
  }
]

쿼리 키가 각 객체의 첫 번째 키여야 한다는 제한 사항을 해결하려면 먼저 key를 기반으로 배열 요소의 첫 번째 인덱스를 결정한 name다음 해당 키를 사용하여 배열을 추출하는 2단계 작업을 수행할 수 있습니다. 요소. 데이터:

jq --stream --arg query 'assets/0' \
   'fromstream(select(.[0][1] == "name" and .[1] == $query) | [0,.[0][0]])' file |
   head -n 1 |
   jq --stream '.[1] as $index | fromstream(inputs | select(.[0][0] >= $index) | .[0][0] -= $index)' - file

위 파이프라인의 첫 번째는 jq우리가 찾고 있는 특정 값을 가진 필드의 모든 요소에 대한 인덱스를 출력합니다. 이 인덱스 중 첫 번째 인덱스를 name선택합니다 . head -n 1두 번째는 jq표준 입력의 정수를 내부 $index변수로 읽은 다음 입력 파일에서 인덱스가 같거나 그보다 큰 요소를 추출합니다(두 번째 읽기) $index.

관련 정보