C++でJSONというと、json.orgにもライブラリがたくさん紹介されているとおり色々ある。でも単に出力するだけの物で、ヘッダファイル一つで簡単に使えるのが無かったので作ってみた。
ちょっとstringの連結コストがかかる気もするけど、まあいいか。
今のところ、std::map<string,any>とstd::vector<any>とstringとintとdoubleが入る。つまりkeyはstringのみで、値はboost::anyをかぶせている。もちろんmapやvectorは入れ子にできる。
boost::anyは何でも入れられる便利な型。
参考:boost::any – 橋本詳解
必要なのはこれだけだけど、boost::any、tuple、format、foreachが必要。
json_builder.h最新版はbitbucketからどうぞ。
json_builder.h
#include <iostream>jsonは要素数不定のただの木なので、再帰でtree walkして要素の型を見て文字列に直して連結し直すだけの関数一つになった。シンプル。
#include <string>
#include <map>
#include <vector>
#include <boost/any.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/format.hpp>
#include <boost/foreach.hpp>
using namespace std;
using namespace boost;
namespace json_builder{
string toJson(any value){
if(value.type() == typeid(vector<any>)){
string result_str;
vector<any> vec = any_cast<vector<any> >(value);
for(int i = 0; i < vec.size(); i++){
result_str += toJson(vec[i]);
if(i < vec.size()-1) result_str += ",";
}
result_str = str(format("[%s]") % result_str);
return result_str;
}
else if(value.type() == typeid(map<string,any>)){
string result_str;
map<string,any> m = any_cast<map<string,any> >(value);
string key;
any value;
int i = 0;
BOOST_FOREACH(tie(key,value), m){
result_str += str(format("\"%s\":%s") % key % toJson(value));
if(++i < m.size()) result_str += ",";
}
result_str = str(format("{%s}") % result_str);
return result_str;
}
else if(value.type() == typeid(string)){
return str(format("\"%s\"") % any_cast<string>(value));
}
else if(value.type() == typeid(int)){
return str(format("\"%d\"") % any_cast<int>(value));
}
else if(value.type() == typeid(double)){
return str(format("\"%d\"") % any_cast<double>(value));
}
}
}
見ての通り、”や’や[や{はescapeしていないので、それらを含む文字列をtoJsonするとparseできないjsonができる。とりあえず今は入れる前にescapeしておいてほしい。
単純にバックスラッシュつければいいだけなのかな? → 対応した
使ってみる。一つのstd::mapをjsonのhashとして標準出力する例
test.cpp
#include <iostream>
#include <string>
#include <map>
#include <boost/any.hpp>
#include "../json_builder.h"
int main(int argc, char* argv[]){
std::map<string,boost::any> user;
user["name"] = std::string("shokai"); // string
user["fullname"] = std::string("sho hashimoto");
user["age"] = 25; // int
user["test"] = 1.23; // double
string json = json_builder::toJson(user);
cout << json << endl;
return 0;
}
出力
{"age":"25","fullname":"sho hashimoto","name":"shokai","test":"1.23"}
より複雑に、mapやvectorを入れ子にした例
test2.cpp
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <boost/any.hpp>
#include "../json_builder.h"
using namespace json_builder;
using namespace std;
using namespace boost;
int main(int argc, char* argv[]){
map<string,any> obj;
obj["test"] = string("testtest");
map<string,any> user;
user["name"] = string("shokai");
user["fullname"] = string("sho hashimoto");
user["age"] = 25;
user["test"] = 6.78;
obj["user"] = user;
std::vector<any> vec;
vec.push_back(string("aaaa"));
vec.push_back(1234.56);
vec.push_back(string("hello work"));
vector<any> vec2;
vec2.push_back(string("nested std::vector"));
vec2.push_back(string("bbbbb"));
vec.push_back(vec2); // std::vecotrの入れ子
obj["params"] = vec;
string json = json_builder::toJson(obj);
cout << json << endl;
return 0;
}
出力
{"params":["aaaa","1234.56","hello work",["nested std::vector","bbbbb"]],
"test":"testtest","user":{"age":"25","fullname":"sho hashimoto","name":"shokai","test":"6.78"}}
出力したjsonが正しいかどうか、確認するためにrubyのjsonモジュールでrubyのオブジェクトに読み込むコードをtestフォルダに置いておいた。
{"params"=>["aaaa", "1234.56", "hello work", ["nested std::vector", "bbbbb"]],ちゃんと読み込めた。
"user"=>
{"name"=>"shokai", "fullname"=>"sho hashimoto", "test"=>"6.78", "age"=>"25"},
"test"=>"testtest"}
parse success
BOOST_FOREACHが涙が出るほど便利だった!!