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