Boost Property Tree

Validation LibraryC++BoostConfiguration ManagementHierarchical DataJSONXMLINI

Library

Boost Property Tree

Overview

Boost Property Tree is a hierarchical data structure manipulation library provided as part of the C++ Boost library collection. It primarily provides reading, writing, and basic validation functionality for configuration data such as JSON, XML, and INI files. Built around the boost::property_tree::ptree class, it offers a unified API for different data formats, enabling concise configuration file management and hierarchical data operations.

Details

Boost Property Tree is a lightweight library for reading, writing, and validating structured data. Internally, it uses a tree structure of key-value pairs and can uniformly handle JSON, XML, INI, and INFO format files. It specializes in configuration file management and hierarchical data operations, providing validation features mainly including type checking, required field verification, and default value setting. While it doesn't have complete schema validation capabilities, it offers high convenience in configuration data management through its concise API and lightweight design.

Key Features

  • Multiple Format Support: Operate JSON, XML, INI, and INFO format files with unified API
  • Hierarchical Data Structure: Intuitive manipulation of hierarchical data through tree structure
  • Type Safety: Template-based type-safe value retrieval
  • Validation Features: Basic type checking and required field verification
  • Lightweight Design: Easy integration as header-only library
  • Boost Ecosystem: Stable quality as part of Boost libraries

Pros and Cons

Pros

  • Unified API for different data formats (JSON, XML, INI)
  • High reliability and stability as part of Boost libraries
  • Lightweight and easy to integrate (header-only)
  • User-friendly interface specialized for configuration file management
  • Type-safe value retrieval with default value support
  • Rich documentation and community support

Cons

  • Does not provide complete schema validation functionality
  • Not suitable for large-scale data processing (high memory usage)
  • Limited array handling (all nodes are key-value pairs)
  • Information loss during round-trip conversion (removal of comments and whitespace)
  • Difficult to implement complex validation rules
  • Not suitable for performance-critical applications

References

Code Examples

Installation and Basic Setup

# Install Boost using vcpkg
vcpkg install boost-property-tree

# Without package manager
# Download and build entire Boost library
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

Basic Configuration File Loading and Validation

#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;

// Basic configuration structure
struct AppConfig {
    std::string app_name;
    int port;
    bool debug_mode;
    std::string log_level;
    double timeout_seconds;
};

// Configuration file loading and validation
class ConfigManager {
public:
    static AppConfig loadFromJson(const std::string& filename) {
        pt::ptree root;
        
        try {
            // Read JSON file
            pt::read_json(filename, root);
            
            // Basic validation and value retrieval
            AppConfig config;
            
            // Required field check (may throw exception)
            config.app_name = root.get<std::string>("app.name");
            config.port = root.get<int>("server.port");
            
            // Retrieval with default values
            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);
            
            // Custom validation
            if (config.port <= 0 || config.port > 65535) {
                throw std::runtime_error("Port number must be in range 1-65535");
            }
            
            if (config.timeout_seconds <= 0) {
                throw std::runtime_error("Timeout must be a positive value");
            }
            
            return config;
            
        } catch (const pt::json_parser_error& e) {
            throw std::runtime_error("JSON file parse error: " + std::string(e.what()));
        } catch (const pt::ptree_bad_path& e) {
            throw std::runtime_error("Required field not found: " + std::string(e.what()));
        } catch (const pt::ptree_bad_data& e) {
            throw std::runtime_error("Invalid data type: " + 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 file parse error: " + std::string(e.what()));
        }
    }
    
    static void saveToJson(const AppConfig& config, const std::string& filename) {
        pt::ptree root;
        
        // Set hierarchical structure
        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);
        
        // Save to JSON file
        pt::write_json(filename, root);
    }
};

