PerlのYAML::SyckとJSON::XSでutf-8ファイルを取り扱うポイント

考えてみると当たり前なんだけど、毎回考えるのが面倒なので動くコードをまとめておく。

Perlunicode周りの挙動が面倒なんだよなあ。Python(3)の方がシンプルでよいね。モダンな言語はやっぱりこの辺の取り扱いがスマートだわ。

YAML::Syck

UTF-8 エンコーディングの差異まとめ YAML::XS、YAML::Syck、YAML::Old、YAML、YAML::Tiny - Tociyuki::Diary

普通の用途としてはLoadFile/DumpFileでファイルI/Oをする時に使うだけでしょう。
$YAML::Syck::ImplicitUnicode = 1 を指定すると、Load/Dumpの際にutf-8 decode/encodeをしてくれるようになります。

use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";

use YAML::Syck;
$YAML::Syck::ImplicitUnicode = 1;

my $names   = LoadFile(shift);
my $keyword = 'ネコプラ';

foreach my $n (@$names){
  printf("%s %s $keyword\n", $n, (($n =~ /$keyword/) ? 'is' : 'is not'));
}
$ perl a.pl data.yaml
立花りく_//ネコプラ// is ネコプラ
坂下雅_なんキニ! is not ネコプラ
成瀬かおり_//ネコプラ// is ネコプラ
双葉ゆり(ラルムーン) is not ネコプラ
大島遥華_KATA☆CHU is not ネコプラ
高宮さくら_//ネコプラ// is ネコプラ
藍川みり_//ネコプラ// is ネコプラ
佐藤まりん_chuLa is not ネコプラ
もも is not ネコプラ

JSON::XS

[Perl] JSON モジュールの utf8 フラグ周りの仕様 tips 注意点: Kawanet Blog II

ややこしいですね。

JSON::XSは直接ファイルI/Oをしてくれるメソッドが用意されていないので、

  1. バイト列として読み込んでutf8(1)経由でデコード
  2. UTF-8文字列として読み込んでutf8(0)(デフォルト)でデコード

のどちらかで処理する必要があります。

use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";

use Path::Class qw/file/;
use JSON::XS;

my $content = file(shift)->slurp;
my $names   = JSON::XS->new->utf8->decode($content);

# あるいはこちらでも可
# my $content = file(shift)->slurp(iomode => '<:encoding(utf-8)');
# my $names   = JSON::XS->new->decode($content);

my $keyword = 'ネコプラ';

foreach my $n (@$names){
  printf("%s %s $keyword\n", $n, (($n =~ /$keyword/) ? 'is' : 'is not'));
}

YAMLと違ってJSONはファイルだけでなくWeb APIでも使われることが多いので、JSON::XSには常にUTF-8 decode済みの文字列を渡す(utf8 disabledとして使う)という運用のほうが分かりやすいかもしれません。

まあ、「入口でencode、出口でencode」の原則さえ守ればどっちでもいいかなと思いますが。