QA@IT

json/common.rb:216:in `match': invalid byte sequence in UTF-8

2651 PV

ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]です。

あるサブプロセスの出力(長いので)をログに取ろうと思って以下のようなコードを書きました:

out = Tempfile.new('')
err = Tempfile.new('')
system command, in: IO::NULL, out: out.path, err: err.path
log = JSON.dump(out: out.read, err: err.read)

しかし最後の行でエラーになってしまいます:

/lib/ruby/1.9.1/json/common.rb:216:in `match': invalid byte sequence in UTF-8 (ArgumentError)
        from /lib/ruby/1.9.1/json/common.rb:216:in `generate'
        from /lib/ruby/1.9.1/json/common.rb:216:in `generate'
        from /lib/ruby/1.9.1/json/common.rb:345:in `dump'
        from /home/shyouhei/spawn-log.rb:4:in `<main>'

コマンドの出力がUTF-8ではないようです。コマンドには手を入れたくないのですが、どうすればJSONに出力を保存することができるようになるのでしょうか。

回答

コマンドの出力が実際にはUTF-8互換であるという保証があるなら、

out.read.force_encoding("utf-8")

などとしてエンコーディング属性だけ強制的に書き換えればいいと思います。

あるいは、

Tempfile.new('', :encoding => 'utf-8')

でもいいかも。

Noteを受けての追記

なるほど。Encoding.default_externalがUTF-8ならそういう問題起きないはずだよなーと疑問には思ってました。。。

実際にUTF-8でないバイト列が含まれているのだとすると、JSONでは扱えないので、自前でエスケープなりエンコーディングしてやるしかなさそうですねぇ。

編集 履歴 (1)
  • いえ、すいません、JSON.dumpの行の前に"p out.read.encoding"を入れてみると、すでにUTF-8になっているようです。しかしやはり例外になります。ので実際にデータがUTF-8ではないのだとおもいます。 -
  • 追記しましたが、out.readの全体をbase64とかしたらhuman readableじゃなくなるので、jsonにする意味ないんですよね多分。だとしたらjsonが向いてない、ということになりそうです。 -
  • JSONなのは他のログと合わせている以上の意味ではないのですけれども、out.readを目で見てもどこがいけないのか分からない程度には「だいたい英語っぽい感じ」の出力なので、たとえばquoted-printableみたいなことになるといいなあと思うのですが…いや、そうか、quoted-printableにすればいいのか? ちょっと考えます。 -
  • だいたい英語で一部が怪しいとかなら、これではダメでしょうか http://qa.atmarkit.co.jp/questions/76 -
  • おぉ、String#encodeでこんなことできたんだ。これはお手軽でいいですね。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。