As promised, in this article we’re going to look at using any
to store a representation of an arbitrary JSON object. In case you didn’t already know, JSON stands for “JavaScript Object Notation” and is easy to parse both for humans and machines (compared to XML, for example). A JSON object is essentially an array of key names followed by a colon followed by the value, separated by commas and surrounded by curly braces. So already we can name our container type for JSON:
using JSON = vector<pair<string,any>>;
Easy! Even at this early stage, we can populate the JSON object (but of course we can’t output it with the stream insertion operator (<<
) as this hasn’t been defined):
int main() {
JSON obj;
obj.emplace_back("a", JSON{});
obj.emplace_back("b", any{});
obj.emplace_back("c", 1.2);
obj.emplace_back("d", "Hi!"s);
cout << obj << '\n'; // this line won't compile at this stage
}
(In case you’re wondering, the parameters to emplace_back()
are forwarded to the constructor for the vector
‘s element type, in this case pair<string,any>
; this makes it slightly more efficient than push_back()
which would take a pair
temporary as its single argument.)
The values used equate to: a) empty JSON object (this is legal as JSON allows nesting to arbitrary depth), b) special JSON type undefined
, c) a double
, and d) a std::string
. If we want to be able to print these out, we must somehow handle them individually. Here are the beginnings of a suitable (global) operator<<
function:
ostream& operator<< (ostream& os, const JSON& json) {
if (json.empty()) {
return os << "{}";
}
auto sep = ""sv;
os << "{ ";
for (const auto& elem : json) {
os << sep;
if (elem.first.find(" ") != string::npos) {
os << '\'' << elem.first << "\': ";
}
else {
os << elem.first << ": ";
}
if (elem.second.type() == typeid(void)) {
os << "undefined";
}
else if (elem.second.type() == typeid(double)) {
os << any_cast<double>(elem.second);
}
else if (elem.second.type() == typeid(string)) {
os << '\'' << any_cast<string>(elem.second) << '\'';
}
else if (elem.second.type() == typeid(JSON)) {
os << any_cast<JSON>(elem.second);
}
sep = ", "sv;
}
return os << " }";
}
This code is not particularly complex as long as you’re familiar with vector
and pair
, and followed the use of comparing typeid(T)
with any
‘s type()
member function:
- Line 2 checks the parameter
json
‘s member functionempty()
, this is fine asjson
is “just” a vector so its member functions can be used in the usual way. This check avoids spaces in the output if the JSON object is empty. - Line 5 sets the variable
sep
which then has the value", "
on exit from the first iteration of the range-for loop, this is the standard trick for outputting separated entities when using range-for. - Line 7 begins the range-for iterating over the
vector
with the current element namedelem
; this element has typepair<string,any>
with fields calledfirst
andsecond
. - Line 9 checks the element key name for any space characters, if there are then this name is outputted with enclosing single quotes. A colon follows in either case.
- Lines 15-26 are chained if-else-if clauses which check the element value type against all the ones we need to handle. The order is unimportant; note the use of
any_cast
in every clause. In production code we would probably want to throw an exception if no match is made; this could be achieved by a final else clause. - Line 29 returns the modified
std::ostream&
in the same way as line 3.
This code is far from polished, but manages to sketch the outline of a method. The output from putting this code together (view full source here) is:
{ a: {}, b: undefined, c: 1.2, d: 'Hi!' }
As an easy exercise you could try to add support for int
s and bool
s, a slightly harder one could be adding support for JSON arrays (which are delimited with square brackets), while a real challenge would be overloading the stream extraction operator (>>
). Hint: for the second of these use vector<any>
and if you get stuck then look here.
That’s all for now, in the next article in this mini-series we’ll look at attempting to replicate this functionality using variant
instead of any
.