Boost Property Tree

バリデーションライブラリC++Boost設定管理階層データJSONXMLINI

ライブラリ

Boost Property Tree

概要

Boost Property Treeは、C++のBoostライブラリコレクションの一部として提供される階層データ構造操作ライブラリです。主にJSON、XML、INIファイルなどの設定データの読み書きと基本的なバリデーション機能を提供します。boost::property_tree::ptreeクラスを中心とした設計により、異なるデータ形式に対して統一されたAPIを提供し、設定ファイルの管理や階層データの操作を簡潔に行うことができます。

詳細

Boost Property Treeは、構造化データの読み書きとバリデーションを行うための軽量なライブラリです。内部的にはkey-valueペアの木構造を使用しており、JSON、XML、INI、INFO形式のファイルを統一的に扱うことができます。設定ファイルの管理や階層データの操作に特化しており、バリデーション機能としては主に型チェック、必須項目の確認、デフォルト値の設定などを提供します。完全なスキーマバリデーション機能は持ちませんが、簡潔なAPIと軽量な設計により、設定データの管理において高い利便性を発揮します。

主な特徴

  • 複数形式対応: JSON、XML、INI、INFO形式のファイルを統一APIで操作
  • 階層データ構造: 木構造による階層データの直感的な操作
  • 型安全性: テンプレートベースの型安全な値取得
  • バリデーション機能: 基本的な型チェックと必須項目の確認
  • 軽量設計: ヘッダーオンリーライブラリとして簡単に統合
  • Boostエコシステム: Boostライブラリの一部として安定した品質

メリット・デメリット

メリット

  • 異なるデータ形式(JSON、XML、INI)に対する統一されたAPI
  • Boostライブラリの一部として高い信頼性と安定性
  • 軽量で導入が簡単(ヘッダーオンリー)
  • 設定ファイルの管理に特化した使いやすいインターフェース
  • 型安全な値取得機能とデフォルト値のサポート
  • 豊富なドキュメントとコミュニティサポート

デメリット

  • 完全なスキーマバリデーション機能は提供しない
  • 大規模なデータ処理には向かない(メモリ使用量が多い)
  • 配列の扱いが限定的(すべてのノードがkey-valueペア)
  • 往復変換での情報損失(コメントや空白文字の削除)
  • 複雑なバリデーションルールの実装が困難
  • パフォーマンスが要求される用途には不向き

参考ページ

書き方の例

インストールと基本セットアップ

# vcpkgを使用してBoostをインストール
vcpkg install boost-property-tree

# パッケージマネージャーを使用しない場合
# Boostライブラリ全体をダウンロード・ビルド
wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2
tar xjf boost_1_83_0.tar.bz2
cd boost_1_83_0
./bootstrap.sh
./b2

基本的な設定ファイル読み込みとバリデーション

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <iostream>
#include <string>

namespace pt = boost::property_tree;

// 基本的な設定構造体
struct AppConfig {
    std::string app_name;
    int port;
    bool debug_mode;
    std::string log_level;
    double timeout_seconds;
};

// 設定ファイルの読み込みとバリデーション
class ConfigManager {
public:
    static AppConfig loadFromJson(const std::string& filename) {
        pt::ptree root;
        
        try {
            // JSONファイルを読み込み
            pt::read_json(filename, root);
            
            // 基本的なバリデーションと値の取得
            AppConfig config;
            
            // 必須項目のチェック(例外が発生する可能性)
            config.app_name = root.get<std::string>("app.name");
            config.port = root.get<int>("server.port");
            
            // デフォルト値付きの取得
            config.debug_mode = root.get<bool>("app.debug", false);
            config.log_level = root.get<std::string>("logging.level", "INFO");
            config.timeout_seconds = root.get<double>("server.timeout", 30.0);
            
            // カスタムバリデーション
            if (config.port <= 0 || config.port > 65535) {
                throw std::runtime_error("ポート番号は1-65535の範囲で指定してください");
            }
            
            if (config.timeout_seconds <= 0) {
                throw std::runtime_error("タイムアウト時間は正の値で指定してください");
            }
            
            return config;
            
        } catch (const pt::json_parser_error& e) {
            throw std::runtime_error("JSONファイルの解析エラー: " + std::string(e.what()));
        } catch (const pt::ptree_bad_path& e) {
            throw std::runtime_error("必須項目が見つかりません: " + std::string(e.what()));
        } catch (const pt::ptree_bad_data& e) {
            throw std::runtime_error("データ型が不正です: " + std::string(e.what()));
        }
    }
    
