将表转换为 json

jes*_*e_b 1 text-processing 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
Run Code Online (Sandbox Code Playgroud)

首选输出:

{
    "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"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下格式也可以使用,但不太理想:

{
    "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"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有谁知道完成此任务的明智方法?

Sté*_*las 5

我会用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"
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

根据正则表达式区分输入中的 3 种类型的行:

  • 那些以:决定当前“工作表”结尾的内容(顶级对象中的键)
  • 以至少一个 ( +)\h水平空格开头的行,后跟一位\d小数位,这些数字是日期(第三级对象中的键),我们将它们记录在数组中@dates,并将它们的编号记录在$n.
  • 至少包含$n空白分隔字段的行,其中最后一个$n字段之前的部分构成第二级对象中的键,并且我们使用 as@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"
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

Tie::Hash::Indexedlibtie-hash-indexed-perlDebian 软件包)是提供有序哈希的几个此类模块之一。

如果这很重要,则对于更接近您期望的格式(具有 4 个空格缩进并且 s 之后但不是之前的空格)的格式,:请替换prettyindent->indent_length(4)->space_after( indent_length(2)for jq-style Pretty-printing)。