I have a problem similar to that described here: C++ Mutually Recursive Variant Type
I am trying to create a JSON representation in C++. Many libraries already offer excellent JSON representations and parsers that are very fast, but I am not reinventing this wheel. I need to create a C++ JSON representation that supports certain space optimizations under specific conditions. In short, if and only if a JSON array contains homogenous data, rather than storing every element as bloated variant types, I need compact storage of native types. I also need to support heterogeneous arrays and standard nested JSON objects.
The following is the "if wishes were horses, beggars would ride" version of the code, which is meant to clearly illustrate intent, but is obviously broken because types are used before any declaration exists. I want to avoid specifying the same information multiple times in types (i.e. Array, Object, and Value should not require duplicated type specifications). I also want to avoid any unnecessarily high run-time costs.
#include <string>
#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
class JSONDocument {
public:
using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Null = void *;
using Key = std::string;
using Path = std::string;
using Value = boost::variant<
Null,
String,
Integer,
Float,
Boolean,
Object,
Array
>;
using Object = std::unordered_map<Key,Value>;
using Array = boost::variant<
std::vector<Null>,
std::vector<String>,
std::vector<Integer>,
std::vector<Float>,
std::vector<Boolean>,
std::vector<Value> >;
private:
Value root;
class value_traversal_visitor : public boost::static_visitor<Value> {
public:
value_traversal_visitor( Path path ) : path(path) {}
Value operator()( Null x ) const {
if( path.empty() ) {
return x;
}
// otherwise throw ...
}
Value operator()( String x ) const {
if( path.empty() ) {
return x;
}
}
...
// special handling for Array and Object types
private:
Path path;
};
public:
Value get( Path path ) {
return boost::apply_visitor( value_traversal_visitor( path ), root );
}
...
};
As you can see, I am including the recursive_wrapper
header. I have tried various invocations of boost::make_recursive_variant and boost::recursive_wrapper, but I always get compiler errors. I do not see how the answer from C++ Mutually Recursive Variant Type solves this, because in every attempt, I get compiler errors (from both gcc++ 5.3 and LLVM/clang++ 3.8) that almost exclusively reference Boost that essentially boil down to types not being convertible or declarations either conflicting or not existing. I would put one of my attempts along with specific compiler error messages here, but I wouldn't know which of the many attempts to use.
I'm hoping somebody can set me on the right path...
Thanks in advance!
Just to build on the accepted answer below, here is an example of a working skeleton for the types and their usages.
#include <string>
#include <unordered_map>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Key = std::string;
using Value = boost::make_recursive_variant<
String,
Integer,
Float,
Boolean,
std::unordered_map<Key, boost::recursive_variant_>,
boost::variant<std::vector<String>,std::vector<Integer>,std::vector<Float>,std::vector<Boolean>,std::vector<boost::recursive_variant_> >
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<std::vector<String>,std::vector<Integer>,std::vector<Float>,std::vector<Boolean>,std::vector<Value> >;
int main( int argc, char* argv[] ) {
Value v;
v = static_cast<Integer>( 7 );
Object o;
v = o;
Array a = std::vector<Integer>( 3 );
v = a;
return 0;
}
You could just use recursive_variant_
placeholder with make_recursive_variant
.
Here's the gist:
using Value = boost::make_recursive_variant<
Null,
String,
Integer,
Float,
Boolean,
std::unordered_map<Key, boost::recursive_variant_>, // Object
std::vector<boost::recursive_variant_> // Array
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<Value>;
Live On Coliru
As you can see there's unimplemented bits in the code (never write functions missing return statements!). Also note the simplifications in control flow for get
and the private visitor implementation.
#include <boost/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/variant/variant.hpp>
#include <string>
#include <unordered_map>
#include <vector>
class JSONDocument {
public:
struct Null { constexpr bool operator==(Null) const { return true; } };
using String = std::string;
using Integer = long;
using Float = double;
using Boolean = bool;
using Key = std::string;
using Path = std::string;
using Value = boost::make_recursive_variant<
Null,
String,
Integer,
Float,
Boolean,
std::unordered_map<Key, boost::recursive_variant_>, // Object
std::vector<boost::recursive_variant_> // Array
>::type;
using Object = std::unordered_map<Key, Value>;
using Array = boost::variant<Value>;
private:
Value root;
struct value_traversal_visitor {
Path path;
using result_type = Value;
result_type operator()(Value const &x) const {
if (path.empty()) {
return x;
}
return boost::apply_visitor(*this, x);
}
result_type operator()(Null) const { throw std::invalid_argument("null not addressable"); }
result_type operator()(String const &) const { throw std::invalid_argument("string not addressable"); }
// special handling for Array and Object types TODO
template <typename T> result_type operator()(T &&) const { return Null{}; }
};
public:
Value get(Path path) { return value_traversal_visitor{path}(root); }
};
int main() {}
void*
for Null because all manner of unwanted implicit conversionsNote that you should probably not use unordered_map
because
See also https://github.com/sehe/spirit-v2-json/blob/master/json.hpp#L37
Not a solution per se, but Here's a way to achieve variant recursivity using std::variant. I thought this might be of interest, since the stl doesn't provide any api for recursive, nor forward-declared types. Compiles using gcc 7.2 -std=c++17
#include <variant>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
struct Nil {};
struct vector1;
using var_t1 = variant<Nil, int, vector1>;
using var_t2 = variant<Nil, double, float, int, var_t1>;
struct vector1 {
vector<var_t2> v_;
};
struct print_var_t2;
struct print_var_t1 {
void operator()(const vector1& v);
void operator()(int) { cout << "int\n"; }
void operator()(const Nil&) { cout << "nil\n"; }
};
struct print_var_t2 {
void operator()(const Nil&) { cout << "Nil\n"; }
void operator()(int) { cout << "int\n"; }
void operator()(double) { cout << "double\n"; }
void operator()(float) { cout << "float\n"; }
void operator()(const var_t1& v);
};
void print_var_t1::operator()(const vector1& v) {
for_each(v.v_.begin(), v.v_.end(), [](const var_t2& x)
{
visit(print_var_t2{}, x);
});
}
void print_var_t2::operator()(const var_t1& v) {
visit(print_var_t1{}, v);
}
int main()
{
vector1 v1;
v1.v_.push_back(.1);
v1.v_.push_back(2.f);
v1.v_.push_back(3);
v1.v_.push_back(var_t2{3});
var_t1 var1 = v1;
std::visit(print_var_t1{}, var1);
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With