Perl で JSON を扱う

近年のウェブシステム開発では JSON (JavaScript Object Notation) は欠かせないデータフォーマットの 1 つでしょう。 JavaScript はもちろんのこと、どのプログラミング言語でも JSON はデフォルトで扱えることがほとんどです。 Perl も例外ではありません。 ここでは、JSON の読み取りと生成を行う Perl モジュール「JSON」について解説します。 以降、Perl モジュール「JSON」のことを JSON モジュールと呼びます。

目次

JSON モジュールは標準モジュールではない?

恐らく Perl で JSON を扱う場合は、JSON モジュールを使うでしょうし、 ネットで検索しても、JSON モジュールの使い方を解説していることが多いでしょう。 まるで JSON モジュールが Perl の 標準モジュール かのように感じるでしょうが、実はそうではありません。

Perl の標準モジュールになっているのは「JSON::PP」というモジュールです。 JSON::PP は Perl 5.14 から標準モジュールとなりました。 JSON::PP は C 言語の助けを借りずに Perl スクリプトのみで作られた JSON 用の Perl モジュールです。

一方で、C 言語で開発された JSON::XS というモジュールも存在しています。 JSON::XS は当然ですがかなり高速に JSON を処理できます。 しかし、JSON::XS は標準モジュールではありませんので、サーバー環境によって使えないことがあります。

本当なら高速な JSON::XS を使いたいけれども、JSON::XS が使えない環境でも JSON を処理したいなら、 まず JSON::XS を試して、ダメなら JSON::PP を試す、といった処理が必要になってしまいます。 その面倒を避けるために用意されたのが、JSON モジュールです。 JSON モジュールは、JSON::XS と JSON::PP のラッパーモジュールとなり、 まずは高速な JSON::XS を試します。JSON::XS が利用できなければ、JSON::PP にフォールバックします。

さて、そのような便利そうな JSON モジュールですが、実はこれも標準モジュールではありません。 JSON::PP が標準モジュールなのに JSON モジュールが標準モジュールでない理由は分かりませんが、いずれにせよ、 ご利用のレンタルサーバーでは JSON モジュールが利用できない可能性はあります。 また、環境によっては Perl 5.14 以上であるにも関わらず、JSON::PP すら利用できない可能性もあります。

実は、そのような絶望的な環境でも、JSON モジュールをローカルに設置すれば動作します。 JSON モジュールは、JSON::PP すら利用できなければ、 JSON モジュールに内蔵されている JSON::backportPP というモジュールにフォールバックします。 そのため、JSON モジュールさえ動かすことさえできれば、何とかなるのです。

JSON モジュールをローカルに設置して利用する方法

まずは meta::cpan から JSON モジュールをダウンロードしてください。 ページの左側メニューに Download リンクがあるはずです。 そこから最新版のモジュールをダウンロードしてください。

まず、extlib というフォルダを作ってください。 実は名前は何でも構いません。 次に、ダウンロードしたモジュールを展開します。 すると、lib フォルダがあるはずです。 この lib フォルダの中身をすべて extlib の中にコピーします。 すると、次のようなファイル構成になるはずです。

📂 extlib/
    📂 JSON/
    📄 JSON.pm
📄 test.pl

上記ディレクトリ構成のように「test.cgi」を作ります。

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/extlib";
use JSON;

print "OK";

test.pl を実行したら OK と出力されるはずです。

JSON モジュールの基本的な使い方

JSON モジュールは、まず new を使ってオブジェクトを生成します。 そのオブジェクトの encode() メソッドを使って、JSON テキストを生成することができます。 逆に JSON テキストを読み込む場合は decode() メソッドを使います。

use JSON;

# オブジェクトを生成
my $json = JSON->new;

# HASH リファレンスを JSON テキストに変換
my $json_data = { name => "Taro", list => [ 1, 4, 3 ] };
my $json_text = $json->encode($json_data);

# JSON テキストを読み込んで HASH リファレンスに戻す
my $data = $json->decode($json_text);

# HASH リファレンスの内容を出力
use Data::Dumper;
print Dumper $data;

このスクリプトは次のような結果を出力します。

