As a personal challenge I want to implement a C++ JSON Parser. As part of that I have implemented the following Types/Data-structures and a way of querying them.
To represent the type of an individual JSON attribute I defined the following std::variant and aliased it as JSONType. My thought behind using an std::variant is that when dealing with a JSON attribute I know that it must be one of the following types, but I can't exactly know which type before parsing the attribute.
Right now I am not concerned about "null" and "arrays with different types".
using JSONType = std::variant
<
bool,
int,
float,
double,
std::string,
std::vector<bool>,
std::vector<int>,
std::vector<float>,
std::vector<double>,
std::vector<std::string>
>;
To represent a JSON object I defined the struct JSONObject. My reasoning behind the attributes member is that for every JSON attribute I have a string as it's key and the value is either a single JSONType (bool, int, ...) or another JSONObject that recursively repeats this structure.
The query function "getIf(keys)" expects a template type T, which is the type of data that the user expects to get out of the query. keys is a sequence of strings , where the first n-1 strings describe the path of nested JSONObjects down the tree to which the attribute resides that we want to return. So the nth string is the name of that attribute.
struct JSONObject
{
std::unordered_map<std::string, std::variant<JSONType, JSONObject>> attributes;
template <class T>
T* getIf(std::vector<std::string> const& keys)
{
JSONObject* temp = this;
// Go to JSONObject where last keys attribute resides.
for (int i = 0; i < (keys.size() - 1); ++i)
{
temp = &std::get<JSONObject>(temp->attributes[keys[i]]);
}
// Find the attribute that we actually want to return,
// which is the attribute that is pointed to by
// the last given key.
JSONType& variant = std::get<JSONType>(temp->attributes[keys[keys.size() - 1]]);
// Check if the given template type T is the same type
// that the attribute that we want to return has.
if (auto* value = std::get_if<T>(&variant))
{
return value;
}
else
{
return nullptr;
}
}
};
The following is an example instatiation and query of a JSONObject that represents the following json file and should result in a tree-like structure like the diagram shows.
JSONObject o
{ // Initialization brackets
{ // unordered_map brackets
{ "boolean", std::variant<JSONType, JSONObject>(true) }, // map entry brackets
{ "nested_object", std::variant<JSONType, JSONObject>(JSONObject
{
{
{ "float", std::variant<JSONType, JSONObject>(3.14123f)},
{ "nested_object_2", std::variant<JSONType, JSONObject>(JSONObject
{
{
{ "string", std::variant<JSONType, JSONObject>(std::string("Hello World"))}
}
}
)},
{ "numbers", std::variant<JSONType, JSONObject>(std::vector<int>{1, 2, 3, 4, 5}) }
}
}
)}
}
};
bool boolean = *o.getIf<bool>({ "boolean" });
float flo = *o.getIf<float>({ "nested_object", "float" });
std::string string = *o.getIf<std::string>({ "nested_object", "nested_object_2", "string" });
std::vector<int> numbers = *o.getIf<std::vector<int>>({ "nested_object", "numbers" });
{
"boolean": true,
"nested_object":
{
"float": 3.14123f,
"nested_object_2":
{
"string": "Hello World"
},
"numbers": [1, 2, 3, 4, 5]
}
}
I am interested in the quality of this solution and alternative solutions. Thanks !