int main() {
    try {
        // Load configuration file
        AppConfig config = ConfigManager::loadFromJson("config.json");
        
        std::cout << "Application name: " << config.app_name << std::endl;
        std::cout << "Port number: " << config.port << std::endl;
        std::cout << "Debug mode: " << (config.debug_mode ? "Enabled" : "Disabled") << std::endl;
        std::cout << "Log level: " << config.log_level << std::endl;
        std::cout << "Timeout: " << config.timeout_seconds << " seconds" << std::endl;
        
        // Save configuration
        ConfigManager::saveToJson(config, "config_backup.json");
        
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

Advanced Validation Features and Hierarchical Data Processing

#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;

// Complex configuration structure
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;
};

// Advanced configuration management class with validation features
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 empty array if path not found
        }
        
        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 empty map if path not found
        }
        
        return result;
    }

public:
    static DatabaseConfig parseDatabaseConfig(const pt::ptree& dbTree) {
        DatabaseConfig config;
        
        // Required field validation
        config.host = dbTree.get<std::string>("host");
        if (!isValidHostname(config.host)) {
            throw std::runtime_error("Invalid hostname: " + config.host);
        }
        
        config.port = dbTree.get<int>("port");
        if (!isValidPort(config.port)) {
            throw std::runtime_error("Invalid port number: " + std::to_string(config.port));
        }
        
        config.username = dbTree.get<std::string>("username");
        if (config.username.empty()) {
            throw std::runtime_error("Username is empty");
        }
        
        config.password = dbTree.get<std::string>("password");
        if (config.password.length() < 8) {
            throw std::runtime_error("Password must be at least 8 characters long");
        }
        
        config.database = dbTree.get<std::string>("database");
        if (config.database.empty()) {
            throw std::runtime_error("Database name is empty");
        }
        
        // Optional field validation
        config.max_connections = dbTree.get<int>("max_connections", 10);
        if (config.max_connections <= 0 || config.max_connections > 1000) {
            throw std::runtime_error("Max connections must be in range 1-1000");
        }
        
        config.connection_timeout = dbTree.get<double>("connection_timeout", 5.0);
        if (config.connection_timeout <= 0) {
            throw std::runtime_error("Connection timeout must be a positive value");
        }
        
        // Array parsing
        config.allowed_hosts = parseStringArray(dbTree, "allowed_hosts");
        for (const auto& host : config.allowed_hosts) {
            if (!isValidHostname(host)) {
                throw std::runtime_error("Invalid allowed hostname: " + host);
            }
        }
        
        return config;
    }
    
    static ServiceConfig loadServiceConfig(const std::string& filename) {
        pt::ptree root;
        
        try {
            pt::read_json(filename, root);
            
            ServiceConfig config;
            
            // Basic information validation
            config.name = root.get<std::string>("service.name");
            if (config.name.empty()) {
                throw std::runtime_error("Service name is empty");
            }
            
            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("Invalid version format: " + config.version);
            }
            
            // Endpoint parsing
            config.endpoints = parseStringMap(root, "service.endpoints");
            
            // Database configuration parsing
            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&) {
                // Skip if database configuration not found
            }
            
            // Environment variable parsing
            config.environment_variables = parseStringMap(root, "environment");
            
            return config;
            
        } catch (const pt::json_parser_error& e) {
            throw std::runtime_error("JSON file parse error: " + std::string(e.what()));
        } catch (const pt::ptree_bad_path& e) {
            throw std::runtime_error("Required field not found: " + std::string(e.what()));
        } catch (const pt::ptree_bad_data& e) {
            throw std::runtime_error("Invalid data type: " + std::string(e.what()));
        }
    }
    
    static void saveServiceConfig(const ServiceConfig& config, const std::string& filename) {
        pt::ptree root;
        
        // Set basic information
        root.put("service.name", config.name);
        root.put("service.version", config.version);
        
        // Set endpoints
        pt::ptree endpoints;
        for (const auto& endpoint : config.endpoints) {
            endpoints.put(endpoint.first, endpoint.second);
        }
        root.add_child("service.endpoints", endpoints);
        
        // Save database configurations
        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);
            
            // Allowed hosts array
            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);
        
        // Set environment variables
        pt::ptree environment;
        for (const auto& env : config.environment_variables) {
            environment.put(env.first, env.second);
        }
        root.add_child("environment", environment);
        
        // Save to file
        pt::write_json(filename, root);
    }
};

