8月 052012
 

boost::property_treeで日本語を含むJSONが文字化けする | Inhale n’ Exhale久しぶりにC++ネタ。JSONを生成するプログラムをC++で書こうなんて人がそもそもいない気もするが、boost::property_tree::write_json()を使うと簡単にJSON出力ができる。

#include <iostream>
#include <sstream>
#include <boost/property_tree/json_parser.hpp>
using namespace std;

int main(int argc, char *argv[])
{
	if(argc < 2)
	{
		cerr << "Usage: " << argv[0] << " message" << endl;
		return 1;
	}

	boost::property_tree::ptree pt;
	pt.put("message", argv[1]);
	boost::property_tree::write_json(cout, pt, false);
	return 0;
}


コマンドライン引数にhelloを渡して出力されたJSONがこちら。

$ g++ -I/usr/include/boost_1_50_0 -o write_json main.cpp
$ ./write_json hello
{"message":"hello"}

では引数に日本語を渡すとどうなるか。

$ ./write_json ほげ
{"message":"\u00E3\u0081\u00BB\u00E3\u0081\u0092"}

シェル実行環境のロケールはen_US.UTF-8。日本語部分は1バイトずつ\u00XXという形にエンコードされている。

このJSONをChrome Development Toolで読み込ませてみる。
GarbledJSON
素敵に文字化け。

JSONの仕様で文字列型は

A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using backslash escapes.

と定義されているので、UTF-8から一旦Unicodeに変換しなければならない。PHPのjson_encode()なんかは、入力の文字コードがUTF-8に限定しているものの、内部でUnicode化してからJSONエンコードをしてくれているようだ。

ということで、まずはUTF-8からUnicodeへ変換する処理が必要になる。また、マルチバイト文字列(std::string)とワイド文字列(std::wstring)との変換もしなければならない。面倒ではあったが、標準ライブラリのcodecvtを使えばなんとかなりそうだったので、UTF-8からUnicodeへの変換を行うwiden()関数と、その逆変換を行うnarrow()関数(JSON出力の目的ではなくてもいいんだけど対称性のため)を提供するCodeCvtクラスを作ってみた。

#include <iostream>
#include <stdexcept>
#include <vector>
#include <string>
#include <cstring>
#include <locale>
#include <boost/property_tree/json_parser.hpp>
using namespace std;

class CodeCvt
{
public:
	explicit CodeCvt(const char *locale="en_US.UTF-8")
	: locale_(locale), codecvt_(use_facet<codecvt_type>(locale_)) {}

	wstring widen(const string &s)
	{
		const char *fb = s.data();
		const char *fe = fb+s.size();
		const char *fn;
		vector<wchar_t> ws(s.size());
		wchar_t *tb = &ws.front();
		wchar_t *te = tb+ws.size();
		wchar_t *tn;
		mbstate_t st;
		bzero(&st, sizeof(st));

		const codecvt_base::result res = codecvt_.in(st, fb, fe, fn, tb, te, tn);
		assert_result(res, fn, fe);
		return wstring(tb, tn);
	}

	string narrow(const wstring &ws)
	{
		const wchar_t *fb = ws.data();
		const wchar_t *fe = fb+ws.size();
		const wchar_t *fn;
		vector<char> s(ws.size()*4);
		char *tb = &s.front();
		char *te = tb+s.size();
		char *tn;
		mbstate_t st;
		bzero(&st, sizeof(st));

		const codecvt_base::result res = codecvt_.out(st, fb, fe, fn, tb, te, tn);
		assert_result(res, fn, fe);
		return string(tb, tn);
	}

private:
	template<class CharT>
	static void assert_result(codecvt_base::result res, const CharT *fn, const CharT *fe)
	{
		switch(res)
		{
			case codecvt_base::ok:
				if(fn != fe)
				{
					throw invalid_argument("Conversion failure");
				}
				break;
			case codecvt_base::partial:
				throw invalid_argument("partial");
			case codecvt_base::error:
				throw invalid_argument("error");
			case codecvt_base::noconv:
				throw invalid_argument("noconv");
		}
	}

	typedef codecvt<wchar_t, char, mbstate_t> codecvt_type;
	const locale locale_;
	const codecvt_type &codecvt_;
};

mbstate_t型の変数stbzero()memset()などで初期化しておかないと、codecvt_.in()codecvt_.out()の中で不測の事態が起こるので注意(実際テスト中に無限ループにハマってデバッガで追いかける羽目になった)。

main()関数側では、ptreeの代わりにwptreeを使い、出力もcoutではなくwcoutを使うようにするだけ。

int main(int argc, char *argv[])
{
	try
	{
		CodeCvt cvt;
		boost::property_tree::wptree pt;
		pt.put(L"message", cvt.widen(argv[1]));
		boost::property_tree::write_json(wcout, pt, false);
		return 0;
	}
	catch(const exception &e)
	{
		cerr << e.what() << endl;
		return 1;
	}
}

さっそく実行してみる。

$ ./write_json ほげ
{"message":"\u307B\u3052"}

ちゃんとUnicodeのバイトシーケンスでエンコードされているのがわかる。

同じようにChrome Development Toolで読み込ませてみる。
FixedJSON

今度はちゃんと「ほげ」と表示されるようになった。

CodeCvtクラスのコンストラクタ引数で別の文字コード(例えばja_JP.EUC-JP)を渡せば、UTF-8以外のマルチバイト文字列からでもJSON化することができるはず。

ソースはGistに置いといた。

 返信する

以下のHTML タグと属性が利用できます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください