SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

C++でJSONを読む

  • X ポスト
  • このエントリーをはてなブックマークに追加

PicoJSON

 さて、このJSON textを解析し、データと構造を読み取るからくり:パーサ(parser)が欲しくてあちこち探してみたところ、PicoJSONを見つけました。PicoJSONの最大のウリは「ヘッダだけで実装された」軽量/コンパクトさ、#include <picojson.h> を一行追加するだけで使えます。

 おためしに先ほどのexample.jsonをPicoJSONでパースし、そのナカミのいくつかを読み出してみましょう。

list01
#include <picojson.h>

#include <fstream>
#include <iostream>
#include <cassert>
#include <memory>
#include <string>

using namespace std;
using namespace picojson;

int main() {
  value root;
  {
    ifstream stream("example.json");
    if ( !stream.is_open() ) return 1;
    stream >> root;
    assert( get_last_error().empty() );
  }
  object image = root.get<object>()["Image"].get<object>();
  cout << "Width=" << image["Width"].get<double>() << endl;
  cout << "Height=" << image["Height"].get<double>() << endl;
  cout << "Title=" << image["Title"].get<string>() << endl;
  array  ids = image["IDs"].get<array>();
  // arrayはstd::vector<picojson::value>なのでrange-based forが使える!
  for ( value item : ids ) {
    cout << item.get<double>() << endl;
  }
}
/* 実行結果:
Width=800
Height=600
Title=View from 15th Floor
116
943
234
38793
*/

 JSON textのパースは実にお気楽、streamをオープンしてpicojson::valueに >> するだけ。そしてそのvalueをboolean,number,string,array,objectのいずれかに変換するのがメンバ関数 X value::get<X>()、ここでXはそれぞれbool,doube,std::string,picojson::array,picojson::objectです。また、bool value::is<X>() はそのvalueがXであるか否かを返します。

 picojson::arrayはstd::vector<picojson::value>、picojson::objectはstd::map<std::string,picojson::value>のtypedefですから、STLに慣れた方なら苦もなく使えることでしょう。

 RFC4627には「JSON text SHALL be encoded in Unicode. The default encoding is UTF-8.」とありますから、JSON text が内包するstringは漢字やかなを含んでいても無問題なはず。やってみましょう。

 コレをUTF-8エンコードしたテキストファイル:utf8.jsonをパースし、幅や高さ、Titleなどを取り出します。

list02
//#undef NDEBUG
#include <picojson.h>

#include <fstream>
#include <iostream>
#include <cassert>
#include <memory>
#include <string>

using namespace std;
using namespace picojson;

int main() {
  value root;
  {
    ifstream stream("utf8.json");
    if ( !stream.is_open() ) return 1;
    stream >> root;
    assert( get_last_error().empty() );
  }
  object image = root.get<object>()["Image"].get<object>();
  cout << "幅=" << image["幅"].get<double>() << endl;
  cout << "高さ=" << image["高さ"].get<double>() << endl;
  cout << "Title=" << image["Title"].get<string>() << endl;
  array  ids = image["IDs"].get<array>();
  for ( value item : ids ) {
    cout << item.get<double>() << endl;
  }
}
/* 実行結果:
幅=0
高さ=0
Title=15髫弱°繧峨・譎ッ隕ウ
116
943
234
38793
*/

 ……あらら、おかしなことになってますね。Windowsだと多バイト文字はShift-JISなのでご覧のありさまです。幅と高さが0になってるのはobject検索時のキーがバケているため検索に引っかからず、対応する値が拾えなかったのでしょう(Debugモードだとpicojson.h内でassertします)。objectのキーや取り出したstringは適宜コード変換せにゃならんのです。

 WindowsAPI:MultiByteToWideChar/WideCharToMultiByteの助けを借りて文字コード変換を施した版がコチラ。

list03
//#undef NDEBUG
#include <picojson.h>

#include <fstream>
#include <iostream>
#include <cassert>
#include <memory>
#include <string>

#include <locale>
#include <Windows.h>

// Unicode(wide) -> multibyte
std::string fromUnicode(const std::wstring& str, UINT codepage =CP_UTF8) {
  std::string result;
  if ( str.size() != 0U ) {
    int size = ::WideCharToMultiByte(codepage, 0, str.data(), str.size(), NULL,0, NULL, NULL );
    assert ( size > 0 );
    auto buffer = std::make_unique<char[]>(size);
    size = ::WideCharToMultiByte(codepage, 0, str.data(), str.size(), buffer.get(), size, NULL, NULL);
    assert ( size > 0 );
    result.assign(buffer.get(), size);
  }
  return result;
}

// multibyte -> Unicode(wide)
std::wstring toUnicode(const std::string& str, UINT codepage =CP_UTF8) {
  std::wstring result;
  if ( str.size() != 0U ) {
    int size = ::MultiByteToWideChar(codepage, 0, str.data(), str.size(), NULL, 0);
    assert( size > 0 );
    auto buffer = std::make_unique<wchar_t[]>(size);
    size = ::MultiByteToWideChar(codepage, 0, str.data(), str.size(), buffer.get(), size);
    assert( size > 0 );
    result.assign(buffer.get(), size);
  }
  return result;
}

// Shift-JIS -> UTF-8
inline std::string stou(const std::string& str) {
  return fromUnicode(toUnicode(str, CP_ACP), CP_UTF8);
}

// UTF-8 -> Shift-JIS
inline std::string utos(const std::string& str) {
  return fromUnicode(toUnicode(str, CP_UTF8), CP_ACP);
}

using namespace std;
using namespace picojson;

int main() {
  value root;
  {
    ifstream stream("utf8.json");
    if ( !stream.is_open() ) return 1;
    stream >> root;
    assert( get_last_error().empty() );
  }
#if 1
  object image = root.get<object>()["Image"].get<object>();
  cout << "幅=" << image[stou("幅")].get<double>() << endl;
  cout << "高さ=" << image[fromUnicode(L"高さ",CP_UTF8)].get<double>() << endl;
  cout << "Title=" << utos(image["Title"].get<string>()) << endl;
  array  ids = image["IDs"].get<array>();
  for ( value item : ids ) {
    cout << item.get<double>() << endl;
  }
#else /* あるいはこうやってもいい */
  wcout.imbue(locale("japanese"));
  object image = root.get<object>()["Image"].get<object>();
  wcout << L"幅=" << image[fromUnicode(L"幅")].get<double>() << endl;
  wcout << L"高さ=" << image[fromUnicode(L"高さ")].get<double>() << endl;
  wcout << L"Title=" << toUnicode(image["Title"].get<string>()) << endl;
  array  ids = image["IDs"].get<array>();
  for ( value item : ids ) {
    wcout << item.get<double>() << endl;
  }
#endif
}
/* 実行結果:
幅=800
高さ=600
Title=15階からの景観
116
943
234
38793
*/

 ちょいとめんどくさいです。C++11にきっちり準拠した処理系なら u8"高さ" のように文字列リテラルの頭に u8 をつけることでUTF-8リテラル扱いとなり、例えば image[u8"幅"].get<double>() などとかなり楽に書けるのですが、VC12(Visual C++ 2013)は残念ながら未サポートのようです。

次のページ
Windows::Data::Json

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/7735 2014/05/13 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング