Kawanet Blog II

アクセスカウンタ

zoom RSS [Perl] JSON モジュールの utf8 フラグ周りの仕様 tips 注意点

<<   作成日時 : 2008/01/07 05:21   >>

なるほど(納得、参考になった、ヘー) ブログ気持玉 7 / トラックバック 0 / コメント 1

Perl の JSON モジュールで日本語を含む文字列を扱う際の tips。
[Perl] JSON モジュール 2.x 系は、1.x 系と互換性が△ の記事で、
JSON::XS モジュールとの互換性(ソース&ドキュメントも!)を実現した代わりに
従来の JSON.pm のインターフェースが obsolete になってしまうのは残念。
今後、JSON.pm は XS 版の JSON::XS とほぼ同機能の Pure Perl 版の JSON::PP の
いずれかを自動選択してくれるラッパーとしての道を歩むようです。
と書いたら、JSON モジュール作者の makamaka さんから
XML::FeedPP::Plugin::DumpJSONのChangesにPPのutf8ハンドリングに
問題有りとありましたが、どのような内容でしょうか。
差し支えなければ教えていただけると助かります。
との コメント を頂いた。ありがとうございます!

もともと JSON::XS は bless 関係で使えないので対象外としていましたが、
JSON::Syck と比較して、JSON::PP モジュールでは日本語がうまく扱えず、
確かに Changes で JSON::PP に問題がありそう、書いてしまいましたが、
よく調べてみると、utf8 フラグ周りのクセに注意すれば使えそうです。
改めて JSON::PP と JSON::XS を確認した結果を、以下にまとめておきます。


JSON モジュールで utf8 を扱う際の注意点


1.encode 時(ハッシュなどのPerlオブジェクトからJSONを生成)は、
   入力ハッシュが日本語などの非 ASCII 文字列を含む場合は、
   必ず utf8 フラグ ON (string) である必要があります。
   utf8 フラグ OFF (octets) の日本語を含むハッシュを入力すると、文字化けします。
   utf8 フラグ OFF (octets) の ASCII 文字のみの場合は、問題は起こりません。

2.encode 時に出力される JSON 文字列の utf8 フラグの状態は、
   utf8() メソッドの指定通りになります。

JSON->new->encode($hash) ⇒ 常に utf8 フラグ ON(デフォルト)
JSON->new->utf8(0)->encode($hash) ⇒ 常に utf8 フラグ ON
JSON->new->utf8(1)->encode($hash) ⇒ 常に utf8 フラグ OFF

3.decode 時(JSONを解析してハッシュなどのPerlオブジェクト生成)は、
   入力する JSON に日本語などの非 ASCII 文字列を含む場合は、
   ->utf8(0) してから、utf8 フラグを ON にした JSON を入力するか、または
   ->utf8(1) してから、utf8 フラグを OFF にした JSON を入力する必要があります。
   それ以外の組み合わせでは、エラーが発生するか、文字化けします。

JSON->new->utf8(0)->decode($string) ⇒ OK!
JSON->new->utf8(1)->decode($string) ⇒ エラー!
JSON->new->utf8(1)->decode($octets) ⇒ OK!
JSON::XS->new->utf8(0)->decode($octets) ⇒ 文字化け!
JSON::PP->new->utf8(0)->decode($octets) ⇒ 全て utf8 フラグ OFF で返ります(例外)

4.decode 時に生成される Perl オブジェクト中の文字列の utf8 フラグの状態は、
   文字列の内容に依存します。
   ASCII 文字のみの文字列は、常に utf8 フラグ OFF になります。
   非 ASCII 文字を含む文字列は、常に utf8 フラグ ON になります。

以上のような仕様らしいことが、ようやく把握できた。


JSON モジュールで utf8 を扱う方法


まとめると、以下に注意すれば、問題なく日本語を扱えそう。

1.encode() 時は、ハッシュ内の全ての非 ASCII 文字列は、必ず予め utf8 フラグ ON にしておく。
2.encode() で出力される JSON 文字列の utf8 フラグは、予め utf8() メソッドで指定できる。
3.decode() 時は、入力する JSON 文字列の utf8 フラグの状態を、必ず utf8() メソッドで指定する。
4.decode() で出力されるハッシュ内の文字列の utf8 フラグの状態は、文字列の内容(ASCII/非ASCII)に依存する。

希望としては3番なんかは、自動的にやってくれると嬉しいのだけど。
また、2番の対としては、4番を指定する方法があっても良さそう。



今回、検証に利用したテストプログラムは以下の通り。

#!/usr/bin/perl

use strict;
use JSON;
use JSON::PP;
use JSON::XS;
use Encode;

{
    my $jaocthash = {"en","Hello","ja","こんにちは"};
    my $enocthash = {"en","Hello","fr","Bonjour"};
    my $jaoctjson = '{"en":"Hello","ja":"こんにちは"}';
    my $enoctjson = '{"en":"Hello","fr":"Bonjour"}';

    my $jastrhash = {};
    my $enstrhash = {};
    $jastrhash->{$_} = Encode::decode_utf8($jaocthash->{$_}) foreach keys %$jaocthash;
    $enstrhash->{$_} = Encode::decode_utf8($enocthash->{$_}) foreach keys %$enocthash;
    my $jastrjson = Encode::decode_utf8($jaoctjson);
    my $enstrjson = Encode::decode_utf8($enoctjson);

    foreach my $mode ( 'JSON::PP', 'JSON::XS' ) {
        foreach my $utf8 ( 0, 1 ) {
            &test_encode( $mode, 'non-ascii', 'octets', $jaocthash, $utf8 );
            &test_encode( $mode, 'non-ascii', 'string', $jastrhash, $utf8 );
            &test_decode( $mode, 'non-ascii', 'octets', $jaoctjson, $utf8 );
            &test_decode( $mode, 'non-ascii', 'string', $jastrjson, $utf8 );
            &test_encode( $mode, 'ascii',     'octets', $enocthash, $utf8 );
            &test_encode( $mode, 'ascii',     'string', $enstrhash, $utf8 );
            &test_decode( $mode, 'ascii',     'octets', $enoctjson, $utf8 );
            &test_decode( $mode, 'ascii',     'string', $enstrjson, $utf8 );
        }
    }
}

sub test_encode {
    my $mode = shift;
    my $head = shift;
    my $type = shift;
    my $hash = shift;
    my $utf8 = shift;
# &check_hash( "$head ($type)\t[hash]", $hash );

    eval {
        my $json = $mode->new;
        $json->utf8( $utf8 ) if defined $utf8;
        my $meth = defined $utf8 ? "->utf8($utf8)" : "";
        my $text = $json->encode($hash);
        &check_scalar( "$head ($type)\t$mode->new$meth->encode(\$hash)", $text );
    };
    print "$head ($type)\t$mode->new->utf8($utf8)->encode(\$hash)\tERROR\n****\t$@\n" if $@;
}

sub test_decode {
    my $mode = shift;
    my $head = shift;
    my $type = shift;
    my $text = shift;
    my $utf8 = shift;
# &check_scalar( "$head ($type)\t[json]", $text );

    eval {
        my $json = $mode->new;
        $json->utf8( $utf8 ) if defined $utf8;
        my $meth = defined $utf8 ? "->utf8($utf8)" : "";
        my $back = $json->decode($text);
        &check_hash( "$head ($type)\t$mode->new$meth->decode(\$text)", $back );
    };
    print "$head ($type)\t$mode->new->utf8($utf8)->decode(\$text)\tERROR\n****\t$@\n" if $@;
}

sub check_hash {
    my $mess = shift;
    my $hash = shift;
    print $mess;
    foreach my $key ( sort keys %$hash ) {
        my $val = $hash->{$key};
        my $flag = Encode::is_utf8($val) ? 'string' : 'octets';
        my $len = length($val);
        print "\tkey=$key flag=$flag length=$len ($val)";
    }
    print "\n";
}

sub check_scalar {
    my $mess = shift;
    my $val = shift;
    print $mess;
    my $flag = Encode::is_utf8($val) ? 'string' : 'octets';
    my $len = length($val);
    print "\tflag=$flag length=$len\n";
    $flag;
}

テーマ

関連テーマ 一覧


月別リンク

ブログ気持玉

クリックして気持ちを伝えよう!
ログインしてクリックすれば、自分のブログへのリンクが付きます。
→ログインへ
気持玉数 : 7
なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー)
ナイス
ガッツ(がんばれ!)
かわいい

トラックバック(0件)

タイトル (本文) ブログ名/日時

トラックバック用URL help


自分のブログにトラックバック記事作成(会員用) help

タイトル
本 文

コメント(1件)

内 容 ニックネーム/日時
どうもutf8の動作についてはわかりにくいところがあると
思いますので、
http://www.donzoko.net/cgi-bin/tdiary/20080107.html
にて簡単な説明をしてみました。何かの参考になれば幸いです。

それと、PPとXSでdecodeの結果が違っているようなのですが、
調べてみたところ、XSのdecodeが入力に利用したテキストの
UTF8フラグを勝手に立てているっぽいんです。
こちらは今XSの作者の方に確認をとっています。
ではでは。

makamaka
2008/01/07 13:42

コメントする help

ニックネーム
本 文
[Perl] JSON モジュールの utf8 フラグ周りの仕様 tips 注意点 Kawanet Blog II/BIGLOBEウェブリブログ
文字サイズ:       閉じる