1

I'm trying to access missing values in boost json ptree. When the key was not matched, a method should be called, in which I can define the returned value. For calculation of returned value I need to access the ptree. Example: the ptree is: { "1": 10, "3": 30 } I would like to interpolate the returned value, i.e. when I query for "2" i want 20 to be returned. Is it possible?

olexij
  • 11
  • 1
  • If you use your own accessor, you can do anything you want. Or do you want boost to somehow magically do this automatically? – Joseph Larson Feb 12 '21 at 20:01
  • Yes, that was my question. I didn't find any documentation or examples for custom accesors. Any hints? – olexij Feb 12 '21 at 22:38

1 Answers1

0

Everything is possible in a Turing-complete programming language (hint: that's all of them).

On actual property trees you could use get_optional and then value_or_eval:

Live On Wandbox/Compiler Explorer

#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <boost/property_tree/json_parser.hpp>
#include <iostream>

int main() {
    boost::property_tree::ptree pt;
    pt.add("1", 10);
    pt.add("3", 30);

    for (auto probe : { "1", "2", "3" }) {
        auto lookup = pt.get_optional<double>(probe).value_or_eval([&pt] {
            write_json(std::cout << "(interpolate from ", pt);
            std::cout << ")\n";
            return 42;
        });

        std::cout << "probe " << probe << " -> " << lookup << "\n";
    }
}

Printing

probe 1 -> 10
(interpolate from {
    "1": "10",
    "3": "30"
}
)
probe 2 -> 42
probe 3 -> 30

The problem with this is that it's neither JSON nor strongly typed.

Alternatives

Here's a random take (literally) and it uses a Json library as opposed to Property Tree:

Live On Compiler Explorer

#include <boost/json.hpp>
#include <boost/json/src.hpp> // header-only
#include <boost/lexical_cast.hpp>
#include <map>
#include <iostream>
#include <iomanip>
#include <random>
using boost::conversion::try_lexical_convert;
namespace json = boost::json;

template <typename Key = double, typename Value = double>
struct MyLookup {
    MyLookup(std::string const& text) : data(parse(text)) 
    { }

    using Table = std::map<Key, Value>;
    static Table parse(std::string const& text) {
        auto raw = json::value_to<std::map<std::string, Value>>(
            json::parse(text));

        Table converted;
        auto key_convert = [](auto &raw_pair) -> std::pair<Key, Value> {
            return {boost::lexical_cast<Key>(raw_pair.first),
                    raw_pair.second};
        };
        std::transform(raw.begin(), raw.end(), inserter(converted, converted.end()), key_convert);
        return converted;
    }

    Value operator[](Key key) const {
        auto [low,up] = data.equal_range(key);
        if (low == data.end())
            throw std::range_error("out of bounds");

        if (up == std::next(low)) {
            return low->second;
        } else {
            if (low == data.begin() || up == data.end())
                throw std::range_error("out of bounds");

            low = std::prev(low);
            auto dx    = up->first  - low->first;
            auto dy    = up->second - low->second;
            auto slope = dy/dx;

            return low->second + (key - (low->first)) * slope;
        }
    }

    auto begin() const { return data.begin(); }
    auto end()   const { return data.end();   }

private:
    Table data;
};

int main() {
    MyLookup data(R"({ "1": 10, "3": 30 })");

    for (auto [k,v] : data) {
        std::cout << "table: " << k << " -> " << v << "\n";
    }

    std::mt19937 prng;
    std::uniform_real_distribution<double> probe(0,5);
    for (int i = 10; --i;) {
        auto key = probe(prng);
        std::cout << "Linear interpolation: " << std::setw(7) << key << " -> ";
        try {
            std::cout << data[key] << '\n';
        } catch (std::exception const &e) {
            std::cout << e.what() << '\n';
        }
    }
}

Prints e.g.

table: 1 -> 10
table: 3 -> 30
Linear interpolation: 0.677385 -> out of bounds
Linear interpolation: 4.17504 -> out of bounds
Linear interpolation: 4.84434 -> out of bounds
Linear interpolation: 1.10517 -> 11.0517
Linear interpolation: 1.54084 -> 15.4084
Linear interpolation:  2.7361 -> 27.361
Linear interpolation: 0.94191 -> out of bounds
Linear interpolation: 4.96441 -> out of bounds
Linear interpolation: 4.98231 -> out of bounds
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added both a property tree and Boost JSON example. – sehe Feb 12 '21 at 22:00
  • Thank you for explanation! I wanted to know, how to hide this kind of calculation inside of ptree, with a translator or accessor while avoiding to share this it to outside. There are not much examples besides translator for converting strings to numbers. I'll take a closer look at boost JSON. – olexij Feb 12 '21 at 22:46
  • You can't do it with a translator, because there's nothing to translate. However, I did show two examples of how to hide lookup misses. – sehe Feb 12 '21 at 23:03
  • Thanks for help @sehe! Summarizing - currently there is no solution for me, as: 1. : I am limited in the boost version, which does not provide . 2. : the proposition of 42 is not a custom accessor, for which I was asking. I think about encapsulating ptree. – olexij Feb 14 '21 at 06:58
  • Obviously "42" was just an example, you could replace it with a function call just like `operator{]` in the other, more serious, example. As far as writing answer code goes, I already wrote 10x more than is common/required. Yes, encapsulation is obviously required. – sehe Feb 14 '21 at 13:44
  • Aside, advice from one professional to another: "I'm limited to XYZ" is rarely true. It's just politics: if "they" want you to write could dealing with JSON, they want you to use a proper library, full stop (so, not property tree). Also, I notice that for some reason the "JSON" is being conflated with "data interpolation". It's probably much healthier to separate concerns here to begin with (like I did in the Boost JSON example) – sehe Feb 14 '21 at 13:46