LoginSignup
3
10

More than 3 years have passed since last update.

c++のrapidjsonでjsonを読み込む

Posted at

はじめに

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に読み込ませて値を取得して標準出力をしています。

cppMain.cpp
#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())でエラーの内容を取得しています。

cppMain.cpp
    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スキーマで型チェックするのがいいかなと思います。

cppMain.cpp
    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キーの文字列を抜き出し表示しています。

cppMain.cpp
#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用のストリームラッパー形式にします。

cppMain.cpp
    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を使用して値のチェックや値の取得、変更などができます。

cppMain.cpp
    std::cout << "---- file parse ----" << std::endl;
    rapidjson::Document doc;
    doc.ParseStream(isw);

jsonスキーマでのバリデーション

jsonスキーマでのバリデーションの使用例

jsonスキーマ用ファイルとチェック対象のjsonファイルを読み込んで、チェック対象のjsonがスキーマのチェックが通るかを調べています。

cppMain.cpp
#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を作成しています。

cppMain.cpp
    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文内にエラーを表示する処理を入れています。

cppMain.cpp
    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系の操作は一通りできそうです。

3
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
10