簡體   English   中英

將 JSON 轉換為 CSV/TSV

[英]Convert JSON to CSV/TSV

我正在嘗試將 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]

我總是建議使用 JSON 解析器,但如果你能保證格式永遠不會改變,你確實可以把它當作一個固定的文本文件。 在生產中,您通常不能。 但如果它是一次性的,那么它肯定有效。

您粘貼到問題中的示例輸入有空格,而不是制表符,因此您的代碼將無法使用它。 我的也不會。 我的輸入是從您的鏈接中復制的,並且有標簽。

您的正則表達式模式似乎有點復雜。 您始終可以使用相同的瑣碎模式,但只需要改變每個名稱前的制表符數量即可。 訣竅是每當您找到一個不是最后一列的名稱時跳到下一行,並重置第一列的整個結構。 我選擇使用數組而不是哈希,因為這樣更有意義,我們可以稍后在輸出時join 最后, sayprint類似,但帶有內置換行符。

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]"
                },
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 解析器太可怕了。

雖然代碼看起來不是很干凈,但我設法創建了 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;
};

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM