■2008-01-07 あけましておめでとうございます
* [Perl][メモ] JSON::XSのutf8とかasciiについて
2010-04-21追記:
本記事は内容が古く、わかりにくいものになっています。モジュール使ってみたけど文字化けして困る〜という方はこのページではなくこちらを参照ください。
kawa.netの川崎さんがPerl の JSON モジュールで日本語を含む文字列を扱う際の tips。 でJSON 2.x(つまりJSON::XS)のutf8関連について取り上げられていらっしゃいました。この新API、utf8()
については、一応XS、PPともにドキュメントにどのような機能なのか書かれているのですが、それでもやはりわかりづらいところがあるかもしれませんので、簡単な補足説明をしたいと思います。最初に箇条書きにしますと、
- 1.x系の$JSON::UTF8とは違って、PerlのUTF8フラグとは直接関係ない
- 入出力JSONデータがUTF-8エンコーディングされてることを前提・保証するためのもの
となります。
まず、JSON 1.xの$JSON::UTF8はde/encodeされたデータにUTF8フラグを立てるためのものでした。これに対してXSは、JSONデータがUnicodeを含むなら自動的にUTF8フラグをたてます。これはつまり、JSONデータがUnicode(UTF8フラグがたっているか、あるいは\uで始まる256以上の値のコード)なら変換されるPerlデータもUnicode扱い(UTF8フラグ立つ)になるということです。ではXSのutf8は何のためにあるかというと、「入出力されたJSONデータが正しくUTF-8エンコーディングされていることを保証する」ためです。
重要なのは、エンコーディング方法としてのUTF-8と、Unicode扱いを示すUTF8フラグを区別することです。そしてUTF8フラグがたっていたら、それはUTF-8エンコードされた文字列じゃなく抽象的なUnicodeなのだと考えます。
Perl⇒JSON(encode)
デフォルトでUnicode扱いの文字列はそのままJSONデータに反映されます。つまりUTF8フラグが立ってれば、出力JSONもフラグがたってます。utf8(1)にすると、UTF8は立ちません。なぜなら、フラグが立っていたらそれはUTF-8じゃないから。UTF-8は1文字を表現するのに1〜4(6)バイト使いますが、それぞれの値は255以下じゃないといけません。UTF8フラグがたっていたら、それは1文字の値が256以上ということで、JSON::XSにとってUTF-8な文字列ではないのです(たとえPerl内部ではUTF-8でバイトが並んでいても)。
JSON⇒Perl
Unicodeと判断される文字列を含むJSONデータはUTF8フラグの立ったPerlデータに変換されます。このこと自体はutf8(1)だろうがutf8(0)だろうが変わりません。UTF8フラグのたったJSON文字列「あ」はPerl文字列に変換してもUTF8フラグの立った「あ」のままです。また、"\u3042"
はUnicodeの「あ」(HIRAGANA LETTER A)に変換されます。ということは、PerlワールドでもそれはUnicode(UTF8フラグがたった文字列)です。これが"\u00e3\u0081\u0082"
なら、UTF8フラグのたたないUTF-8な「あ」になるでしょう。ではutf8(1)で何がかわるかというと、JSONデータがUTF-8エンコーディングされた文字列でないとエラーを出すようになります。つまり、UTF8フラグがたっていたらそれはUTF-8じゃないから、エラーです。もちろん、フラグがたってなくてもUTF-8エンコーディング的に不正ならエラーです。
utf8()
の意味するところはこれだけなのです。このメソッドには、HTTP経由のrequest, responseなど(UTF-8でやり取りする前提で)Perlの外界からデータが入ってくるときも、外界へ出力するときも正しくUTF-8エンコーディングされていることを保証させることで、セキュリティを下げないようにしようとするXS作者の意図があります。
$obj = $json->utf8->decode($text); # UTF-8的に不正な文字列ではない print $json->utf8->encode($obj); # Wide なんちゃらな警告は出ない
これはまた、XSがexportするencode_json, decode_json
がJSON->new->utf8(1)->encode/decode
と同義である理由でもあります。
ところでascii(1)はencodeにのみ作用して、UnicodeをU+0000〜U+FFFFなら\uXXXXに、それ以上はUTF-16サロゲートペア(\uXXXX\uYYYY)に変換します(RFC4627)。これにより出力されるJSONがasciiのみを含むことを保証します。
*
追記:川崎さんの記事で、XSとPPで動作が異なる結果がありますが、どうもこれはXSのバグかもしれませんので今作者に確認とってます。decodeの際に入力した非UTF8フラグのテキストにUTF8フラグがたってしまうようなのです…… 動作として正しいものでした。ので、必要に応じてチェックかけてください。