    static AppConfig loadFromXml(const std::string& filename) {
        pt::ptree root;
        
        try {
            pt::read_xml(filename, root);
            
            AppConfig config;
            config.app_name = root.get<std::string>("config.app.name");
            config.port = root.get<int>("config.server.port");
            config.debug_mode = root.get<bool>("config.app.debug", false);
            config.log_level = root.get<std::string>("config.logging.level", "INFO");
            config.timeout_seconds = root.get<double>("config.server.timeout", 30.0);
            
            return config;
            
        } catch (const pt::xml_parser_error& e) {
            throw std::runtime_error("XMLファイルの解析エラー: " + std::string(e.what()));
        }
    }
    
    static void saveToJson(const AppConfig& config, const std::string& filename) {
        pt::ptree root;
        
        // 階層構造の設定
        root.put("app.name", config.app_name);
        root.put("app.debug", config.debug_mode);
        root.put("server.port", config.port);
        root.put("server.timeout", config.timeout_seconds);
        root.put("logging.level", config.log_level);
        
        // JSONファイルに保存
        pt::write_json(filename, root);
    }
};

int main() {
    try {
        // 設定ファイルの読み込み
        AppConfig config = ConfigManager::loadFromJson("config.json");
        
        std::cout << "アプリケーション名: " << config.app_name << std::endl;
        std::cout << "ポート番号: " << config.port << std::endl;
        std::cout << "デバッグモード: " << (config.debug_mode ? "有効" : "無効") << std::endl;
        std::cout << "ログレベル: " << config.log_level << std::endl;
        std::cout << "タイムアウト: " << config.timeout_seconds << "秒" << std::endl;
        
        // 設定の保存
        ConfigManager::saveToJson(config, "config_backup.json");
        
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

高度なバリデーション機能と階層データ処理

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <vector>
#include <map>
#include <optional>
#include <regex>

namespace pt = boost::property_tree;

// 複雑な設定構造体
struct DatabaseConfig {
    std::string host;
    int port;
    std::string username;
    std::string password;
    std::string database;
    int max_connections;
    double connection_timeout;
    std::vector<std::string> allowed_hosts;
};

struct ServiceConfig {
    std::string name;
    std::string version;
    std::map<std::string, std::string> endpoints;
    std::vector<DatabaseConfig> databases;
    std::map<std::string, std::string> environment_variables;
};

// 高度なバリデーション機能を持つ設定管理クラス
class AdvancedConfigManager {
private:
    static bool isValidHostname(const std::string& hostname) {
        std::regex hostname_regex(R"(^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$)");
        return std::regex_match(hostname, hostname_regex);
    }
    
    static bool isValidPort(int port) {
        return port > 0 && port <= 65535;
    }
    
    static std::vector<std::string> parseStringArray(const pt::ptree& tree, const std::string& path) {
        std::vector<std::string> result;
        
        try {
            for (const auto& item : tree.get_child(path)) {
                result.push_back(item.second.get_value<std::string>());
            }
        } catch (const pt::ptree_bad_path&) {
            // パスが見つからない場合は空の配列を返す
        }
        
        return result;
    }
    
    static std::map<std::string, std::string> parseStringMap(const pt::ptree& tree, const std::string& path) {
        std::map<std::string, std::string> result;
        
        try {
            for (const auto& item : tree.get_child(path)) {
                result[item.first] = item.second.get_value<std::string>();
            }
        } catch (const pt::ptree_bad_path&) {
            // パスが見つからない場合は空のマップを返す
        }
        
        return result;
    }

public:
    static DatabaseConfig parseDatabaseConfig(const pt::ptree& dbTree) {
        DatabaseConfig config;
        
        // 必須項目のバリデーション
        config.host = dbTree.get<std::string>("host");
        if (!isValidHostname(config.host)) {
            throw std::runtime_error("無効なホスト名: " + config.host);
        }
        
        config.port = dbTree.get<int>("port");
        if (!isValidPort(config.port)) {
            throw std::runtime_error("無効なポート番号: " + std::to_string(config.port));
        }
        
        config.username = dbTree.get<std::string>("username");
        if (config.username.empty()) {
            throw std::runtime_error("ユーザー名が空です");
        }
        
        config.password = dbTree.get<std::string>("password");
        if (config.password.length() < 8) {
            throw std::runtime_error("パスワードは8文字以上で設定してください");
        }
        
        config.database = dbTree.get<std::string>("database");
        if (config.database.empty()) {
            throw std::runtime_error("データベース名が空です");
        }
        
        // オプション項目のバリデーション
        config.max_connections = dbTree.get<int>("max_connections", 10);
        if (config.max_connections <= 0 || config.max_connections > 1000) {
            throw std::runtime_error("最大接続数は1-1000の範囲で設定してください");
        }
        
        config.connection_timeout = dbTree.get<double>("connection_timeout", 5.0);
        if (config.connection_timeout <= 0) {
            throw std::runtime_error("接続タイムアウトは正の値で設定してください");
        }
        
        // 配列の解析
        config.allowed_hosts = parseStringArray(dbTree, "allowed_hosts");
        for (const auto& host : config.allowed_hosts) {
            if (!isValidHostname(host)) {
                throw std::runtime_error("無効な許可ホスト名: " + host);
            }
        }
        
        return config;
    }
    
    static ServiceConfig loadServiceConfig(const std::string& filename) {
        pt::ptree root;
        
        try {
            pt::read_json(filename, root);
            
            ServiceConfig config;
            
            // 基本情報のバリデーション
            config.name = root.get<std::string>("service.name");
            if (config.name.empty()) {
                throw std::runtime_error("サービス名が空です");
            }
            
            config.version = root.get<std::string>("service.version", "1.0.0");
            std::regex version_regex(R"(^\d+\.\d+\.\d+$)");
            if (!std::regex_match(config.version, version_regex)) {
                throw std::runtime_error("無効なバージョン形式: " + config.version);
            }
            
            // エンドポイントの解析
            config.endpoints = parseStringMap(root, "service.endpoints");
            
            // データベース設定の解析
            try {
                for (const auto& dbItem : root.get_child("databases")) {
                    DatabaseConfig dbConfig = parseDatabaseConfig(dbItem.second);
                    config.databases.push_back(dbConfig);
                }
            } catch (const pt::ptree_bad_path&) {
                // データベース設定が見つからない場合はスキップ
            }
            
            // 環境変数の解析
            config.environment_variables = parseStringMap(root, "environment");
            
            return config;
            
        } catch (const pt::json_parser_error& e) {
            throw std::runtime_error("JSONファイルの解析エラー: " + std::string(e.what()));
        } catch (const pt::ptree_bad_path& e) {
            throw std::runtime_error("必須項目が見つかりません: " + std::string(e.what()));
        } catch (const pt::ptree_bad_data& e) {
            throw std::runtime_error("データ型が不正です: " + std::string(e.what()));
        }
    }
    
    static void saveServiceConfig(const ServiceConfig& config, const std::string& filename) {
        pt::ptree root;
        
        // 基本情報の設定
        root.put("service.name", config.name);
        root.put("service.version", config.version);
        
        // エンドポイントの設定
        pt::ptree endpoints;
        for (const auto& endpoint : config.endpoints) {
            endpoints.put(endpoint.first, endpoint.second);
        }
        root.add_child("service.endpoints", endpoints);
        
        // データベース設定の保存
        pt::ptree databases;
        for (const auto& db : config.databases) {
            pt::ptree dbTree;
            dbTree.put("host", db.host);
            dbTree.put("port", db.port);
            dbTree.put("username", db.username);
            dbTree.put("password", db.password);
            dbTree.put("database", db.database);
            dbTree.put("max_connections", db.max_connections);
            dbTree.put("connection_timeout", db.connection_timeout);
            
            // 許可ホストの配列
            pt::ptree allowedHosts;
            for (const auto& host : db.allowed_hosts) {
                pt::ptree hostItem;
                hostItem.put("", host);
                allowedHosts.push_back(std::make_pair("", hostItem));
            }
            dbTree.add_child("allowed_hosts", allowedHosts);
            
            databases.push_back(std::make_pair("", dbTree));
        }
        root.add_child("databases", databases);
        
        // 環境変数の設定
        pt::ptree environment;
        for (const auto& env : config.environment_variables) {
            environment.put(env.first, env.second);
        }
        root.add_child("environment", environment);
        
        // ファイルに保存
        pt::write_json(filename, root);
    }
};

int main() {
    try {
        // 複雑な設定ファイルの読み込み
        ServiceConfig config = AdvancedConfigManager::loadServiceConfig("service_config.json");
        
        std::cout << "サービス名: " << config.name << std::endl;
        std::cout << "バージョン: " << config.version << std::endl;
        
        std::cout << "エンドポイント:" << std::endl;
        for (const auto& endpoint : config.endpoints) {
            std::cout << "  " << endpoint.first << ": " << endpoint.second << std::endl;
        }
        
        std::cout << "データベース設定:" << std::endl;
        for (const auto& db : config.databases) {
            std::cout << "  " << db.host << ":" << db.port << " (" << db.database << ")" << std::endl;
            std::cout << "    最大接続数: " << db.max_connections << std::endl;
            std::cout << "    タイムアウト: " << db.connection_timeout << "秒" << std::endl;
            
            if (!db.allowed_hosts.empty()) {
                std::cout << "    許可ホスト: ";
                for (const auto& host : db.allowed_hosts) {
                    std::cout << host << " ";
                }
                std::cout << std::endl;
            }
        }
        
        std::cout << "環境変数:" << std::endl;
        for (const auto& env : config.environment_variables) {
            std::cout << "  " << env.first << "=" << env.second << std::endl;
        }
        
        // 設定の保存
        AdvancedConfigManager::saveServiceConfig(config, "service_config_backup.json");
        
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

条件付きバリデーションとエラーハンドリング

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/optional.hpp>
#include <functional>
#include <vector>
#include <memory>

namespace pt = boost::property_tree;

// バリデーションエラーの詳細情報
struct ValidationError {
    std::string path;
    std::string message;
    std::string actual_value;
    std::string expected_format;
    
    ValidationError(const std::string& p, const std::string& m, 
                   const std::string& actual = "", const std::string& expected = "")
        : path(p), message(m), actual_value(actual), expected_format(expected) {}
};

// バリデーション結果
struct ValidationResult {
    bool is_valid;
    std::vector<ValidationError> errors;
    
    ValidationResult() : is_valid(true) {}
    
    void addError(const ValidationError& error) {
        errors.push_back(error);
        is_valid = false;
    }
    
    void addError(const std::string& path, const std::string& message,
                  const std::string& actual = "", const std::string& expected = "") {
        addError(ValidationError(path, message, actual, expected));
    }
};

// バリデーションルールの基底クラス
class ValidationRule {
public:
    virtual ~ValidationRule() = default;
    virtual bool validate(const pt::ptree& tree, const std::string& path, 
                         ValidationResult& result) const = 0;
};

// 必須項目チェック
class RequiredRule : public ValidationRule {
public:
    bool validate(const pt::ptree& tree, const std::string& path, 
                 ValidationResult& result) const override {
        try {
            tree.get<std::string>(path);
            return true;
        } catch (const pt::ptree_bad_path&) {
            result.addError(path, "必須項目が見つかりません");
            return false;
        }
    }
};

// 型チェック
template<typename T>
class TypeRule : public ValidationRule {
private:
    std::string type_name;
    
public:
    TypeRule(const std::string& name) : type_name(name) {}
    
    bool validate(const pt::ptree& tree, const std::string& path, 
                 ValidationResult& result) const override {
        try {
            auto value = tree.get<T>(path);
            return true;
        } catch (const pt::ptree_bad_path&) {
            return true; // パスが見つからない場合はスキップ
        } catch (const pt::ptree_bad_data&) {
            result.addError(path, type_name + "型に変換できません", 
                          tree.get<std::string>(path, ""), type_name);
            return false;
        }
    }
};

// 範囲チェック
template<typename T>
class RangeRule : public ValidationRule {
private:
    T min_value;
    T max_value;
    
public:
    RangeRule(T min_val, T max_val) : min_value(min_val), max_value(max_val) {}
    
    bool validate(const pt::ptree& tree, const std::string& path, 
                 ValidationResult& result) const override {
        try {
            T value = tree.get<T>(path);
            if (value < min_value || value > max_value) {
                result.addError(path, "値が範囲外です", 
                              std::to_string(value), 
                              std::to_string(min_value) + "-" + std::to_string(max_value));
                return false;
            }
            return true;
        } catch (const pt::ptree_bad_path&) {
            return true; // パスが見つからない場合はスキップ
        } catch (const pt::ptree_bad_data&) {
            return true; // 型エラーは他のルールに任せる
        }
    }
};

// 正規表現チェック
class RegexRule : public ValidationRule {
private:
    std::regex pattern;
    std::string pattern_description;
    
public:
    RegexRule(const std::string& regex_pattern, const std::string& description)
        : pattern(regex_pattern), pattern_description(description) {}
    
    bool validate(const pt::ptree& tree, const std::string& path, 
                 ValidationResult& result) const override {
        try {
            std::string value = tree.get<std::string>(path);
            if (!std::regex_match(value, pattern)) {
                result.addError(path, "パターンに一致しません", value, pattern_description);
                return false;
            }
            return true;
        } catch (const pt::ptree_bad_path&) {
            return true; // パスが見つからない場合はスキップ
        } catch (const pt::ptree_bad_data&) {
            return true; // 型エラーは他のルールに任せる
        }
    }
};

// 条件付きバリデーション
class ConditionalRule : public ValidationRule {
private:
    std::string condition_path;
    std::function<bool(const pt::ptree&)> condition;
    std::vector<std::unique_ptr<ValidationRule>> rules;
    
public:
    ConditionalRule(const std::string& cond_path, 
                   std::function<bool(const pt::ptree&)> cond)
        : condition_path(cond_path), condition(cond) {}
    
    void addRule(std::unique_ptr<ValidationRule> rule) {
        rules.push_back(std::move(rule));
    }
    
    bool validate(const pt::ptree& tree, const std::string& path, 
                 ValidationResult& result) const override {
        try {
            if (condition(tree)) {
                bool all_valid = true;
                for (const auto& rule : rules) {
                    if (!rule->validate(tree, path, result)) {
                        all_valid = false;
                    }
                }
                return all_valid;
            }
            return true;
        } catch (const std::exception&) {
            return true; // 条件評価エラーの場合はスキップ
        }
    }
};

// 包括的なバリデーター
class ConfigValidator {
private:
    std::map<std::string, std::vector<std::unique_ptr<ValidationRule>>> rules;
    
public:
    void addRule(const std::string& path, std::unique_ptr<ValidationRule> rule) {
        rules[path].push_back(std::move(rule));
    }
    
    ValidationResult validate(const pt::ptree& tree) const {
        ValidationResult result;
        
        for (const auto& pathRules : rules) {
            const std::string& path = pathRules.first;
            const auto& pathRuleList = pathRules.second;
            
            for (const auto& rule : pathRuleList) {
                rule->validate(tree, path, result);
            }
        }
        
        return result;
    }
};

// 使用例
int main() {
    try {
        // バリデーションルールの設定
        ConfigValidator validator;
        
        // 基本的なルール
        validator.addRule("server.host", std::make_unique<RequiredRule>());
        validator.addRule("server.host", std::make_unique<TypeRule<std::string>>("string"));
        validator.addRule("server.host", std::make_unique<RegexRule>(
            R"(^[a-zA-Z0-9.-]+$)", "ホスト名形式"));
        
        validator.addRule("server.port", std::make_unique<RequiredRule>());
        validator.addRule("server.port", std::make_unique<TypeRule<int>>("int"));
        validator.addRule("server.port", std::make_unique<RangeRule<int>>(1, 65535));
        
        // 条件付きルール
        auto sslRule = std::make_unique<ConditionalRule>(
            "server.ssl_enabled",
            [](const pt::ptree& tree) {
                return tree.get<bool>("server.ssl_enabled", false);
            }
        );
        sslRule->addRule(std::make_unique<RequiredRule>());
        sslRule->addRule(std::make_unique<TypeRule<std::string>>("string"));
        validator.addRule("server.ssl_cert_path", std::move(sslRule));
        
        // 設定ファイルの読み込み
        pt::ptree config;
        pt::read_json("complex_config.json", config);
        
        // バリデーション実行
        ValidationResult result = validator.validate(config);
        
        if (result.is_valid) {
            std::cout << "設定ファイルは有効です。" << std::endl;
        } else {
            std::cout << "設定ファイルにエラーがあります:" << std::endl;
            for (const auto& error : result.errors) {
                std::cout << "  パス: " << error.path << std::endl;
                std::cout << "  エラー: " << error.message << std::endl;
                if (!error.actual_value.empty()) {
                    std::cout << "  実際の値: " << error.actual_value << std::endl;
                }
                if (!error.expected_format.empty()) {
                    std::cout << "  期待される形式: " << error.expected_format << std::endl;
                }
                std::cout << std::endl;
            }
        }
        
    } catch (const std::exception& e) {
        std::cerr << "エラー: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}