Home | Libraries | People | FAQ | More |
In this tutorial we are going to use the incremental json::reader
parser to build another
kind of incremental parser, so we are going to introduce a distinction
between incremental pull parsers and incremental
push parsers. The main difference between them is
the direction of control. With pull parsers, like json::reader
, the user extracts or pulls
one token after another, whereas with push parser the tokens are automatically
pushed to the user via callback functions.
We will use json::reader
to build the push parser, because pull parsers are well-suited to create
other kinds of parser interfaces. The serialization output archives that
we saw in a previous tutorial is another example of a higher-level parser
build on top of pull parsers. This tutorial demonstrates how json::reader
can be used to create a push parser.
A push parser iterates over the JSON input and invokes callback functions for each parsed data item. Each data type has a distinct callback function. The user provides the implemention of these callback functions. The design is a variation of the Builder pattern, and this is how XML SAX parsers work.
First we define the push_parser
class which takes the callback functions as a template parameter.[5]
#include <trial/protocol/json/reader.hpp> template <typename Callbacks> class push_parser { public: push_parser(const json::reader& reader) : reader(reader) {} void parse(); private: Callbacks callbacks; json::reader reader; };
The Callbacks
template
parameter must be a class that implements a member function for each callback
function. The Callbacks
class looks something like this:
#include <cstdint> #include <string> class my_callbacks { public: void on_null(); void on_boolean(bool); void on_integer(std::intmax_t); void on_number(double); void on_string(const std::string&); void on_begin_array(); void on_end_array(); void on_begin_object(); void on_end_object(); };
We are not going to implement my_callbacks
here, although a simple implementation could be to simply print the type
and value in each callback function.
After these preliminary definitions, we have now arrived at the crux of
the problem: how to implement the push_parser::parse()
function. Fortunately that is very simple
using a pull parser:
json::reader::next()
.
json::reader::symbol()
.
json::reader::value<T>()
.
Here is the entire implementation in its full glory:
void push_parser::parse() { do { switch (reader.symbol()) { case json::symbol::null: callbacks.on_null(); break; case json::symbol::boolean: callbacks.on_boolean(reader.value<bool>()); break; case json::symbol::integer: callbacks.on_integer(reader.value<std::intmax_t>()); break; case json::symbol::number: callbacks.on_number(reader.value<double>()); break; case json::symbol::string: callbacks.on_string(reader.value<std::string>()); break; case json::symbol::begin_array: callbacks.on_begin_array(); break; case json::symbol::end_array: callbacks.on_end_array(); break; case json::symbol::begin_object: callbacks.on_begin_object(); break; case json::symbol::end_object: callbacks.on_end_object(); break; default: break; } } while (reader.next()); }
Finally, we use the above push parser as follows:
json::reader reader("[null,true,42]"); // Replace with actual JSON input push_parser<my_callbacks> parser(reader); parser.parse();