테이블을 json으로 변환

테이블을 json으로 변환

JSON으로 변환하려는 대규모 데이터 테이블이 있는데 jq, mlr 또는 이와 유사한 도구가 부족한 awk 기술에 의존하지 않고도 이러한 작업을 수행할 수 있는지 확실하지 않습니다.

샘플 테이블:

Balance_sheet for AAPL:

                                                        2023-09-30      2022-09-30      2021-09-30      2020-09-30
Treasury Shares Number                                         0.0             NaN             NaN             NaN
Ordinary Shares Number                               15550061000.0   15943425000.0   16426786000.0   16976763000.0

선호하는 출력:

{
    "Balance_sheet for AAPL": {
        "Treasury Shares Number": {
            "2023-09-30": "0.0",
            "2022-09-30": "NaN",
            "2021-09-30": "NaN",
            "2020-09-30": "NaN"
        },
        "Ordinary Shares Number": {
            "2023-09-30": "15550061000.0",
            "2022-09-30": "15943425000.0",
            "2021-09-30": "16426786000.0",
            "2020-09-30": "16976763000.0"
        }
    }
}

다음 형식도 작동하지만 덜 이상적입니다.

{
    "Balance_sheet for AAPL": {
        "2023-09-30": {
            "Treasury Shares Number": "0.0",
            "Ordinary Shares Number": "15550061000.0"
        },
        "2022-09-30": {
            "Treasury Shares Number": "NaN",
            "Ordinary Shares Number": "15943425000.0"
        },
        "2021-09-30": {
            "Treasury Shares Number": "NaN",
            "Ordinary Shares Number": "16426786000.0"
        },
        "2020-09-30": {
            "Treasury Shares Number": "NaN",
            "Ordinary Shares Number": "16976763000.0"
        }
    }
}

이 작업을 수행하는 합리적인 방법을 아는 사람이 있습니까?

답변1

나는 다음을 사용할 것이다 perl:

$ perl -MJSON::PP -ae '
  if (/^(.*):$/) {$sheet = $1}
  elsif (/^\h+\d/) {$n = (@dates = @F)}
  elsif (/^(.*?)((?:\h+)\H+){$n}$/) {
    $i = -$n;
    $j{$sheet}->{$1} = {map {$_ => $F[$i++]} @dates}
  }
  END {print JSON::PP->new->pretty->encode(\%j)}' your-file
{
   "Balance_sheet for AAPL" : {
      "Ordinary Shares Number" : {
         "2023-09-30" : "15550061000.0",
         "2020-09-30" : "16976763000.0",
         "2022-09-30" : "15943425000.0",
         "2021-09-30" : "16426786000.0"
      },
      "Treasury Shares Number" : {
         "2020-09-30" : "NaN",
         "2023-09-30" : "0.0",
         "2021-09-30" : "NaN",
         "2022-09-30" : "NaN"
      }
   }
}

정규식을 기반으로 입력에서 3가지 유형의 라인을 구별합니다.

  • :현재 "시트"(최상위 개체의 키)를 결정하는 것
  • 하나 이상의 가로 공백으로 +시작 \h하고 그 뒤에 \d소수점 한 자리가 오는 줄, 이 숫자는 날짜(세 번째 수준 개체의 키)이며 배열에 기록 @dates하고 해당 숫자는 에 기록합니다 $n.
  • 최소한 $n공백으로 구분된 필드를 포함하는 행은 마지막 필드 앞의 부분이 두 번째 수준 개체의 키를 구성하고 키로 사용하고 마지막 필드를 값으로 $n사용하여 해당 키에 대한 세 번째 수준 개체를 만듭니다 .@dates$n
  • 다른 모든 것(예제 입력의 빈 줄)은 무시됩니다.

JSON 객체는 Perl 연관 배열을 표현하므로 멤버의 순서는 무작위입니다. 각 객체의 멤버가 키를 기준으로 정렬되는 canonical플래그( )를 설정하여 일관된 순서를 얻을 수 있습니다 .JSON::PP->new->pretty->canonical->encode(\%j)

JSON 개체의 필드 순서가 테이블의 순서를 반영하는 것이 중요한 경우(설명 참조) 다음을 perldoc JSON::PP수행할 수 있습니다.묶다다양한 유형의 해시에 대한 이러한 배열은 다음과 같은 것을 사용합니다.

$ perl -MData::Dumper -MTie::Hash::Indexed -MJSON::PP -ae '
  BEGIN{tie %j, $m = "Tie::Hash::Indexed"}
  if (/^(.*):$/) {tie my %s, $m; $j{$sheet = $1} = \%s}
  elsif (/^\h+\d/) {$n = (@dates = @F)}
  elsif (/^(.*?)((?:\h+)\H+){$n}$/) {
    tie my %s, $m;
    $i = -$n;
    %s = map {$_ => $F[$i++]} @dates;
    $j{$sheet}->{$1} = \%s
  }
  END {print JSON::PP->new->pretty->encode(\%j)}' your-file
{
   "Balance_sheet for AAPL" : {
      "Treasury Shares Number" : {
         "2023-09-30" : "0.0",
         "2022-09-30" : "NaN",
         "2021-09-30" : "NaN",
         "2020-09-30" : "NaN"
      },
      "Ordinary Shares Number" : {
         "2023-09-30" : "15550061000.0",
         "2022-09-30" : "15943425000.0",
         "2021-09-30" : "16426786000.0",
         "2020-09-30" : "16976763000.0"
      }
   }
}

