はじめに
C++でjsonを扱う方法を調べたのでまとめました。
開発では、C++とpythonを連携して、jsonの解析などはライブラリの豊富なpythonで行い、計算は処理の早いC++で行う方法が開発しやすいですが、
速度を意識する開発の場合、C++ですべてを行いたい場合があります。そのような時に使えるのがこのライブラリです。
環境
- ubuntu:18.04.2
- g++:7.4.0
インストール
インストールはapt install rapidjson-dev
だけです。ヘッダオンリーのライブラリなのでインストール後のコンパイルでは特に何もせずに使用できます。今回の例でいうとg++ cppMain.cpp -o cppMain
でコンパイルしています。
rapidjsonの使い方
json形式のcharの解析
json形式のcharの解析の使用例
json形式のchar(sample_json)をrapidjsonのDocumentに読み込ませて値を取得して標準出力をしています。
# include "rapidjson/document.h"
# include "rapidjson/error/en.h"
# include <iostream>
static const char* sample_json = R"(
{
"pets": "dog",
"age": 5,
"like": [
"walking",
"eating",
"hamster"
]
}
)";
int main(){
rapidjson::Document doc;
doc.Parse(sample_json);
if(doc.HasParseError()) {
std::cout << "error offset:" << doc.GetErrorOffset() << std::endl;
std::cout << "error pase:" << rapidjson::GetParseError_En(doc.GetParseError()) << std::endl;
return 1;
}
std::cout << "---- string ----" << std::endl;
if(!doc["pets"].IsString()) {
std::cout << "pets value is not string" << std::endl;
return 1;
}
const char* a = doc["pets"].GetString();
std::cout << "pets value:" << a << std::endl;
std::cout << "---- list ----" << std::endl;
const rapidjson::Value& c = doc["like"].GetArray();
for (auto& d : c.GetArray()) {
const char* e = d.GetString();
std::cout << "list value:" << e << std::endl;
}
std::cout << "---- key list ----" << std::endl;
for(rapidjson::Value::ConstMemberIterator itr = doc.MemberBegin(); itr != doc.MemberEnd(); ++itr) {
std::cout << "key name:" << itr->name.GetString() << std::endl;
}
return 0;
}
json形式のcharの解析の説明
jsonの解析
まず、rapidjsonのDocumentを宣言しています。このDocumentを主に使用してjsonの解析や値の取得などを行っていきます。次にDocumentのParse関数にsample_jsonを渡して解析しています。
その後、HasParseError関数で解析でエラーしたかを判断しています。
エラーが出た場合はGetErrorOffsetでエラーの位置、rapidjson::GetParseError_En(doc.GetParseError())でエラーの内容を取得しています。
rapidjson::Document doc;
doc.Parse(sample_json);
if(doc.HasParseError()) {
std::cout << "error offset:" << doc.GetErrorOffset() << std::endl;
std::cout << "error pase:" << rapidjson::GetParseError_En(doc.GetParseError()) << std::endl;
return 1;
}
jsonの値の取得
解析が成功したDocumentを使用して、値を取得しています。値の型をIsString関数などのIs関数でチェックして、値の取得にはGetString関数やGetInt関数などそれぞれの型に一致したGet関数で値を取得できます。アレイやオブジェクトを取得するときはrapidjson::Valueに変換してから改めてStringやIntなどの型に応じたGet関数で値を取得しています。
全部の型チェックは手間なので後述するjsonスキーマで型チェックするのがいいかなと思います。
std::cout << "---- string ----" << std::endl;
if(!doc["pets"].IsString()) {
std::cout << "pets value is not string" << std::endl;
return 1;
}
const char* a = doc["pets"].GetString();
std::cout << "pets value:" << a << std::endl;
std::cout << "---- list ----" << std::endl;
const rapidjson::Value& c = doc["like"].GetArray();
for (auto& d : c.GetArray()) {
const char* e = d.GetString();
std::cout << "list value:" << e << std::endl;
}
json形式のファイルの解析
json形式のファイルの解析の使用例
pet.jsonという名前のファイルを読み込んで、そのjsonのpetsキーの文字列を抜き出し表示しています。
# include "rapidjson/document.h"
# include "rapidjson/istreamwrapper.h"
# include <fstream>
# include <iostream>
int main(){
std::cout << "---- file read ----" << std::endl;
std::ifstream ifs("pet.json");
rapidjson::IStreamWrapper isw(ifs);
std::cout << "---- file parse ----" << std::endl;
rapidjson::Document doc;
doc.ParseStream(isw);
std::cout << "---- string ----" << std::endl;
const char* a = doc["pets"].GetString();
std::cout << "pets value:" << a << std::endl;
return 0;
}
json形式のファイルの解析の説明
jsonファイルの読み込み
std::ifstreamクラスを使用してpet.jsonを読み込みます。その後、rapidjson::IStreamWrapperへファイルを読み込んだクラスを入れることでrapidjson用のストリームラッパー形式にします。
std::cout << "---- file read ----" << std::endl;
std::ifstream ifs("pet.json");
rapidjson::IStreamWrapper isw(ifs);
jsonのストリームラッパーの解析
charの解析と同様にrapidjsonのDocumentを宣言します。
charの解析では、Parse関数を使用していましたが、今回はParseStream関数に先ほど作成したrapidjsonのストリームラッパーを読み込ませて解析します。あとは、charと同様にdocを使用して値のチェックや値の取得、変更などができます。
std::cout << "---- file parse ----" << std::endl;
rapidjson::Document doc;
doc.ParseStream(isw);
jsonスキーマでのバリデーション
jsonスキーマでのバリデーションの使用例
jsonスキーマ用ファイルとチェック対象のjsonファイルを読み込んで、チェック対象のjsonがスキーマのチェックが通るかを調べています。
# include "rapidjson/document.h"
# include "rapidjson/istreamwrapper.h"
# include "rapidjson/schema.h"
# include "rapidjson/stringbuffer.h"
# include <fstream>
# include <iostream>
int main(){
std::cout << "---- schema file read ----" << std::endl;
std::ifstream schema_ifs("schema.json");
rapidjson::IStreamWrapper schema_isw(schema_ifs);
std::cout << "---- schema file parse ----" << std::endl;
rapidjson::Document schema_doc;
schema_doc.ParseStream(schema_isw);
rapidjson::SchemaDocument schema(schema_doc);
rapidjson::SchemaValidator validator(schema);
std::cout << "---- json file read ----" << std::endl;
std::ifstream ifs("pet.json");
rapidjson::IStreamWrapper isw(ifs);
std::cout << "---- json file parse ----" << std::endl;
rapidjson::Document doc;
doc.ParseStream(isw);
std::cout << "---- json validator ----" << std::endl;
if (!doc.Accept(validator)) {
rapidjson::StringBuffer sb;
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
std::cout << "Invalid schema:" << sb.GetString() << std::endl;
std::cout << "Invalid keyword:" << validator.GetInvalidSchemaKeyword() << std::endl;
}
return 0;
}
jsonスキーマでのバリデーションの説明
jsonスキーマファイルの読み込み
上で説明したようにjsonスキーマファイルを読み込んでDocumentに変換します。変換後のDocumentをスキーマ用のDocumentに変換して、Validatorクラスを生成するときに与えてvalidatorを作成しています。
std::cout << "---- schema file read ----" << std::endl;
std::ifstream schema_ifs("schema.json");
rapidjson::IStreamWrapper schema_isw(schema_ifs);
std::cout << "---- schema file parse ----" << std::endl;
rapidjson::Document schema_doc;
schema_doc.ParseStream(schema_isw);
rapidjson::SchemaDocument schema(schema_doc);
std::cout << "---- schema set validate ----" << std::endl;
rapidjson::SchemaValidator validator(schema);
チェック対象ファイルの読み込み
上で説明したようにjsonファイルを読み込んでDocumentに変換します。チェック対象がファイルじゃない(ストリーム)のときも上で説明したようにDocumentに変換します。
チェック対象Documentのvalidate
チェック対象のDocumentのAccept関数に上で作成したvalidatorを入れることでチェックがOKかNGかわかります。OKの場合はAcceptがtrueを返却するため、以下のようにif文内にエラーを表示する処理を入れています。
std::cout << "---- json validator ----" << std::endl;
if (!doc.Accept(validator)) {
rapidjson::StringBuffer sb;
validator.GetInvalidDocumentPointer().StringifyUriFragment(sb);
std::cout << "Invalid schema:" << sb.GetString() << std::endl;
std::cout << "Invalid keyword:" << validator.GetInvalidSchemaKeyword() << std::endl;
}
バリデーションエラー時の表示
Invalid schema:#/age
Invalid keyword:type
おわりに
C++でrapidjsonを使用してjsonを読み込む方法をまとめました。型を意識しなければならないのでpythonに比べれば若干手間ですが、簡単にjsonを読み込めました。さらにjsonスキーマを用いたバリデーションやシリアライズもできるため、pythonでできるjson系の操作は一通りできそうです。