##はじめに
学生の頃からプログラムを書いていますが、
当時は自分はわからなかったことが、今になってわかったことがありました。
それは、関数の引数と返り値をしっかりと設計することです。
それについて書いてみようと思います。
タイトルにはmapと書いていますが、mapも引数や返り値にするのを控えたいです。
##jsonを引数、または返り値にしてしまった時の代償
API叩いたり、システムの内部パラメーターとして保存するとき、
よく使うフォーマットがjsonです。
json読み込みの関数を書いてみます。
boostのptreeを使います。
{
id:"1",
data:"I am kikochan"
}
### よくない方法
void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
boost::property_tree::read_json(jsonFile, jsonPtree);
}
void main(){
//呼び出し
boost::property_tree::ptree pt;
read_json_file("test.json",&pt);
std::string id = pt.get_optional<std::string>("id");
std::string data = pt.get_optional<std::string>("data");
//また別の処理時に使いたいので呼び出し
//この例では同じ関数内で呼び出しているけど、
//別のファイル、別の処理で呼び出していると仮定してください。
//結果、jsonのキーはなんだっけとか、同じ処理を書いてしまうことが増えてしまいます。
boost::property_tree::ptree pt;
read_json_file("test.json",&pt);
std::string id = pt.get_optional<std::string>("id");
std::string data = pt.get_optional<std::string>("data");
}
この実装はその時はいいかもしれませんが、
後々、ptreeで値渡しを行なっていると、パッとみた時に、
型がわからなくなるのと、取得する際に都度jsonのキーを入力する必要が出ます。
これは良くないです。
### まだマシな方法
void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
boost::property_tree::read_json(jsonFile, jsonPtree);
}
//サブルーチンにして、あちこちから呼び出す。
void getParameter(std::string& id, std::string& data){
boost::property_tree::ptree pt;
read_json_file("test.json",&pt);
id = pt.get_optional<std::string>("id");
data = pt.get_optional<std::string>("data");
}
void main(){
std::string id;
std::string data;
//jsonのキーの心配はなく、IDEのコード補完も効くから上の例より良いです。
getParameter(&id,&data);
}
main関数でptreeを取得しパースするのではなく、
一度、間に関数を挟みます。
その関数にptreeの取得とパースを一任する方法です。
こうすると、idとdataは型が決まっているので、IDEのコード補完が効きますし、
都度、jsonから情報を読み込むのにパースの処理を書く必要がありません。
### 一番いい方法
//jsonをクラスとして定義
//コンストラクタ内では、自動的にjsonを読み込みます。
//今回のクラスはjsonの読み込みのみになります。
//jsonのwriteを行いたい場合は、setterを定義し実装してください。
class TestJsonObject{
std::string id_;
std::string data_;
void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
boost::property_tree::read_json(jsonFile, jsonPtree);
}
public:
TestJsonObject(){
boost::property_tree::ptree pt;
read_json_file("test.json",pt);
id_ = *pt.get_optional<std::string>("id");
data_ = *pt.get_optional<std::string>("data");
}
std::string id(){return id_;}//getter
std::string data(){return data_;}//getter
};
void main(){
TestJsonObject json;
std::string id = json.id();
std::string data = json.data();
}
jsonはデータですので、それを包括するデータクラスたるものを作成することをお勧めします。
そのクラスには、自動的にjsonのデコード、または外部からjson stringを入力した時に、
メンバ変数に格納をする仕組みを実装します。
jsonのパラメータが変更された場合は、そのデータクラスを修正すれば大丈夫ですし、
IDEでももちろんコード補完は行われます。
c++にはデコード、エンコードライブラリはないので手実装しましたが、
SwiftではCodableがありますので、そのようなライブラリがあればそちらを使うことをお勧めします。
最後に
プログラムの実装はなるべく分離度を高くすることが、
効率的な開発に繋がります。
今回は、jsonにフォーカスしての分離の高めかたになります。
少しでもラップしてあげることで、わかりやすくなります。
リストの場合
{
"id" : "0"
"lists": [
{
"id":"1",
"data":"I am kikochan"
},{
"id":"2",
"data":"I am kikochan 2"
}
]
}
class TestJsonObject{
public:
//コンストラクタをprivateにすることで外部からオブジェクトの生成は禁止する。
//friendでTestJsonObjectはprivateのアクセスを許可する。
//なので、TestJsonObjectはTestJsonObjectIdDataを作れる
class TestJsonObjectIdData{
friend TestJsonObject;
std::string id_;
std::string data_;
TestJsonObjectIdData() = default;
public:
std::string id(){return id_;}
std::string data(){return data_;}
};
private:
std::string id_;
std::vector<TestJsonObjectIdData> lists_;
void read_json_file(std::string jsonFile, boost::property_tree::ptree& jsonPtree) {
boost::property_tree::read_json(jsonFile, jsonPtree);
}
public:
TestJsonObject(){
boost::property_tree::ptree pt;
read_json_file("test.json",pt);
id_ = *pt.get_optional<std::string>("id");
for (ptree::value_type& list : pt.get_child("lists")) {
TestJsonObjectIdData testJsonObjectIdData;
auto info = list.second;
testJsonObjectIdData.id_ = *info.get_optional<std::string>("id");
testJsonObjectIdData.data_ = *info.get_optional<std::string>("data");
lists_.push_back(testJsonObjectIdData);
}
}
std::string id(){return id_;}
std::vector<TestJsonObjectIdData> lists(){return lists_;}
};
int main(){
TestJsonObject testJsonObject;
auto k = testJsonObject.lists();
std::cout << testJsonObject.id() << std::endl;
for(auto &&s : k){
std::cout << s.id() << std::endl;
std::cout << s.data() << std::endl;
}
}