Tie::Hash::Indexed( libtie-hash-indexed-perl데비안 패키지)는 순서화된 해싱을 제공하는 여러 모듈 중 하나입니다.

이것이 중요하다면 예상한 형식에 더 가까운 형식을 위해 ( for -style Pretty-printing) 으로 :바꾸십시오 (4개의 공백 들여쓰기 및 s 뒤에 공백이 있지만 이전에는 공백이 없음).prettyindent->indent_length(4)->space_afterindent_length(2)jq

답변2

POSIX awk를 사용하십시오.

$ cat tst.awk
BEGIN {
    inStep = 4
    print "{"
}

sub(/:$/,"") {
    indent = inStep
    printf "%*s\"%s\": {\n", indent, "", $0
    next
}

!numDates && /^[[:space:]]/ {
    numDates = split($0,dates)
    next
}

numDates && match($0,"[[:space:]]+([^[:space:]]+[[:space:]]*){"numDates"}$") {
    indent += inStep
    printf "%s%*s\"%s\": {\n", (numItems++ ? ",\n" : ""), indent, "", substr($0,1,RSTART-1)

    indent += inStep
    $0 = substr($0,RSTART,RLENGTH)
    for ( i=1; i<=numDates; i++ ) {
        printf "%*s\"%s\": \"%s\"%s\n", indent, "", dates[i], $i, (i<numDates ? "," : "")
    }
    indent -= inStep

    printf "%*s}", indent, ""
    indent -= inStep
}

END {
    printf "\n%*s}\n", indent, ""
    print "}"
}

$ awk -f tst.awk file
{
    "Balance_sheet for AAPL": {
        "Treasury Shares Number": {
            "2023-09-30": "0.0",
            "2022-09-30": "NaN",
            "2021-09-30": "NaN",
            "2020-09-30": "NaN"
        },
        "Ordinary Shares Number": {
            "2023-09-30": "15550061000.0",
            "2022-09-30": "15943425000.0",
            "2021-09-30": "16426786000.0",
            "2020-09-30": "16976763000.0"
        }
    }
}

여러 "대차대조표" 블록을 처리해야 하는 경우 다음을 추가하면 됩니다.

if ( numTables++ ) {
    printf "\n%*s},\n", indent, ""
}
numDates = numItems = 0

sub()예를 들어 다음 입력이 주어지면 이 줄 바로 아래에 있습니다 .

$ cat file2
Balance_sheet for AAPL:

                                                        2023-09-30      2022-09-30      2021-09-30      2020-09-30
Treasury Shares Number                                         0.0             NaN             NaN             NaN
Ordinary Shares Number                               15550061000.0   15943425000.0   16426786000.0   16976763000.0

Balance_sheet for foo:

                                                        2023-09-30      2022-09-30      2021-09-30      2020-09-30
Treasury Shares Number                                         0.0             NaN             NaN             NaN
Ordinary Shares Number                               15550061000.0   15943425000.0   16426786000.0   16976763000.0

이 스크립트는 다음과 같습니다.

$ cat tst.awk
BEGIN {
    inStep = 4
    print "{"
}

sub(/:$/,"") {
    if ( numTables++ ) {
        printf "\n%*s},\n", indent, ""
    }
    numDates = numItems = 0
    indent = inStep
    printf "%*s\"%s\": {\n", indent, "", $0
    next
}

!numDates && /^[[:space:]]/ {
    numDates = split($0,dates)
    next
}

numDates && match($0,"[[:space:]]+([^[:space:]]+[[:space:]]*){"numDates"}$") {
    indent += inStep
    printf "%s%*s\"%s\": {\n", (numItems++ ? ",\n" : ""), indent, "", substr($0,1,RSTART-1)

    indent += inStep
    $0 = substr($0,RSTART,RLENGTH)
    for ( i=1; i<=numDates; i++ ) {
        printf "%*s\"%s\": \"%s\"%s\n", indent, "", dates[i], $i, (i<numDates ? "," : "")
    }
    indent -= inStep

    printf "%*s}", indent, ""
    indent -= inStep
}

END {
    printf "\n%*s}\n", indent, ""
    print "}"
}

다음과 같은 출력이 생성됩니다.

$ awk -f tst.awk file2
{
    "Balance_sheet for AAPL": {
        "Treasury Shares Number": {
            "2023-09-30": "0.0",
            "2022-09-30": "NaN",
            "2021-09-30": "NaN",
            "2020-09-30": "NaN"
        },
        "Ordinary Shares Number": {
            "2023-09-30": "15550061000.0",
            "2022-09-30": "15943425000.0",
            "2021-09-30": "16426786000.0",
            "2020-09-30": "16976763000.0"
        }
    },
    "Balance_sheet for foo": {
        "Treasury Shares Number": {
            "2023-09-30": "0.0",
            "2022-09-30": "NaN",
            "2021-09-30": "NaN",
            "2020-09-30": "NaN"
        },
        "Ordinary Shares Number": {
            "2023-09-30": "15550061000.0",
            "2022-09-30": "15943425000.0",
            "2021-09-30": "16426786000.0",
            "2020-09-30": "16976763000.0"
        }
    }
}

