Streaming json parser, written on top of yajl.
The main use case for this parser is very long json documents with known structure, e.g. importing some data from a json representation, or reading a json from network and processing it chunk by chunk.
This is an event-driven parser on steroids - you specify the expected json structure and your callbacks, and they will be called after a whole piece of document is parsed (an object, for example), not just on MapKey
or ArrayEnd
events.
Also you can check the Concepts.
#include <iostream>
#include "sjparser/sjparser.h"
using SJParser::Array;
using SJParser::Member;
using SJParser::Object;
using SJParser::Parser;
using SJParser::SArray;
using SJParser::Value;
using SJParser::Presence;
class TestPrinter {
public:
void writeObject(std::string str, int64_t integer,
std::vector<std::string> array) {
std::cout << str << " " << integer << " [";
const char *delimiter = "";
for (const auto &elt : array) {
std::cout << delimiter << elt;
delimiter = ",";
}
std::cout << "]\n";
}
};
TestPrinter DB;
int main() {
/* Declare parser. It expects an array of objects, where first member is a
* string, second member is an optional integer with default value 0, and
* third member is an array of strings. We don't want to process individual
* elements of the object's thied member, so we will use an array parser that
* stores it's parsing result in an std::vector.
*/
Parser parser{Array{Object{
std::tuple{Member{"string", Value<std::string>{}},
Member{"integer", Value<int64_t>{}, Presence::Optional, 0},
Member{"array", SArray{Value<std::string>{}}}}}}};
/* Callback, will be called once object is parsed.
* It receives a reference to the object parser as an argument, so we use
* parsers type aliases to get the type we need.
*/
auto objectCb = [&](decltype(parser)::ParserType::ParserType &parser) {
// Some external API call
DB.writeObject(
// Rvalue reference to the first object member (std::string)
parser.pop<0>(),
// Lvalue reference to the second object member (int64_t) or default
// value if it is not present
parser.get<1>(),
// Rvalue reference to the third object member
// (std::vector<std::string>)
parser.pop<2>());
// Returning false from the callback with make the parser stop with an error
return true;
};
parser.parser().parser().setFinishCallback(objectCb);
// Parse a piece of json. During parsing object callback will be called.
parser.parse(R"(
[{
"string": "str1",
"integer": 1,
"array": ["1", "2"]
}, {
"string": "str2",
"array": ["3", "4"]
}])");
// Finish parsing
parser.finish();
return 0;
}
For more examples, please see tests.
For building sjparser you will need:
cmake
3.8 or higher;make
;yajl
;- c++ compiler with c++17 support (should work with gcc 8 and clang 7);
SJPARSER_WITH_TESTS
- Build tests if the config is not Debug;SJPARSER_WITH_COVERAGE
- Add coverage target (only in Debug config);SJPARSER_BUILD_SHARED_LIBRARY
- Build shared library even in case of submodule build;
mkdir build
cd build
cmake ../ -DCMAKE_BUILD_TYPE=Release
make
If you want to build tests for the release build you can use this command:
cmake ../ -DCMAKE_BUILD_TYPE=Release -DSJPARSER_WITH_TESTS=On
make install
For the debug build you will need gtest
.
cmake ../ -DCMAKE_BUILD_TYPE=Debug
make test
For the documentation you will need doxygen
.
make documentation
The documentation will be available in documentation/html
.
For the coverage you will need:
gcov
;gcovr
;
cmake ../ -DCMAKE_BUILD_TYPE=Debug -DSJPARSER_WITH_COVERAGE=On
make
make coverage
The coverage report will be available in coverage/report
.
For the static analysis you will need clang-tidy
.
make check
For code formatting you will need clang-format
.
make format
Expected json structure is specified as constructor arguments of SJParser::Parser
and entities parsers.
Membersof SJParser::Object
, SJParser::SCustomObject
, SJParser::SAutoObject
, SJParser::Union
and SJParser::SUnion
are mandatory.
You can pass SJParser::Presence::Optional
to a SJParser::Member
constructor to make the member optional.
Members of `` in a standalone mode with a default value are not mandatory, even empty object would be successfully parsed.
If you call get()
or pop()
on a parser of an entity, that was not present in the parsed object, exception will be thrown.
You can check if member was parsed with method isSet()
.
So, for your mandatory members you can just use get()
or pop()
, and for optional you can do checks with isSet()
first.
For the SJParser::Object
parsers there are two methods for accessing the members parsers:
parser<n>()
- Returns a refecence to the n-th member parser;get<n>()
- If the n-th member stores parsed value (is aSJParser::Value
,SJParser::SAutoObject
,SJParser::SCustomObject
,SJParser::SUnion
orSJParser::SArray
), then a reference to a parsed value will be returned (or, if the entity was not present in the json - an exception will be thrown). Otherwise, a reference to the member parser will be returned;pop<n>()
- If the n-th member stores parsed value (is aSJParser::Value
,SJParser::SAutoObject
,SJPArser::SCustomObject
,SJParser::SUnion
orSJParser::SArray
), then an rvalue reference to a parsed value will be returned (or, if the entity was not present in the json - an exception will be thrown). Otherwise, this method is not defined;