A lightweight, flexible, header-only JSON parser and generator library for C++17.
The jstream library provides an event-based streaming JSON parser and a simple interface for generating JSON data. It aims to be a flexible and easy-to-use library for working with JSON in C++ applications.
- Event-based parsing: Parse JSON data as a stream of events
- Path-based access: Access JSON elements using path expressions
- Flexible configuration: Supports comments, unquoted keys, trailing commas, hexadecimal numbers, and special constants
- Locale-independent: Number parsing is not affected by locale settings
- Unicode support: Handles Unicode characters and surrogate pairs in strings
- Variant-based data model: Uses
std::variantfor type-safe JSON representation - Header-only: Just include
include/jstream.h
Just include the header file in your project:
#include "jstream.h"#include "jstream.h"
#include <iostream>
int main() {
const char* json = R"({
"name": "John Doe",
"age": 30,
"cities": ["New York", "London", "Tokyo"]
})";
// Parse JSON
jstream::Reader reader(json);
// Enable optional features if needed
reader.allow_comment(true);
reader.allow_unquoted_key(true);
// Iterate through JSON events
while (reader.next()) {
if (reader.match("{name") && reader.isstring()) {
std::cout << "Name: " << reader.string() << std::endl;
} else if (reader.match("{age") && reader.isnumber()) {
std::cout << "Age: " << reader.number() << std::endl;
} else if (reader.match("{cities[*") && reader.isstring()) {
std::cout << "City: " << reader.string() << std::endl;
}
}
return 0;
}Reader can parse JSON that arrives incrementally. Create a reader with an input callback, call input() to append chunks, and check is_not_enough_input() when parsing pauses mid-token.
#include "jstream.h"
#include <iostream>
int main() {
std::string chunk1 = R"({"name": "Jo")";
std::string chunk2 = R"(hn", "age": 30})";
jstream::Reader reader;
reader.input(chunk1);
reader.input(chunk2);
while (reader.next()) {
if (reader.match("{name") && reader.isstring()) {
std::cout << "Name: " << reader.string() << std::endl;
} else if (reader.match("{age") && reader.isnumber()) {
std::cout << "Age: " << reader.number() << std::endl;
}
}
}For callback-driven streaming, set up the callback with parse():
jstream::Reader reader;
reader.parse([&](){ reader.input(get_next_chunk()); });
while (reader.next()) { /* ... */ }When a stream contains multiple top-level JSON documents, parse the first document, call next_document() to clear the EndDocument state, and continue parsing.
#include "jstream.h"
#include <iostream>
int main() {
// Create a JSON writer that outputs to stdout
jstream::Writer writer([](const char* p, int n) {
std::cout.write(p, n);
});
// Configure formatting if needed
writer.enable_indent(true);
writer.enable_newline(true);
// Generate JSON
writer.object({}, [&]() {
writer.string("name", "John Doe");
writer.number("age", 30);
writer.array("cities", [&]() {
writer.string("New York");
writer.string("London");
writer.string("Tokyo");
});
writer.boolean("active", true);
writer.null("optionalField");
});
return 0;
}You can also collect the output in a std::string:
jstream::Writer writer; // no callback
writer.string("hello", "world");
std::string json = writer; // implicit std::string conversionOr insert raw JSON text:
writer.raw("metadata", "{\"source\":\"api\"}");#include "jstream.h"
#include <iostream>
int main() {
// Create a JSON object
jstream::Variant root;
auto obj = jstream::obj(root);
// Add properties
obj["name"] = "John Doe";
obj["age"] = 30.0;
// Add an array
auto& cities = jstream::arr(obj["cities"]);
cities.push_back("New York");
cities.push_back("London");
cities.push_back("Tokyo");
// Access values
if (jstream::is_string(obj.value("name"))) {
std::cout << "Name: " << jstream::get<std::string>(obj.value("name")) << std::endl;
}
return 0;
}The parser records detailed error information including the message, byte offset, line, and column:
jstream::Reader reader(json);
while (reader.next()) { }
if (reader.has_error()) {
for (auto const& e : reader.errors()) {
std::cout << e.what() << " at line " << e.line
<< ", column " << e.column << std::endl;
}
}The Reader class supports the following configuration options:
allow_comment(bool): Allow C and C++ style comments in JSONallow_ambiguous_comma(bool): Allow trailing commas in arrays and objectsallow_unquoted_key(bool): Allow unquoted object keysallow_hexadecimal(bool): Allow hexadecimal number format (0xNNNand-0xNNN)allow_special_constant(bool): Allow special constants likeInfinityandNaNallow_key_in_array(bool): Allow"key":"value"syntax inside arrays
Path expressions provide a concise way to match JSON locations:
{key}- Match an object key[*]- Match any array element (constant values){*}- Match any object key (constant values)*[- Match any array start*{- Match any object start**- Match any nested path (must be at the end)@...- Relative path from the currentnest()baseline
Examples:
reader.match("{user{name"); // user.name
reader.match("{items[*{price"); // items[].price
reader.match("{items[*{price"); // price inside any object in items
reader.match_start_object("{items[*{"); // any object inside items array
reader.nest([&](){ // enter nested scope
if (reader.match("@{value")) { // match "value" relative to nest baseline
// ...
}
});Useful predicates and accessors on Reader:
state()- Current state (StateType)is_constant(),is_structure(),is_value()- Classify the current stateis_start_object(),is_end_object(),is_start_array(),is_end_array(),is_end_document()isnull(),isboolean(),isnumber(),isstring()is_not_enough_input()- True when parsing paused because the buffer ended mid-token (streaming mode)key(),string(),number(),boolean()path(),depth(),tell()extract()- Raw text of the last parsed element
A qmake project file is provided for convenience:
qmake jstream.pro
makeThe unit tests use Google Test:
cd test-cpp
make
./myapp- C#:
jstream-cs/directory. SeeREADME_CSharp.md. - Go:
jstream-go/directory. SeeREADME_Go.md. - 日本語:
README_ja.md
This software is distributed under the MIT license.