답변3

사용행복하다(이전 Perl_6)

~$ raku -MJSON::Fast -e '
         my $a = lines[0..1].trim-trailing;         \
         my @a = slurp.map("Date" ~ *).lines.map:   \
             *.subst(:global, / <alpha>+ % " " /, { .trans(" " => "_") } ).split(/ \s+ /);  \
         @a = [Z] @a; my %h; for 1..^@a[0].elems -> $j {   \
             %h.append: @a.[0][$j] => %(@a.map( { $_.[0] => $_.[$j] } )[1..*]) };  \ 
         put to-json( $a => %h, :sorted-keys );'   file

위 내용은 Perl 계열의 프로그래밍 언어인 Raku로 작성된 답변입니다. OP에서 게시한 "대차대조표" 테스트 파일이 제한되어 있다는 점을 고려하면 "전처리" 테이블도 이에 따라 제한되며 사용자 정의될 수 있습니다. 따라서 데이터를 표준 형식으로 구성하는 데 사용되는 처음 몇 줄의 코드는 모든 "대차 대조표"를 처리하는 방법을 나타내는 것이 아니라 Raku 언어의 "맛"을 제공하는 것으로 해석되어야 합니다.

  1. Raku 모듈은 JSON::Fast명령줄에 로드됩니다.
  2. 첫 번째 문은 처음 두 줄을 $a스칼라로 읽고, trim-ming은 후행 공백을 제거합니다.
  3. 두 번째 진술 ). slurp파일의 나머지 부분을 메모리에 저장하고, 두번째). 접두사 "Date"문자열, ). 모든 것을 lines,). 각각은 다음과 같이 입력 line됩니다 .map이자형). 공백에 밑줄이 그어 지도록 subst단일 문자 공백으로 구분된 문자 패턴을 디자인하여 줄 레이블에서 공백을 제거합니다. 그럼 마침내% " "trans_에프). 데이터는 나머지 \s+공백으로 분할되며 이(현재 직사각형) 테이블은 @a배열에 저장됩니다.
  4. 행이 열이 되고 그 반대로 테이블 @a[Z]"zip" 변환됩니다. 직사각형 데이터가 아닌 다른 항목에 이 연산자를 사용할 경우 주의하십시오.
  5. %h해시 값이 선언됩니다.
  6. "공유" 데이터 열의 수를 반복합니다.). %()키-값 쌍을 포함하는 익명 해시를 생성합니다. 각 쌍에는 다음과 같은 날짜(행 레이블)가 있습니다.열쇠해당 "공유" 데이터 열의 경우(여기서 인덱스를 사용하면 [1..*]중복된 "태그" 쌍이 제거됩니다).
  7. 다른 레벨을 추가하고,두번째). 적절한 "공유"(즉, 열) 레이블은 다음과 같습니다.열쇠익명(날짜/주식) 해시 의 경우 %()두 번째 수준이 됩니다.. 키/값으로 변환된 "날짜/공유" 열은 해시값 append으로 편집 됩니다.%h
  8. 마지막으로 (정말 잘해요)이 시점에서) $a"Balance_Sheet" 헤더가 다음과 같이 다시 추가됩니다.열쇠해시 %h하다, 그리고 이 (이제는 다중 레벨) 최종 해시 테이블이 변환되고 to-json()명명 :sorted-keys된 매개변수가 추가되어 출력됩니다 put.

입력 예:

Balance_sheet for AAPL:

                                                        2023-09-30      2022-09-30      2021-09-30      2020-09-30
Treasury Shares Number                                         0.0             NaN             NaN             NaN
Ordinary Shares Number                               15550061000.0   15943425000.0   16426786000.0   16976763000.0

변환된 예제 입력 시트 [Z]("Balance_Sheet" 헤더 행 제외):

(Date Treasury_Shares_Number Ordinary_Shares_Number)
(2023-09-30 0.0 15550061000.0)
(2022-09-30 NaN 15943425000.0)
(2021-09-30 NaN 16426786000.0)
(2020-09-30 NaN 16976763000.0)

최종 JSON출력:

{
  "Balance_sheet for AAPL:": {
    "Ordinary_Shares_Number": {
      "2020-09-30": "16976763000.0",
      "2021-09-30": "16426786000.0",
      "2022-09-30": "15943425000.0",
      "2023-09-30": "15550061000.0"
    },
    "Treasury_Shares_Number": {
      "2020-09-30": "NaN",
      "2021-09-30": "NaN",
      "2022-09-30": "NaN",
      "2023-09-30": "0.0"
    }
  }
}

https://raku.land/cpan:TIMOTIMO/JSON::빠른
https://docs.raku.org/언어/hashmap
https://docs.raku.org/
https://raku.org

관련 정보