Search code examples
c++boostboost-propertytree

Change how boost::property_tree reads translates strings to bool


I've gotten lost in the header files for the boost property_tree and given the lack of documentation around the lower layers, I've decided to ask what the easy way is to over-ride the stream translator to change how Boolean values are parsed.

The problem is that on the input side of a property tree, there are users, and they can modify the configuration files. A Boolean value might be specified in a number of ways, like:

dosomething.enabled=true
dosomething.enabled=trUE
dosomething.enabled=yes
dosomething.enabled=ON
dosomething.enabled=1

The default behaviour is to check for 0 or 1 and then use

std::ios_base::boolalpha 

to get the stream to try to parse the value in the appropriate manner for the current locale...which could be insane if we try to send a configuration file to international customers.

So what's the easiest way to override this behaviour or bool only? Not only easiest to implement, but easiest to use - so that the users of my class which derives from iptree don't need to do something special for Boolean values.

Thanks!


Solution

  • You can specialize boost::property_tree::translator_between so that a property tree will use a custom translator for a bool value type. This specialization must be visible (i.e. #included) by clients wanting the customized behavior. Here's a working example:

    #include <iostream>
    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/json_parser.hpp>
    #include <boost/algorithm/string/predicate.hpp>
    
    // Custom translator for bool (only supports std::string)
    struct BoolTranslator
    {
        typedef std::string internal_type;
        typedef bool        external_type;
    
        // Converts a string to bool
        boost::optional<external_type> get_value(const internal_type& str)
        {
            if (!str.empty())
            {
                using boost::algorithm::iequals;
    
                if (iequals(str, "true") || iequals(str, "yes") || str == "1")
                    return boost::optional<external_type>(true);
                else
                    return boost::optional<external_type>(false);
            }
            else
                return boost::optional<external_type>(boost::none);
        }
    
        // Converts a bool to string
        boost::optional<internal_type> put_value(const external_type& b)
        {
            return boost::optional<internal_type>(b ? "true" : "false");
        }
    };
    
    /*  Specialize translator_between so that it uses our custom translator for
        bool value types. Specialization must be in boost::property_tree
        namespace. */
    namespace boost {
    namespace property_tree {
    
    template<typename Ch, typename Traits, typename Alloc> 
    struct translator_between<std::basic_string< Ch, Traits, Alloc >, bool>
    {
        typedef BoolTranslator type;
    };
    
    } // namespace property_tree
    } // namespace boost
    
    int main()
    {
        boost::property_tree::iptree pt;
    
        read_json("test.json", pt);
        int i = pt.get<int>("number");
        int b = pt.get<bool>("enabled");
        std::cout << "i=" << i << " b=" << b << "\n";
    }
    

    test.json:

    {
        "number" : 42,
        "enabled" : "Yes"
    }
    

    Output:

    i=42 b=1
    

    Please note that this example assumes that the property tree is case insensitive and uses std::string. If you want BoolTranslator to be more general, you'll have to make BoolTranslator a template and provide specializations for wide strings and case sensitive comparisons.