Boost Property Tree
ライブラリ
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;
}