$VAR1 = {
          'list' => [
                      1,
                      4,
                      3
                    ],
          'name' => 'Taro'
        };

基本的な使い方はたったのこれだけです。 しかし、細かい点で注意するべきところがありますので、以降で詳細に解説します。

JSON テキストを整形する

JSON テキストを生成する際に、インデントを入れて人が見やすい形に整形したいときがあるでしょう。 その場合は、JSON モジュールのオブジェクトを生成する際に pretty() メソッドを呼び出します。

my $json = JSON->new->pretty();
my $data = { name => "Taro", list => [ 1, 4, 3 ] };
print $json->encode($data);

このサンプルコードは次のような結果を出力します。

{
   "name" : "Taro",
   "list" : [
      1,
      4,
      3
   ]
}

この整形された JSON テキストを見て : (コロン) の前後にスペースが入れられているのが気になる人もいるでしょう。 もしコロンの前のスペースを削除したいなら、JSON モジュールのオブジェクトを生成する際に space_before() メソッドを呼び出し、引数に 0 を指定します。

my $json = JSON->new->pretty()->space_before(0);

さらにコロンの後ろのスペースも削除したいなら、space_after() メソッドを呼び出し、 引数に 0 を指定します。

my $json = JSON->new->pretty()->space_before(0)->space_after(0);

これで出力結果は次のようになります。

{
   "name":"Taro",
   "list":[
      1,
      4,
      3
   ]
}

hashref と arrayref 以外を許さない

JSON モジュールの encode() メソッドは、基本的には hashref または arrayref を指定します。 しかし、JSON モジュールの v4.0 以降では、文字列などもデフォルトで許します。

my $json = JSON->new;
print $json->encode('Taro');    # Taro
print $json->encode(undef);     # null

文字列を指定すれば、そのまま出力されます。 そして、undef を指定すると、null が出力されます。

しかし、hashref と arrayref のみを許可したいばあがあるでしょう。 その場合は、JSON モジュールのオブジェクトを次のように生成します。

my $json = JSON->new->allow_nonref(0);

allow_nonref() には引数として 0 を与えます。 こうすることで、もしencode() メソッドに hashref と arrayref 以外の値が指定されると例外が発生します。

hash- or arrayref expected (not a simple scalar, use allow_nonref to allow this) at test.pl line 12.

UTF-8 の外部文字列で JSON テキストを生成する

日本語などのマルチバイトの文字を扱う場合、use utf8 プラグマをセットしていることでしょう。 この場合、JSON オブジェクトの encode() メソッドで生成される JSON テキストは内部文字列として生成されます。 もしこの内部文字列の JSON テキストを外部に出力したいなら、外部文字列にエンコードしないといけません。

use utf8;
use JSON;
use Encode;

my $json = JSON->new;
my $data = { name => '太郎', pref => '東京都'};
my $jtext = $json->encode($data);
print Encode::encode('UTF-8', $jtext);

多くのシーンでは、生成した JSON テキストは外部に出力することがほとんどでしょう。 JSON モジュールのオブジェクトの utf8() メソッドを使うと、encode() で JSON テキストを生成する際に、自動的に外部文字列にエンコードしてくれます。

use utf8;
use JSON;

my $json = JSON->new->utf8();
my $data = { name => '太郎', pref => '東京都'};
my $jtext = $json->encode($data);
print $jtext;

この utf8() メソッドは、encode() メソッドによる JSON テキスト生成処理だけでなく、 decode() メソッドによる JSON テキスト読み取り処理にも影響を与えます。 この場合は、読み取る JSON テキストも UTF-8 エンコーディングの外部文字列であるとの前提で、 自動的に内部文字列に変換します。

use utf8;
use JSON;

my $jtext = $ARGV[0];                   # '{ "name": "太郎", "pref": "東京都" }'
my $json  = JSON->new->utf8();
my $data  = $json->decode($jtext);
print length( $data->{name} ), "\n";    # 2
print length( $data->{pref} ), "\n";    # 3

変数 $data には hashref が格納されますが、その中のテキストは内部文字列になっています。 length 関数の結果がバイト数ではなく文字数になっています。