JSONを見やすく表示する

WebAPIの返すJSONは、おいそれと読めるもんじゃない。例えばTwitterのpublic_timelineだとこんな感じ。

そこで、JSONを見やすく表示するCGIを作ってみた。

JSONPerlのオブジェクトに変換する

JSONモジュールを使えば、簡単にJSONPerlのオブジェクトに変換できる。

use JSON qw(from_json);

my $obj = from_json($json);

JavaScriptのオブジェクトと配列は、Perlのハッシュと配列に変換される。*1

JSON入れ子構造をulで表現する

見やすく表現するために、JSON入れ子はHTMLのul要素で表現することにした。

sub json_to_html {
    my ($json) = @_;

    my $html;

    if (ref($json) eq 'HASH') {
        foreach my $key (sort keys %$json) {
            $html .= $cgi->li($cgi->strong("$key: "),
                                json_to_html($json->{$key}));
        }
        return defined $html
                    ? 'Object{<a href="#">...</a>}'.$cgi->ul($html)
                    : 'Object{}';
    }
    elsif (ref($json) eq 'ARRAY') {
        for (my $i = 0; $i < @$json; $i++) {
            $html .= $cgi->li($cgi->strong("$i: "),
                                json_to_html($json->[$i]));
        }
        return defined $html
                    ? 'Array[<a href="#">...</a>]'.$cgi->ul($html)
                    : 'Array[]';
    }

    return defined $json
                ? $cgi->tt(q(').$cgi->escapeHTML($json).q('))
                : '(null)';
}

入力パラメータはPerlのオブジェクト。HTMLの編集にはCGI.pmを使ってます。

こんな感じで json_to_html() を呼び出せばよい。

my $json = $cgi->param('json');
my $html;
if (length $json) {
    eval {
        $html = $cgi->ul({class=>'JSON'},
                        $cgi->li(json_to_html(from_json($json))));
    };
    if ($@) {
        $@ =~ s/ at \S+ line \d+\.$//;
        $html = $cgi->p($@);
    }
}

from_json() は変換に失敗するとdieするので、ちょっと厄介。evalブロック内で処理する必要がある。dieした際に有用なメッセージが $@ に入ってるんだけど、ファイル名と行番号はじゃまなので捨ててます。*2

要素を非表示にできるようにする

大きなデータの場合、全体を表示すると見にくいので、Firebugのように要素の表示・非表示を切り替えられるようにした。

以下のJavaScriptを埋め込む。jQueryを使っています。

$(document).ready(function(){

    $('.JSON ul').each(function(){
        var ul = $(this);
        ul.parent().children('a').click(function(){
            ul.slideToggle('fast');
            return false;
        });
        ul.hide();
    });

    $('.JSON > li > ul').show();
});

全体のコード

#!/usr/bin/perl -T

use strict;
use CGI;
use CGI qw(fatalsToBrowser);
use JSON qw(from_json);

my $cgi = new CGI;
$cgi->charset('utf-8');

my $jquery_url
        = 'http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js';

my $script = <<'++';

$(document).ready(function(){

    $('.JSON ul').each(function(){
        var ul = $(this);
        ul.parent().children('a').click(function(){
            ul.slideToggle('fast');
            return false;
        });
        ul.hide();
    });

    $('.JSON > li > ul').show();
});
++

sub json_to_html {
    my ($json) = @_;

    my $html;

    if (ref($json) eq 'HASH') {
        foreach my $key (sort keys %$json) {
            $html .= $cgi->li($cgi->strong("$key: "),
                                json_to_html($json->{$key}));
        }
        return defined $html
                    ? 'Object{<a href="#">...</a>}'.$cgi->ul($html)
                    : 'Object{}';
    }
    elsif (ref($json) eq 'ARRAY') {
        for (my $i = 0; $i < @$json; $i++) {
            $html .= $cgi->li($cgi->strong("$i: "),
                                json_to_html($json->[$i]));
        }
        return defined $html
                    ? 'Array[<a href="#">...</a>]'.$cgi->ul($html)
                    : 'Array[]';
    }

    return defined $json
                ? $cgi->tt(q(').$cgi->escapeHTML($json).q('))
                : '(null)';
}

my $json = $cgi->param('json');
my $html;
if (length $json) {
    eval {
        $html = $cgi->ul({class=>'JSON'},
                        $cgi->li(json_to_html(from_json($json))));
    };
    if ($@) {
        $@ =~ s/ at \S+ line \d+\.$//;
        $html = $cgi->p($@);
    }
}

print   $cgi->header(-charset=>'utf-8'),
        $cgi->start_html(
            -title      => 'JSON to HTML',
            -encoding   => 'utf-8',
            -script     => [ { src => $jquery_url }, $script ],
        ),
        $cgi->h1($cgi->a({href=>''},'JSON to HTML')),
        $html,
        $cgi->start_form,
        $cgi->h2('Input JSON'),
        $cgi->textarea('json','',20,120),
        $cgi->div($cgi->submit);
        $cgi->endform;
        $cgi->end_html;

以下で動作させていますが、JSON::PPでの動作のため大きなJSONはパーズできません...

*1:残念なことに数値か文字列かの情報は保存できない。そもそもPerlでは両者を区別してないからね。

*2:JSONモジュールがdieするときに行番号を出さないようにすべきと思うけどなあ。