15

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!

Arunas
  • 1,282
  • 1
  • 11
  • 17

2 Answers2

22

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
  • Well, wow. Thanks @Emile - this works. It is currently indistinguishable from magic. I haven't tried the output yet, but it looks like the 'false' output works only by chance; shouldn't there be quotes around the false? – Arunas Mar 19 '12 at 01:41
  • haha, and of course since I'm writing Boost test cases for the code, it's also apparent that `str == "0"` doesn't belong in the same sentence as `iequals(str, "yes")` – Arunas Mar 19 '12 at 02:03
  • 1
    @Arunas : Yes there should be quotes around "false". I'm surprised that even compiled. If you get around to studying *partial template specialization*, this answer won't appear so magical. :-) – Emile Cormier Mar 19 '12 at 02:52
  • 2
    It's more magical because of knowing which template to specialize! – Arunas Mar 19 '12 at 04:35
  • 2
    actually the translator_between part is not necessary as you can specify a custom translator when calling methods of the property_tree like get like so: properties.get( "status", EStatusTranslator() ) where EStatusTranslator is a translator like your BoolTranslator – f4. Feb 04 '13 at 10:07
  • @f4 - while this is a nice solution for a small number of uses, it doesn't quite fall into the class of 'so that the users of my class don't need to do something special to read boolean values'. But +1 for a useful tip. – Arunas Jun 04 '14 at 14:30
  • @Arunas indeed :) And btw I've been using variants of this BoolTranslator ever since but I had long forgotten the translator_between trick... which could have come in handy recently ;) – f4. Jun 04 '14 at 19:27
1

There is also a good example at theboostcpplibraries.com.

Based on that, I wrote for a custom parser (declaration omitted):

boost::optional<bool> string_to_bool_translator::get_value(const std::string &s) {
    auto tmp = boost::to_lower_copy(s);
    if (tmp == "true" || tmp == "1" || tmp == "y" || tmp == "on") {
       return boost::make_optional(true);
    } else if (tmp == "false" || tmp == "0" || tmp == "n" || tmp == "off") {
      return boost::make_optional(false);
    } else {
        return boost::none;
    }
} 

It's only for bool and std::string but easily extendable.

Then,

boost::property_tree::ptree pt;
...
string_to_bool_translator tr;
auto optional_value = pt.get_optional<bool>(key, tr);
sebkraemer
  • 435
  • 3
  • 12
  • Which is pretty much what @f4 suggested in the comments of the earlier answer. +1 for a full example. Note that since you have to specify the translator, it's the sort of thing that's likely to be forgotten by somebody and that might sow confusion. – Arunas Feb 08 '17 at 07:14
  • True. I guess that's what translator_between will ensure, but I overlooked that and didn't try it yet. – sebkraemer Feb 08 '17 at 14:35