int main() {
    try {
        // Load complex configuration file
        ServiceConfig config = AdvancedConfigManager::loadServiceConfig("service_config.json");
        
        std::cout << "Service name: " << config.name << std::endl;
        std::cout << "Version: " << config.version << std::endl;
        
        std::cout << "Endpoints:" << std::endl;
        for (const auto& endpoint : config.endpoints) {
            std::cout << "  " << endpoint.first << ": " << endpoint.second << std::endl;
        }
        
        std::cout << "Database configurations:" << std::endl;
        for (const auto& db : config.databases) {
            std::cout << "  " << db.host << ":" << db.port << " (" << db.database << ")" << std::endl;
            std::cout << "    Max connections: " << db.max_connections << std::endl;
            std::cout << "    Timeout: " << db.connection_timeout << " seconds" << std::endl;
            
            if (!db.allowed_hosts.empty()) {
                std::cout << "    Allowed hosts: ";
                for (const auto& host : db.allowed_hosts) {
                    std::cout << host << " ";
                }
                std::cout << std::endl;
            }
        }
        
        std::cout << "Environment variables:" << std::endl;
        for (const auto& env : config.environment_variables) {
            std::cout << "  " << env.first << "=" << env.second << std::endl;
        }
        
        // Save configuration
        AdvancedConfigManager::saveServiceConfig(config, "service_config_backup.json");
        
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}

Conditional Validation and Error Handling

#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;

// Detailed validation error information
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) {}
};

// Validation result
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));
    }
};

// Base class for validation rules
class ValidationRule {
public:
    virtual ~ValidationRule() = default;
    virtual bool validate(const pt::ptree& tree, const std::string& path, 
                         ValidationResult& result) const = 0;
};

// Required field check
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, "Required field not found");
            return false;
        }
    }
};

// Type check
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; // Skip if path not found
        } catch (const pt::ptree_bad_data&) {
            result.addError(path, "Cannot convert to " + type_name + " type", 
                          tree.get<std::string>(path, ""), type_name);
            return false;
        }
    }
};

// Range check
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, "Value out of range", 
                              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; // Skip if path not found
        } catch (const pt::ptree_bad_data&) {
            return true; // Leave type errors to other rules
        }
    }
};

// Regular expression check
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, "Pattern does not match", value, pattern_description);
                return false;
            }
            return true;
        } catch (const pt::ptree_bad_path&) {
            return true; // Skip if path not found
        } catch (const pt::ptree_bad_data&) {
            return true; // Leave type errors to other rules
        }
    }
};

// Conditional validation
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; // Skip on condition evaluation error
        }
    }
};

// Comprehensive validator
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;
    }
};

// Usage example
int main() {
    try {
        // Configure validation rules
        ConfigValidator validator;
        
        // Basic rules
        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.-]+$)", "hostname format"));
        
        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));
        
        // Conditional rule
        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));
        
        // Load configuration file
        pt::ptree config;
        pt::read_json("complex_config.json", config);
        
        // Execute validation
        ValidationResult result = validator.validate(config);
        
        if (result.is_valid) {
            std::cout << "Configuration file is valid." << std::endl;
        } else {
            std::cout << "Configuration file has errors:" << std::endl;
            for (const auto& error : result.errors) {
                std::cout << "  Path: " << error.path << std::endl;
                std::cout << "  Error: " << error.message << std::endl;
                if (!error.actual_value.empty()) {
                    std::cout << "  Actual value: " << error.actual_value << std::endl;
                }
                if (!error.expected_format.empty()) {
                    std::cout << "  Expected format: " << error.expected_format << std::endl;
                }
                std::cout << std::endl;
            }
        }
        
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    
    return 0;
}