This C++ JSON parser library provides functionality to parse and manipulate JSON data. It allows you to read JSON from strings or files, modify the data, and serialize it back to strings or files. The library also provides comprehensive support for various C++ types including custom types, standard containers, and complex types.
The library supports parsing and serializing JSON objects, arrays, strings, numbers, booleans, and null values.
- Parse JSON data from a string or a file.
- Extract data into C++ types such as
std::string,int,bool,std::complex, and more. - Serialize C++ types back into JSON format.
- Supports custom types via operator overloads for
<<and>>. - Handles both arrays and objects, and supports serialization of complex types such as
std::tuple,std::vector, andstd::map. - Accepts a JSON5-style superset of JSON, see the "Accepted syntax" section below.
- Error handling with detailed exception types such as
parser_error,type_error, andrange_error.
The library needs a C++11 compiler and CMake 3.14 or newer. It is made of one header and one source file, so it must be compiled and linked, it is not header-only.
include(FetchContent)
FetchContent_Declare(json
GIT_REPOSITORY https://github.com/Galfurian/json.git
GIT_TAG v3.0.0
)
FetchContent_MakeAvailable(json)
target_link_libraries(my_target PRIVATE json::json)When the library is consumed this way, its tests, examples and documentation are not built, and its warning flags are not applied to your code.
json::json also brings the include path of ordered_map, a header-only dependency fetched at a pinned commit.
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build
cmake --install buildfind_package(json 3 REQUIRED)
target_link_libraries(my_target PRIVATE json::json)The package also installs the ordered_map headers that json.hpp includes.
Clone the repository, add include/ and the include/ directory of ordered_map to your include path, and compile src/json.cpp together with your sources.
git clone https://github.com/Galfurian/json.gitThen include the header:
#include <json/json.hpp>#include <json/json.hpp>
#include <iostream>
int main() {
const char* json_string = R"(
{
// Single-line comment
"name": "John",
/* Multi-line comment */
"age": 30,
"city": "New York" // Inline comment
})";
json::jnode_t root = json::parser::parse(json_string);
std::string name = root["name"].as_string();
int age = root["age"].as_number<int>();
std::string city = root["city"].as_string();
std::cout << "Name: " << name << "\n";
std::cout << "Age: " << age << "\n";
std::cout << "City: " << city << "\n";
return 0;
}#include <json/json.hpp>
#include <iostream>
int main() {
json::jnode_t root = json::parser::parse_file("data.json");
std::string name = root["name"].as_string();
int age = root["age"].as_number<int>();
std::string city = root["city"].as_string();
std::cout << "Name: " << name << "\n";
std::cout << "Age: " << age << "\n";
std::cout << "City: " << city << "\n";
return 0;
}You can serialize standard C++ types and custom types to JSON using the << operator.
#include <json/json.hpp>
#include <iostream>
struct Person {
std::string name;
int age;
Person(std::string name, int age) : name(name), age(age) {}
friend json::jnode_t& operator<<(json::jnode_t& lhs, const Person& rhs) {
lhs.set_type(json::JTYPE_OBJECT);
lhs["name"] << rhs.name;
lhs["age"] << rhs.age;
return lhs;
}
};
int main() {
Person p("Alice", 30);
json::jnode_t node;
node << p;
std::cout << node.to_string() << std::endl;
return 0;
}This example demonstrates how to read a JSON file, parse its contents, and then write the modified JSON back to a file.
#include <json/json.hpp>
#include <iostream>
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <path-to-json-file>\n";
return 1;
}
std::string filename(argv[1]);
// Directly parsing the file using the parse function
json::jnode_t root = json::parser::parse_file(filename);
// Modify or inspect the JSON data
std::cout << "The JSON data read from the file:\n";
std::cout << root.to_string(true, 2) << "\n";
// Writing the modified JSON data to a new file
json::parser::write_file("out-" + filename, root, false);
std::cout << "Modified JSON written to out-" << filename << "\n";
return 0;
}While the library allows more granular control through tokenization and manual parsing (as shown in the next code), most users will find it more straightforward to use the parse function directly for simplicity and efficiency.
#include <json/json.hpp>
#include <iostream>
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <path-to-json-file>\n";
return 1;
}
std::string filename(argv[1]), content;
// Read the file.
if (!json::parser::read_file(filename, content)) {
std::cerr << "Failed to open file `" << filename << "`\n";
return 1;
}
// Extract tokens.
std::vector<json::detail::token_t> tokens;
json::detail::tokenize(content, tokens);
// Parse tokens
json::jnode_t root;
std::size_t k = 0;
json::detail::json_parse(tokens, 0UL, k, root);
// Write the file
json::parser::write_file("out-" + filename, root, false);
return 0;
}This library supports the following types for serialization and deserialization:
std::stringboolint,unsigned,long,long longfloat,doublestd::complexstd::tuple,std::vector,std::list,std::set,std::map,std::unordered_map- Custom types (via operator overloads)
benchmarks/bench_parse.cpp parses and prints a generated 8.7 MB document (50000 records), best of 5 runs, GCC 13 -O2 (Release):
| Library | Parse | |
|---|---|---|
| this library | ~24 MB/s | ~127 MB/s |
| nlohmann/json 3.11.3 | ~60 MB/s | ~314 MB/s |
The parser first builds a token list and then the tree, so it needs memory proportional to the number of tokens (about 48 bytes each) on top of the tree. Keep that in mind for documents of hundreds of megabytes, and use max_input_size.
Run it with -DBUILD_BENCHMARKS=ON (add -DBENCHMARK_NLOHMANN=ON for the comparison).
The parser accepts standard JSON plus these JSON5 conveniences:
//and/* ... */comments- single-quoted strings
- unquoted object keys (identifiers made of letters, digits,
_and$) - trailing commas in objects and arrays
- numbers with a leading
+, a leading or trailing dot (.5,5.), in hexadecimal (0xFF), and the special valuesInfinity,-InfinityandNaN - line continuations: a backslash before a line break inside a string removes the line break
Everything else that is not valid JSON is rejected with a parser_error: invalid numbers (01, -, 1.2.3, 0x), bare words used as values, missing commas, unterminated strings and comments, invalid escape sequences, and any data after the root value.
Strings may contain \uXXXX escapes, including surrogate pairs. They are decoded to UTF-8.
Global options live in the json::config namespace:
| Option | Default | Meaning |
|---|---|---|
max_depth |
256 |
Maximum nesting of objects and arrays. Deeper documents raise a parser_error. |
max_input_size |
0 |
Maximum size in bytes of a document. 0 means no limit. |
strict_type_check |
false |
Throw type_error instead of returning a default when a value has a different type. Also reports numbers that do not fit the requested C++ type. |
strict_existance_check |
false |
Throw instead of returning a null node when a key does not exist. |
replace_escape_characters |
false |
Escape special characters when serializing strings. |
string_delimiter_character |
' |
Quote used when serializing strings. |
max_depth and max_input_size can also be passed for a single call, without touching global state:
json::parse_options options;
options.max_depth = 32;
options.max_input_size = 1 << 20; // 1 MiB
json::jnode_t root = json::parser::parse(text, options);
json::jnode_t file = json::parser::parse_file("data.json", options);parse_file throws parser_error if the file cannot be opened.
The other options are global variables: set them once at startup, before using the library from several threads. Parsing and reading a tree that is not modified are otherwise safe to do from different threads on different objects.
When you parse untrusted input, keep max_depth low and limit the size of the input you hand to the parser.
The library uses exceptions to handle errors:
parser_error: General parsing error. It carries thelinewhere the problem was found.type_error: Type mismatch error, derived fromparser_error.range_error: Accessing an out-of-bound element, derived fromparser_error.
On malformed input the parser either returns a tree or throws parser_error. This is checked by the test suite, which includes a seeded mutation fuzz test, and by a CI job that runs all the tests under AddressSanitizer and UndefinedBehaviorSanitizer.
This project is licensed under the MIT License - see the LICENSE.md file for details.
Contributions are welcome! Please feel free to open issues and pull requests.