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で読み込ませてみる。
素敵に文字化け。
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
型の変数st
をbzero()
や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で読み込ませてみる。
今度はちゃんと「ほげ」と表示されるようになった。
CodeCvt
クラスのコンストラクタ引数で別の文字コード(例えばja_JP.EUC-JP
)を渡せば、UTF-8以外のマルチバイト文字列からでもJSON化することができるはず。
ソースはGistに置いといた。