perl 将JSON转换为CSV/TSV

wtzytmuj  于 2022-11-15  发布在  Perl
关注(0)|答案(3)|浏览(261)

我正在尝试将JSON格式的数据(https://rest.kegg.jp/get/br:ko00001/json)转换为CSV/TSV。我已经能够在awk和sed中完成此操作,但我正在学习Perl以用于更大的项目,因此学习在没有JSON模块的情况下完成此操作会很有帮助。

sed -E 's/^\t{2}"name"/\t\t"level 1"/g;s/^\t{3}"name"/\t\t\t"level 2"/g;s/^\t{4}"name"/\t\t\t\t"level 3"/g;s/^\t{5}"name"/\t\t\t\t\t"level 4"/g' json.json | awk 'BEGIN {OFS="\t"} NR > 4 {match($0, /"([^"]+)": *("[^"]*")/, a)} {tag = a[1]; val = gensub(/^"|"$/, "", "g", a[2]); f[tag] = val; if (tag == "level 4") {print f["level 1"], f["level 2"], f["level 3"], f["level 4"]}}' > table.tsv

上面是我如何通过awk和sed制作的。json.json是从链接下载的。
下面是我到目前为止在没有JSON模块的情况下在Perl中所做的尝试。我想通过这种方式来了解数据结构和Perl的工作原理。

use strict;

my $brite_hierarchy_filepath = shift @ARGV;

open my $brite_hierarchy, '<:utf8', $brite_hierarchy_filepath or die q{Can't open $brite_hierarchy_filepath: $!\n};

while (my $line = <$brite_hierarchy>) {
    next if $. == 4;
    chomp $line;

    $line =~ s/\A\t{2}"name"/"level_1"/;           
    $line =~ s/\A\t{3}"name"/"level_2"/;         
    $line =~ s/\A\t{4}"name"/"level_3"/;
    $line =~ s/\A\t{5}"name"/"level_4"/;

    my ($tag) = $line =~ /\A"(.*?)"/; 
    my ($value) = $line =~ /\A"level_[1-4]":"(.*?)"/;
    my %field = ($tag => $value) unless $tag eq "" && $value eq "";

    for (keys %field) {
        print join("\t", $field{"level_1"}, $field{"level_2"}, $field{"level_3"}, $field{"level_4"}, "\n");
    };
    last if eof $brite_hierarchy;
};

这是数据的简要外观。

{
        "name":"ko00001",
        "children":[
        {
            "name":"09100 Metabolism",
            "children":[
            {
                "name":"09101 Carbohydrate metabolism",
                "children":[
                {
                    "name":"00010 Glycolysis \/ Gluconeogenesis [PATH:ko00010]",
                    "children":[
                    {
                        "name":"K00844  HK; hexokinase [EC:2.7.1.1]"
                    },
                    {
                        "name":"K12407  GCK; glucokinase [EC:2.7.1.2]"
                    },
                    {
                        "name":"K00845  glk; glucokinase [EC:2.7.1.2]"
...

和TSV格式的所需输出。

09100 Metabolism    09101 Carbohydrate metabolism   00010 Glycolysis \/ Gluconeogenesis [PATH:ko00010]  K00844  HK; hexokinase [EC:2.7.1.1]
09100 Metabolism    09101 Carbohydrate metabolism   00010 Glycolysis \/ Gluconeogenesis [PATH:ko00010]  K12407  GCK; glucokinase [EC:2.7.1.2]
09100 Metabolism    09101 Carbohydrate metabolism   00010 Glycolysis \/ Gluconeogenesis [PATH:ko00010]  K00845  glk; glucokinase [EC:2.7.1.2]
hlswsv35

hlswsv351#

我总是建议使用JSON解析器,但如果您能保证格式永远不变,您确实可以将其视为固定的文本文件。在生产环境中,您通常无法做到这一点。但如果它是一次性的,那么它当然可以工作。
您粘贴到问题中的示例输入包含空格,而不是制表符,因此您的代码无法在其上工作。我的代码也无法在其上工作。我的输入是从您的链接复制的,并且包含制表符。
你的正则表达式模式看起来有点复杂。你可以总是有相同的简单模式,但是只需要改变每个名字前面的制表符的数量。诀窍是当你发现一个名字不是最后一列时跳到下一行,并在第一列重置整个结构。我选择使用数组而不是哈希。因为这样更有意义,我们可以在以后输出的时候使用join。最后,say类似于print,但是有一个内置的换行符。

use strict;
use warnings;
use feature 'say';

my @names;
while (<DATA>) {
    if ( m/^\t"name":"(.+)"/) {
        undef @names;
        $names[0] = $1;
        next;
    }
    if (m/^\t\t"name":"(.+)"/) {
        $names[1] = $1;
        next;
    }
    if (m/^\t\t\t"name":"(.+)"/) {
        $names[2] = $1;
        next;
    }
    if (m/^\t\t\t\t"name":"(.+)"/) {
        $names[3] = $1;
        next;
    }
    if (m/^\t\t\t\t\t"name":"(.+)"/) {
        $names[4] = $1;
        say join "\t", @names;
    }
}

__DATA__
{
    "name":"ko00001",
    "children":[
    {
        "name":"09100 Metabolism",
        "children":[
        {
            "name":"09101 Carbohydrate metabolism",
            "children":[
            {
                "name":"00010 Glycolysis \/ Gluconeogenesis [PATH:ko00010]",
                "children":[
                {
                    "name":"K00844  HK; hexokinase [EC:2.7.1.1]"
                },
                {
                    "name":"K12407  GCK; glucokinase [EC:2.7.1.2]"
                },
bttbmeg0

bttbmeg02#

use v5.14;
use warnings;
use open ":std", ":encoding(UTF-8)";

my @names;
while ( <> ) {
   my ( $tabs, $name ) = /^\t{2}(\t*)"name": "(.*)"/
      or next;

   my $level = length( $tabs );
   $names[ $level ] = $name;

   say join "\t", @names if $level == 4;
}

不使用JSON解析器是可怕的。

xn1cxnb4

xn1cxnb43#

尽管代码看起来不是很清晰,但我还是设法创建了TSV格式的表,与sed和awk生成的表非常相似。
感谢所有关于使用模块JSON的信息,但是通过这种方式,我了解了更多关于在循环块外部使用变量的信息,我们可以存储它以用于循环中的下一轮。

use strict;

my $brite_hierarchy_filepath = shift @ARGV;

open my $brite_hierarchy, '<:utf8', $brite_hierarchy_filepath or die q{Can't open $brite_hierarchy_filepath: $!\n};

my $previous_1;
my $previous_2;
my $previous_3;

while (my $line = <$brite_hierarchy>) {
    next if $. == 4;
    chomp $line;
    
    # change accordingly to the hierarchical levels
    $line =~ s/\A\t{2}"name"/"level_1"/;           
    $line =~ s/\A\t{3}"name"/"level_2"/;         
    $line =~ s/\A\t{4}"name"/"level_3"/;
    $line =~ s/\A\t{5}"name"/"level_4"/;

    # find the categories and put them into a hash
    my ($tag) = $line =~ /\A"(.*?)"/; 
    my ($value) = $line =~ /\A"level_[1-4]":"(.*?)"/;
    my %field = ($tag => $value) unless $tag eq "" && $value eq "";

    for (keys %field) {
        $previous_1 = $field{"level_1"} if $_ eq "level_1" && $field{"level_1"} ne "";
        $previous_2 = $field{"level_2"} if $_ eq "level_2" && $field{"level_2"} ne "";
        $previous_3 = $field{"level_3"} if $_ eq "level_3" && $field{"level_3"} ne "";
        print join("\t", $previous_1, $previous_2, $previous_3, $field{"level_4"}, "\n") unless $field{"level_4"} eq "";
    };
    last if eof $brite_hierarchy;
};

相关问题