A lightweight and lightning-fast C++ JSON & JSON5 parser designed for high performance and minimal footprint.
- Zero-dependency: Easy to integrate into any C++ project.
- Atomic file saving: Guarantees data integrity by using a temporary file before overwriting.
- Minimal memory overhead: Ideal for resource-constrained environments.
- High-performance parsing: Benchmarked for speed against other leading parsers.
- Simple API: Intuitive and easy to use.
- Supported JSON
- Data types
- Simple examples
- Getting Started
- Building and testing the project
- Data Range
- TODO
- Supported CPP
See more in the specifications document.
TinyJSON is a fully compliant JSON5 parser. JSON5 is a superset of JSON that makes it easier for humans to write and maintain.
- Objects: Keys can be unquoted identifiers (e.g.,
{name: "value"}) or single-quoted. Trailing commas are allowed. - Arrays: Trailing commas are allowed.
- Strings: Can be single-quoted (
'hello'). Can span multiple lines by escaping newlines. Support for additional escapes like\v,\0,\xHH, and\u{HHHHH}. - Numbers: Can be hexadecimal (
0x123). Can have leading or trailing decimal points (.5,5.). Support forNaNandInfinity. Can start with an explicit plus sign (+42). - Comments: Support for single-line (
//) and multi-line (/* ... */) comments. - Whitespace: Support for additional Unicode whitespace characters.
To parse JSON5, you must set the specification flag in parse_options.
#include "TinyJSON.h" #include <iostream> using namespace TinyJSON; int main() { const char* json5_text = R"( { // A comment unquoted: 'single quoted string', hex: 0xFF, array: [1, 2, 3,], // trailing comma nested: { val: .5 // leading decimal } } )"; parse_options options; options.specification = parse_options::json5_1_0_0; options.throw_exception = true; try { auto* tj = TJ::parse(json5_text, options); if (tj) { auto* obj = dynamic_cast<TJValueObject*>(tj); std::cout << "Hex value: " << obj->get_number<int>("hex") << std::endl; delete tj; } } catch (const TJParseException& e) { std::cerr << "Parse error: " << e.what() << std::endl; } return 0; }
| JSON | c++/TinyJSON |
|---|---|
| object | TJValueObject |
| array | TJValueArray |
| string | TJValueString |
| number | TJValueNumber (long long, long double, exponent) |
| true | TJValueBoolean (true) |
| false | TJValueBoolean (false) |
| null | TJValueNull (nullptr) |
We follow Semantic Versioning 2.0.0, semver.org
- MAJOR version when you make incompatible API changes
- MINOR version when you add functionality in a backward compatible manner
- PATCH version when you make backward compatible bug fixes
The version is set in the TinyJSON.h file.
static const short TJ_VERSION_MAJOR = 0; static const short TJ_VERSION_MINOR = 2; static const short TJ_VERSION_PATCH = 10; static const char TJ_VERSION_STRING[] = "0.2.10";
TinyJSON provides a convenient operator[] for object member access and a templated as<T>() method for casting values.
auto* tj = TJ::parse(R"({"a": {"b": 10}})"); if (tj) { // Access nested members directly int value = (*tj)["a"]["b"].as<int>(); // 10 // Handling missing keys (behavior depends on parse_options::throw_exception) // If throw_exception is false (default), it returns a null value if ((*tj)["missing"].is_null()) { // ... } delete tj; }
- Depth: (
max_depth:64) You can set how deep you want to allow the parsing to go. - Throw: (
throw_exception:false) If you want to throw exceptions or simply return null. - Strict: (
strict:false) If true, getter methods (likeget_boolean,get_string, etc.) will throw an exception if the key is not found or the type is incorrect. If false, they will return a default value (e.g., 0, "", false). - Specification: (
specification:parse_options::rfc8259) What specs will the parser be following/enforcing.- rfc4627
- rfc7159
- rfc8259
- json5_1_0_0
- Callback: (
callback_function:std::function<void(message_type, const TJCHAR*, int line, int column)>) Callback function called where there is an error, warning etc. NB: The Callback function is called even if Throw is false.lineandcolumnare 1-based and point to where in the source the message was raised, (the first character is line 1, column 1).'\n','\r\n'and a lone'\r'each count as a single new line, a new line resets the column to 1.- The column counts
TJCHARcode units, so a multi-byte utf-8 character counts as more than one column. A utf-8 BOM is not counted. - If there is no position, (the source is null, the file could not be opened, a key was not found, a value could not be converted etc.), both
lineandcolumnare-1. - If a getter method (
try_get_string,get_*(..), orget<>(..)) is called on a key that does not exist in aTJValueObject, a warning message indicating the missing key (e.g."The key 'missing' was not found!") is passed to the callback function. - trace
- debug
- info
- warning
- error
- fatal
For example ...
TinyJSON::parse_options options = {};
options.throw_exception = true;
options.max_depth = 10;
options.callback_function = [&](TinyJSON::parse_options::message_type message_type, const TJCHAR* exception_message, int line, int column) {
// something, something ...
// line == 2, column == 3 for the example below.
};
try
{
auto blah = TinyJSON::TJ::parse("[\n 0123]", options)
...
}
catch(TinyJSON::TJParseException ex)
{
ex.what(); // Numbers cannot have leading zeros
ex.line(); // 2
ex.column(); // 3
}- Throw: (
throw_exception:false) If you want to throw exceptions or simply return false. - write_formatting: (
write_formatting::indented) The formatting we want to write the file with.- none
- indented
- Byte order mark: (
byte_order_mark:none)- none
- uft8
For example ...
TinyJSON::write_options options = {};
options.throw_exception = true;
options.write_formatting = TinyJSON::formatting::minify;
try
{
...
// get JSON somewhere or create it.
...
if( TinyJSON::TJ::write_file("file.json", *json, options))
{
// all good
}
...
}
catch(TinyJSON::TJParseException ex)
{
ex.what(); // what?
}The parsing exception is TinyJSON::TJParseException and can be made optional in the TinyJSON::parse_options flag.
The exception also has the 1-based line() and column() of where the error happened, (both are -1 if there is no position).
TinyJSON::parse_options options = {};
options.throw_exception = true;
try
{
auto blah = TinyJSON::TJ::parse("[0123]", options)
...
}
catch(TinyJSON::TJParseException ex)
{
ex.what(); // Numbers cannot have leading zeros
ex.line(); // 1
ex.column(); // 2
}The parsing exception is TinyJSON::TJParseException and can be made optional in the TinyJSON::parse_options flag.
TinyJSON::write_options options = {};
options.throw_exception = true;
options.write_formatting = TinyJSON::formatting::minify;
try
{
auto json = TinyJSON::TJ::parse( "{ \"Hello\" : \"World\" }" );
TinyJSON::TJ::write_file("file.json", *json, options)
...
}
catch(TinyJSON::TJWriteException ex)
{
ex.what(); // some write error
}You can simply call the TinyJSON::TJ::is_valid(...) method
if(TinyJSON::TJ::is_valid("[123,456]")) { // do this } else { // do that }
To read a JSON file you simply need to call the static method parse_file, the extention does not have to be .json
auto json = TinyJSON::TJ::parse_file( "myfile.json" ); ... delete json;
This will then return an object that you can inspect as per normal.
To read a JSON string you simply need to call the static method parse
auto json = TinyJSON::TJ::parse( "{ \"Hello\" : \"World\" }" ); ... delete json;
This will then return an object that you can inspect.
... auto value = json->try_get_string("Hello"); // "World" auto no_value = json->try_get_string("Life"); // null ...
You can also use literals
using namespace TinyJSON; ... auto json = "[12,13,14]"_tj; // use it. ... delete json; ...
Or just use the string directly
You can convert the json to 'pretty' indented json
#define TJ_INCLUDE_STD_STRING 1 #include "TinyJSON.h" using namespace TinyJSON; ... // output a pretty JSON std::cout << "[12,13,14]"_tj_indent; ...
Or minify code.
#define TJ_INCLUDE_STD_STRING 1 #include "TinyJSON.h" using namespace TinyJSON; ... // output a pretty JSON std::cout << "[12 , 13 , 14]"_tj_minify; ...
There are 2 ways to find a key, case sensitive or insensitive, (the default is case sensitive).
You can load the json either by parsing or creating it manually.
auto object = new TinyJSON::TJValueObject(); object->set_string("a", "Hello"); object->set_string("b", "World"); object->set_string("c", "Bye"); ... // search for something auto case_sensitive = object->try_get_value("Hello"); auto case_insensitive = object->try_get_value("HelLO", false); // all good as we don't care about case. auto case_insensitive_not_found = object->try_get_value("HelLO"); // = null as the search was case sensitive ... delete object;
You can use has_key to check if an object contains a specific key.
auto json = TinyJSON::TJ::parse(R"({"a": 42})"); auto jobject = dynamic_cast<TinyJSON::TJValueObject*>(json); if (jobject->has_key("a")) { // do something } // Case insensitive check bool exists = jobject->has_key("A", false); // returns true
auto object = new TinyJSON::TJValueObject(); object->set_string("a", "Hello"); object->set_string("b", "World"); object->set_string("c", "Bye"); object->set_boolean("d", true); object->set_float("e", 42.15); auto dump = object->dump(); /* { "a": "Hello", "b": "World", "c": "Bye", "d": true, "e": 42.15 } */
You can also remove a value
auto object = new TinyJSON::TJValueObject(); object->set_string("a", "Hello"); object->set_string("b", "World"); object->set_string("c", "Bye"); object->set_boolean("d", true); object->pop("b"); auto dump = object->dump(); /* { "a": "Hello", "b": "World", "c": "Bye", "d": true } */
To write a JSON string you simply need to call the method write_file on the JSON object returned.
auto json = TinyJSON::TJ::parse( "{ \"Hello\" : \"World\" }" ); if( TinyJSON::TJ::write_file("file.json", *json)) { // all good } else { // something broke } delete json;
Regardless if you are parsing a file or parsing a string, TinyJSON will parse the file for UTF-8 DOM if there is one.
While you can dump a string to (re)write a json string you might want to use it directly in your code.
auto json = TinyJSON::TJ::parse( R"("This is a test \" with a quote")" ); auto actual_string = json->dump_string(); /* This is a test " with a quote */ delete json;
The formatting types are
TinyJSON::formatting::indented TinyJSON::formatting::minify
Each objects are read into TJValue* classes of type TJValueObject.
auto json = TinyJSON::TJ::parse( "{ \"Hello\" : \"World\" }" ); auto value = json->try_get_string("Hello"); // "World" auto no_value = json->try_get_string("Life"); // null delete json;
or
auto json = TinyJSON::TJ::parse( "{ \"Hello\" : \"World\" }" ); auto value = json->get_string("Hello"); // "World" auto no_value = json->get_string("Life"); // "" delete json;
You can dynamically create a new object and then add a named values to it.
auto object = new TinyJSON::TJValueObject(); object->set_number("a", 42); object->set_float("b", 0.005); object->set_null("c"); ... delete json;
Each arrays are read into TJValue* classes of type TJValueArray.
Then each items in the array are also TJValue* of type string, number and so on.
auto json = TinyJSON::TJ::parse( "[ 12, 14, 16 ]" ); auto array_of_values = dynamic_cast<TinyJSON::TJValueArray*>(json); auto number_of_items = array_of_values->get_number_of_items(); // 3 auto value1 = array_of_values->at(0); //14 auto value2 = array_of_values->at(2); //16 auto value3 = array_of_values->at(5); // null delete json;
You can dynamically create a new array and then add string, numbers and so on.
auto json = new TinyJSON::TJValueArray(); json->add_boolean(true); json->add_boolean(false); json->add_number(42); ... delete json;
You can remove an item at a certain position using remove_at(index).
auto json = new TinyJSON::TJValueArray(); json->add_string("Hello"); json->add_string("Tiny"); json->add_string("World"); // Remove "Tiny" at index 1 json->remove_at(1); ... delete json;
Each TJValue* item can be of different type
Assuming the array below with multiple items, you can query the type of each TJValue* and do something accordingly.
auto json = TinyJSON::TJ::parse( "[ ... ]" ); auto array_of_values = dynamic_cast<TinyJSON::TJValueArray*>(json); auto value = array_of_values->at(0); if(value->is_object()) { /**/} if(value->is_array()) { /**/} if(value->is_string()) { /**/} if(value->is_number()) { /**/} if(value->is_true()) { /**/} if(value->is_false()) { /**/} if(value->is_null()) { /**/} delete json;
auto json = TinyJSON::TJ::parse(R"( 1, 2, 3 ])"); for (auto& a : *json) { // do stuff } delete json; ...
NB: You can technically everything...
auto json = TinyJSON::TJ::parse("2.5"); for (auto& a : *json) { // do stuff } ... delete json;
The generic get<...>( ... ) method provides a convenient way to retrieve values of various types directly.
For TJValueObject (key-based):
auto json = TinyJSON::TJ::parse(R"({"a": 42, "b": "hello", "c": true})"); auto obj = dynamic_cast<TJValueObject*>(json); int a = obj->get<int>("a"); // 42 std::string b = obj->get<std::string>("b"); // "hello" bool c = obj->get<bool>("c"); // true
The get_or<...>(key, default_value) method allows you to specify a default value if the key is not found or the type is incorrect.
auto json = TinyJSON::TJ::parse(R"({"a": 42})"); auto obj = dynamic_cast<TJValueObject*>(json); int a = obj->get_or<int>("a", 0); // 42 int b = obj->get_or<int>("b", 100); // 100 (key not found) std::string c = obj->get_or<std::string>("c", "default"); // "default"
For any TJValue* (direct value):
auto val = obj->try_get_value("a"); int a = val->get<int>(); // 42
The generic get also supports std::vector for arrays of numbers or floats:
auto json = TinyJSON::TJ::parse(R"({"ints": [1, 2, 3], "floats": [1.1, 2.2]})"); auto obj = dynamic_cast<TJValueObject*>(json); ```cpp auto ints = obj->get<std::vector<int>>("ints"); auto doubles = obj->get<std::vector<double>>("floats");
Getter methods on TJValue (or through TJValueObject keys) can be used to retrieve specific types. By default, these methods are non-strict and return default values if the type is incorrect. By setting strict: true in parse_options, these methods will throw a TJParseException instead. If a key is not found when calling try_get_string, get_*(..), or get<>(..), a warning callback is triggered with a message indicating the key that was not found (e.g. "The key 'missing' was not found!") if a callback function is registered in parse_options.
- get_number()
- get_float()
- get_string()
- get_boolean()
- get_numbers()
- get_floats()
TinyJSON::parse_options options; options.strict = true; options.throw_exception = true; auto json = TinyJSON::TJ::parse(R"( { "a": true, "b": 123 } )", options ); auto jobject = dynamic_cast<TinyJSON::TJValueObject*>(json); auto valuea = jobject->get_boolean("a"); // returns true try { auto valueb = jobject->get_string("b"); // THROW! "b" is a number, not a string } catch (const TinyJSON::TJParseException& e) { // Handle error }
The generic set<...>( ... ) method provides a convenient way to set values of various types directly.
For TJValueObject (key-based):
TinyJSON::TJValueObject obj; obj.set("a", 42); // set int obj.set("b", "hello"); // set string obj.set("c", true); // set boolean obj.set("d", 3.14); // set double
The generic set also supports std::string and std::vector for arrays of numbers or floats:
TinyJSON::TJValueObject obj; std::string val = "hello"; obj.set("b", val); // set std::string std::vector<int> ints = {1, 2, 3}; obj.set("ints", ints); // set std::vector<int>
You can also use specific set methods:
- set_number(key, value)
- set_float(key, value)
- set_string(key, value)
- set_boolean(key, value)
- set_null(key)
TinyJSON::TJValueObject obj; obj.set_number("a", 42); obj.set_string("b", "hello"); obj.set_boolean("c", true); obj.set_null("d");
TinyJSON is designed to be as simple to integrate as possible.
You can simply copy src/TinyJSON.h and src/TinyJSON.cpp directly into your project.
Clone the repository into your project (e.g., in a third_party/ directory) and add it to your CMakeLists.txt:
add_subdirectory(third_party/TinyJSON) target_link_libraries(your_target PUBLIC TinyJSON)
You can automatically download and build TinyJSON using CMake's FetchContent:
include(FetchContent) FetchContent_Declare( TinyJSON GIT_REPOSITORY https://github.com/FFMG/TinyJSON.git GIT_TAG v0.2.10 # or main ) FetchContent_MakeAvailable(TinyJSON) target_link_libraries(your_target PUBLIC TinyJSON)
Here is the absolute minimum code needed to parse a JSON string and read a value:
#include "TinyJSON.h" #include <iostream> int main() { // 1. Parse a string auto json = TinyJSON::TJ::parse(R"({"name": "TinyJSON", "fast": true})"); // 2. Read values auto obj = dynamic_cast<TinyJSON::TJValueObject*>(json); if (obj) { std::cout << "Library: " << obj->get_string("name") << std::endl; std::cout << "Fast? " << (obj->get_boolean("fast") ? "Yes" : "No") << std::endl; } // 3. Clean up delete json; return 0; }
- Clone this repository
- init the submodules
git submodule --init --recursive
The whole number ranges are +9223372036854775807 and -9223372036854775806
- Make sure that all the code follow our own coding standards.
- [] Update comments
- Finish documentation how to use the code.
- [] Run/Create some benchmark tests, make sure they are up to date.
- Arrays
- Objects
- Values
- More tests!
- Add version number (started at version 0.0.1)
- Copyright
- Add 2 kinds of error handling, either throw or return an error structure.
- [] While we do not care about size, (on paper), it would be nice to implement limits in the code.
- depth
- [] string
- [] numbers
- Automated build
- Run on linux/gcc/g++ or something other than visual studio.
g++ -std=c++11 -Wall -Wextra -Werror -O3 src/tinyJSON.cpp -o a.exe - We need to add copy and move constructors to
TJValueand the derived classes. - [] Add Macro(s) explanation like
TJ_INCLUDE_STD_STRINGfor example. - Add support for JSON5 https://json5.org š https://github.com/json5/json5
The supported versions of c++ are c++ 11 and later.