C++でJsonの扱うためのライブラリ「nlohmann」を使って、JsonファイルをC++のクラスにマッピングする方法です。
開発環境
- C++14
- visual studio 2022
nlohmannライブラリの準備
Githubからzipダウンロードして、プロジェクトに配置しました。
今回は直接コードを参照させるのでzip解凍後の「json-develop\include」フォルダ下にある「nlohmann」フォルダへの参照を設定します。
Visual Studioではプロジェクトの「プロパティ>C/C++>全般>追加のインクルードディレクトリ」に追加します。例は示しやすいように絶対パスで設定していますが、実際にはプロジェクトからの相対パスが適切です。
Jsonのマッピング処理
Json定義
Jsonで扱える型を複数用意しました。
{
"Key": "Key1",
"Value": 1,
"IsTest": true,
"ObjData": {
"Key": "ObjKey"
},
"ObjList": [
{
"Key": "ObjKey1"
},
{
"Key": "ObjKey2"
}
]
}
Class定義
Jsonに対応したClass定義です。Structでも問題ありません。
class Obj {
public:
std::string Key;
};
class Data {
public:
std::string Key;
int Value;
bool IsTest;
Obj ObjData;
std::vector<Obj> ObjList;
};
マッピングの定義
void from_json(const json& j, Obj& p) {
p.Key = j.at("Key").get<std::string>();
}
void from_json(const json& j, Data& p) {
p.Key = j.at("Key").get<std::string>();
p.Value = j.at("Value").get<int>();
p.IsTest = j.at("IsTest").get<bool>();
p.ObjData = j.at("ObjData").get<Obj>();
p.ObjList = j.at("ObjList").get<std::vector<Obj>>();
}
from_json関数を定義することで、Jsonをクラスにマッピングすることができます。
これは第二引数を使った関数のオーバーロードによって実現される仕組みになっています。
ちなみにgetの代わりにget_toを使ってマッピングすることもできます。
j.at("ObjData").get_to(p.ObjData);
実行側の処理
int main()
{
std::ifstream stream("test.json");
if (!stream.is_open())
throw new std::exception("Failed open file.");
if (!json::accept(stream))
throw new std::exception("jsonのフォーマットが不正");
// json::acceptがフォーマットチェック時にpositionを進めてしまうので、先頭に戻す
stream.seekg(0, std::ios::beg);
json j = json::parse(stream);
std::cout << j.dump() << std::endl;
auto result = j.get<Data>();
std::cout << result.Key << std::endl;
std::cout << result.Value << std::endl;
std::cout << result.IsTest << std::endl;
std::cout << result.ObjData.Key << std::endl;
std::cout << result.ObjList[0].Key << std::endl;
std::cout << result.ObjList[1].Key << std::endl;
}
出力結果
{"IsTest":true,"Key":"Key1","ObjData":{"Key":"ObjKey"},"ObjList":[{"Key":"ObjKey1"},{"Key":"ObjKey2"}],"Value":1}
Key1
1
1
ObjKey
ObjKey1
ObjKey2
nullチェックなどのバリデーション
ここまでが基本的なJsonファイルの読み込み処理です。
次にバリデーションを設定します。
Jsonファイルの項目が未指定の場合
先ほどの例で、Jsonファイルの一部をコメントアウトします。
{
"Key": "Key1",
"Value": 1,
"IsTest": true,
// コメントアウト
//"ObjData": {
// "Key": "ObjKey"
//},
"ObjList": [
{
"Key": "ObjKey1"
},
{
"Key": "ObjKey2"
}
]
}
このまま実行するとacceptやparseでコメントが構文エラーになってしまうので、引数を追加で渡してコメントアウトを許可します。
if (!json::accept(stream, true))
throw new std::exception("jsonのフォーマットが不正");
stream.seekg(0, std::ios::beg);
json j = json::parse(stream, nullptr, true, true);
これでコメントアウトが効くようになるのですが、実行するとparseでObjDataが見つからずエラーになるためマッピング処理も変更します。
if (j.contains("ObjData")) {
p.ObjData = j.at("ObjData").get<Obj>();
}
contatins関数で項目の存在チェックができ、項目がない場合にマッピング処理がスキップされます。
Jsonファイルの項目にnullが指定された場合
次にObjDataにnullが指定された場合のバリデーションを組み込みます。
{
"Key": "Key1",
"Value": 1,
"IsTest": true,
"ObjData": null,
"ObjList": [
{
"Key": "ObjKey1"
},
{
"Key": "ObjKey2"
}
]
}
if (!j.at("ObjData").is_null()) {
p.ObjData = j.at("ObjData").get<Obj>();
}
このようなis_null関数によるチェックで、nullの場合にマッピング処理をスキップできます。
バリデーション処理をまとめる
null指定時や項目ごと未指定時のバリデーション処理を1つにまとめてみました。
template<class T>
T GetJsonOrDefault(const json& j, std::string key, T defaultValue = T()) {
if (!j.contains(key))
return defaultValue;
auto item = j.at(key);
if (item.is_null())
return defaultValue;
return item.get<T>();
}
template<class T>
std::shared_ptr<T> GetPtrOrNull(const json& j, std::string key) {
if (!j.contains(key))
return nullptr;
auto item = j.at(key);
if (item.is_null())
return nullptr;
return std::make_shared<T>(item.get<T>());
}
呼び出し側のソース
p.IsTest = GetJsonOrDefault<bool>(j, "IsTest", false);
p.ObjData = GetJsonOrDefault<Obj>(j, "ObjData");
// ポインタとして取得
std::shared_ptr<Obj> objPtr = GetPtrOrNull<Obj>(j, "ObjData");
取得できない場合のデフォルト値も一緒に指定できるようにしてあります。
最後に
まだまだnlohmann/jsonには機能がたくさんありますが、設定ファイルとして扱う分は記事に書いてある内容で十分かと思います。