Correctness, speed, memory, and binary-size measurements for JESON, compared with established C and C++ JSON libraries. The project asks an embedded question: how much correctness and convenience can fit into limited flash and RAM? This is a JESON-maintained fork of Milo Yip's nativejson-benchmark.
Copyright(c) 2014-2016 Milo Yip (miloyip@gmail.com)
This fork was created to develop JESON for constrained targets—ESP32, Nordic nRF/Zephyr, and similar devices—against repeatable evidence rather than isolated microbenchmarks. Its focused build compares JESON with cJSON, reflect-cpp, RapidJSON, nlohmann/json, and ArduinoJson using the same documents, operations, allocation tracker, conformance suite, and code-size harness.
Correctness comes first: a fast parser that changes a number, accepts malformed input, or cannot round-trip a document is not a useful winner. Performance is then measured as parse/stringify/pretty-print time, memory, allocation count, and executable size.
The original all-library benchmark remains available below. The root CMake build and the current charts are the maintained JESON comparison lane.
JESON is primarily a typed embedded JSON library, not a DOM library. It is designed for firmware that needs a pleasant interface but cannot casually spend flash and RAM on layers of generic infrastructure. It parses straight into the destination aggregate, so normal application code does not need to build a generic tree and then copy/map it into a model.
The benchmark contains three JESON rows because it measures three distinct use cases—not three performance settings for one API:
| Benchmark label | C++ API | Use it when |
|---|---|---|
| JESON typed | Field, structs, parse(json, model) |
The schema is known. JSON is parsed directly into the model without a DOM. |
| JESON Mutable DOM | jeson::Value |
The schema is unknown and the generic JSON tree must be edited. |
| JESON Compact DOM | jeson::CompactDocument |
The schema is unknown and the document must be inspected or selectively reserialized, but not edited. |
The compact-DOM optimization is schema-less only. It did not change the typed parser; the typed benchmark shows the already-existing direct-to-model path rather than a 0.1-to-0.2 speedup. For a known device configuration, typed JESON remains the intended and lowest-RAM JESON API in this run.
The Compact DOM has a deliberately narrow role: gateways, protocol bridges, and diagnostics that must traverse a larger unknown-schema document. It is not the recommended API for known device messages, editable JSON, or blind pass-through. Those cases should use typed JESON, the Mutable DOM, or the original input bytes without parsing. Its fixed page cost can also make it use more memory than the Mutable DOM for a tiny document—the large-DOM result below does not mean it wins every payload size.
enum class Mode { SLEEP, NORMAL, BOOST };
struct Radio {
jeson::Field<"channel", jeson::Validator<unsigned,
jeson::Minimum<11>, jeson::Maximum<26>>> channel = 15;
};
struct DeviceConfig {
jeson::Field<"device_id", jeson::Required<std::string>> deviceId;
jeson::Field<"radio", Radio> radio;
jeson::Field<"mode", Mode> mode = Mode::NORMAL;
jeson::Field<"sample_interval_ms", jeson::Validator<unsigned,
jeson::Minimum<100>, jeson::Maximum<60000>>> sampleInterval = 1000;
jeson::Field<"labels", std::vector<std::string>> labels;
};
DeviceConfig config;
auto result = jeson::parse(payload, config); // JSON directly into the model
if (result) {
config.sampleInterval = 5000; // ordinary member access
auto json = jeson::toJson(config); // same model writes JSON
}Field<"wire_name", T>is the only mapping declaration. The JSON name and C++ type cannot drift away in a separate registration table.- Fields support ordinary assignment, comparison, and nested member access; wrapper plumbing does not leak into normal application code.
- C++ initializers are JSON defaults.
Required<T>makes key presence explicit, whileValidator<T, ...>composes Pydantic-style range and size rules. - Structs, enums,
std::optional, andstd::vectorcompose naturally, and the same aggregate is serialized without a second conversion function. - Parsing returns accumulated, path-aware errors. Unknown keys can be ignored so old and new firmware revisions can exchange configuration safely.
- A
Patch<T>model supports partial configuration or command updates without weakening the requirements of the full model.
Its main strengths are:
- Typed by default: direct JSON-to-aggregate parsing without a required DOM or separate mapping layer.
- Correct: both schema-less JESON DOMs pass all 136 checks in this benchmark, including syntax, double conversion, Unicode strings, and round trips.
- Small: the normal stripped compact-DOM executable is 68.1 KiB. A
reproducible embedded-size profile (
-Oz, ThinLTO, no exceptions/RTTI) is 51.4 KiB, meeting the optimistic sub-52 KiB target on this toolchain. - Fast output: JESON has the lowest combined stringify and pretty-print time in the checked three-document run.
- Low generic parse RAM: the Compact DOM (
CompactDocument) lowers the summed parse peaks from 9.4 MiB for the Mutable DOM (Value) to 5.6 MiB and cuts allocation calls by about 94%. - Tiny typed parse RAM: the representative configuration parse peaks at only 144 bytes because JESON writes directly into the application model.
- Embedded-friendly API: header-only, dependency-free C++20, no runtime registration, and no exception-based parse error path.
- Schema and validation together: macro-free field names, required values, defaults, nested models, enums, optionals, vectors, patches, Pydantic-style constraints, and accumulated path-aware errors.
- Portable integration: CMake, ESP-IDF, PlatformIO, Arduino, Zephyr, Meson, Bazel, Conan, and pkg-config metadata live in the JESON repository.
This is more useful than reading a timing chart alone: the libraries do not ask the application to write the same kind of code.
| Library | Typical style | Where it is strongest | JESON's trade-off |
|---|---|---|---|
| JESON | Typed aggregate, direct parse; mutable and compact DOMs | Known-schema embedded configuration, commands, and telemetry | Smaller, focused JSON API; requires C++20 and uses the standard containers selected by the model. |
| reflect-cpp | Automatic reflection with typed and generic APIs | Rich compile-time reflection and many serialization formats | Closest inspiration, but broader in scope. JESON targets a compact JSON-only feature set. |
| ArduinoJson | JsonDocument and variants |
Embedded DOM work, explicit document capacity/allocator control | JESON puts the typed schema, defaults, and validation in the C++ model instead. |
| RapidJSON | DOM, SAX, writer, custom allocators | Raw JSON performance and low-level control | JESON gives up some API breadth for direct models and less mapping code. |
| nlohmann/json | STL-like dynamic JSON value | General-purpose ergonomics, conversions, and ecosystem | JESON does not require a generic tree before reaching a typed model. |
| cJSON | C node tree and explicit ownership | Portable C projects and a straightforward DOM | JESON requires modern C++, but supplies typed conversion and validation automatically. |
The focused adapters are pinned for reproducibility and several are historical versions. The table describes programming models, not a universal ranking, and the measured executable size is the complete benchmark operation—not a promise about the size of every firmware image.
| Area | Checked result | Interpretation |
|---|---|---|
| Conformance | 136/136 | Both JESON DOMs pass every checked validation, number, string, and round-trip case. |
| Combined compact-DOM parse | 10.2 ms | Down from 13.4 ms for Value; below the 15 ms target and close to the 10 ms stretch target. |
| Combined compact-DOM peaks | 5.64 MiB | About 40% below Value; lowest summed parse peak in this checked DOM run. |
| Combined stringify / pretty | 7.9 / 10.3 ms | Output performance remains effectively unchanged. |
| Normal stripped executable | 68.1 KiB | Comparable schema-less Release build with exceptions and RTTI enabled. |
| Embedded-size profile | 51.4 KiB | JESON compact parse + statistics with size flags and unused runtime features disabled. |
| Typed configuration peak | 144 bytes | Direct-to-model heap peak; no generic document is constructed. |
The large-document charts are schema-less DOM measurements. The application-model charts use a separate small embedded configuration and distinguish native typed mapping from DOM plus manual field copying. They are not a before/after measurement of JESON's typed parser. The comparison versions are reproducibly pinned and partly historical; repeat with the target compiler and device before making a product decision.
More specifically, the application-model lane uses native typed parsing only for JESON typed and reflect-cpp. ArduinoJson, cJSON, nlohmann/json, RapidJSON, JESON Mutable DOM, and JESON Compact DOM parse a generic DOM and then copy the fields manually. The chart compares the completed mapping task; it does not claim that every row is a native typed API.
The original author (Milo Yip) of this benchmark is also the primary author of RapidJSON.
Although the development of benchmark is attempted to be as objective and fair as possible, every benchmarks have their drawbacks, and are limited to particular testing procedures, datasets and platforms. And also, this benchmark does not compare additional features that a library may support, or the user-friendliness of APIs, securities, cross-platform, etc. The author encourage users to benchmarks with their own data sets and platforms.
| Benchmark | Description |
|---|---|
| Parse Validation | Use JSON_checker test suite to test whether the library can identify valid and invalid JSONs. (fail01.json is excluded as it is relaxed in RFC7159. fail18.json is excluded as depth of JSON is not specified.) |
| Parse Double | 66 JSONs, each with a decimal value in an array, are parsed. The parsed double values are compared to the correct answer. |
| Parse String | 9 JSONs, each with a string value in an array, are parsed. The parsed strings are compared to the correct answer. |
| Roundtrip | 27 condensed JSONs are parsed and stringified. The results are compared to the original JSONs. |
| Benchmark | Description |
|---|---|
| Parse | Parse in-memory JSON into DOM (tree structure). |
| Stringify | Serialize DOM into condensed JSON in memory. |
| Prettify | Serialize DOM into prettified (with indentation and new lines) JSON in memory. |
| Statistics | Traverse DOM and count the number of JSON types, total length of string, and total numbers of elements/members in array/objects. |
| Sax Round-trip | Parse in-memory JSON into events and use events to generate JSON in memory. |
| Sax Statistics | Parse in-memory JSON into events and use events to conduct the statistics. |
| Code size | Executable size in byte. (Currently only support jsonstat program, which calls "Parse" and "Statistics" to print out statistics of a JSON file. ) |
All benchmarks contain the following measurements:
| Measurement | Description |
|---|---|
| Time | Duration in millisecond |
| Memory | Memory consumption in bytes for the result data structure. |
| MemoryPeak | Peak memory consumption in bytes throughout the parsing process. |
| AllocCount | Number of memory allocation (including malloc, realloc(), new et al.) |
This fork includes two JESON C++20 schema-less adapters. JESON Mutable DOM
uses the editable, STL-oriented jeson::Value type. JESON Compact DOM uses
the read-only, paged jeson::CompactDocument type with 24-byte nodes. The
nativejson operations remain schema-less and comparable with the other DOM
libraries. A separate benchmark parses the same representative device
configuration directly into a typed application model.
The root CMake build is a reproducible six-library lane containing two JESON modes,
cJSON, reflect-cpp, RapidJSON, nlohmann/json, and ArduinoJson. It builds only
those adapters, configures reflect-cpp with JSON support only, instruments its
bundled yyjson allocator, and creates an individual jsonstat executable for
each library's code-size measurement.
git submodule update --init -- \
thirdparty/ArduinoJson thirdparty/cJSON thirdparty/jeson \
thirdparty/nlohmann thirdparty/rapidjson thirdparty/reflect-cpp
cmake -S . -B build-selected -DCMAKE_BUILD_TYPE=Release \
-DNATIVEJSON_MACHINE=my-machine -DNATIVEJSON_ENABLE_NATIVE_ARCH=OFF
cmake --build build-selected --parallel
cd build-selected/bin
./nativejson_release_x64_cmake --verify-only
./nativejson_release_x64_cmake --conformance-only
./nativejson_release_x64_cmake --performance-only
../../tools/run-typed.sh .. 100000Or run tools/run-selected.sh my-machine. Set
NATIVEJSON_ENABLE_NATIVE_ARCH=ON only when comparing repeated runs on the
same CPU; it makes cross-machine results less portable. CSV output is written
under result/ and intentionally ignored because timings are machine-specific.
To benchmark an uncommitted JESON checkout without touching the pinned
submodule, configure with -DJESON_INCLUDE_DIR=/path/to/jeson/include.
For binary-size work, tools/report-code-size.sh build-selected prints both
the nativejson raw executable size and a copy stripped of local symbols. The
executables all perform the same parse-and-statistics operation; they are more
meaningful than comparing whole library archives with different feature sets.
tools/report-jeson-embedded-size.sh builds the explicitly labeled JESON-only
size profile with -Oz, ThinLTO, -fno-exceptions, and -fno-rtti; it is not
silently mixed into the fair Release chart.
The selected submodules are pinned for reproducibility. In particular, the original benchmark pins historical releases of ArduinoJson 5.6.6, cJSON 1.5.0, nlohmann/json 2.1.1, and RapidJSON 1.1.0. The focused build does not pretend those are current releases: use the exact-pin result to track JESON or update all comparison pins and adapters together for a contemporary shootout. The old nlohmann adapter is compiled as C++17 because that release predates C++20 allocator removals; JESON and reflect-cpp compile as C++20.
A checked example run, including limitations and exact environment, is in
docs/results/apple-arm64-2026-11-06.md.
These charts show that checked Apple arm64 run. Orange and amber are JESON; lower is better except for conformance. The first group compares only schema-less DOM operation. It does not measure JESON's normal typed path.
The executable performs the same parse-and-statistics job for every adapter. See the detailed report for both raw and stripped sizes and the embedded-toolchain caveat.
This lane makes the mapping style explicit: the JESON typed and reflect-cpp rows parse directly into a model. The Compact DOM and Mutable DOM rows are included to show the cost of choosing schema-less parsing plus manual field copying; they are not implementations used by the typed API.
Regenerate the SVGs after updating the checked summary CSV with:
tools/generate-selected-charts.pyCurrently 45 libraries have adapters. They are listed in alphabetic order:
| Library | Language | Version | Notes |
|---|---|---|---|
| ArduinoJson | C++ | 5.6.6 | |
| Boost.JSON | C++ | 1.80.0 | |
| CAJUN | C++ | 2.0.3 | |
| C++ REST SDK | C++11 | v2.8.0 | Need Boost on non-Windows platform. DOM strings must be UTF16 on Windows and UTF8 on non-Windows platform. |
| ccan/json | C | ||
| cJSON | C | 1.5.0 | |
| Configuru | C++ | 2015-12-18 | gcc/clang only |
| dropbox/json11 | C++11 | ||
| Facil.io | C | 0.5.3 | |
| FastJson | C++ | Not parsing number per se, so do it as post-process. | |
| folly | C++11 | 2016.08.29.00 | Need installation |
| gason | C++11 | ||
| jansson | C | v2.7 | |
| jeayeson | C++14 | ||
| json-c | C | 0.12.1 | |
| jsoncons | C++11 | 0.97.1 | |
| json-voorhees | C++ | v1.1.1 | |
| json spirit | C++ | 4.08 | Need Boost |
| Json Box | C++ | 0.6.2 | |
| JsonCpp | C++ | 1.0.0 | |
| hjiang/JSON++ | C++ | ||
| jsmn | C | Not parsing number per se, so do it as post-process. | |
| jvar | C++ | v1.0.0 | gcc/clang only |
| JESON | C++20 | 0.2.0 | Uses CompactDocument and Value in the DOM lane, plus direct typed parsing in the application-model lane. |
| Jzon | C++ | v2-1 | |
| nbsdx/SimpleJSON | C++11 | ||
| Nlohmann/json | C++11 | v2.0.3 | |
| parson | C | ||
| picojson | C++ | 1.3.0 | |
| pjson | C | No numbers parsing, no DOM interface | |
| POCO | C++ | 1.7.5 | Need installation |
| qajson4c | C | 1.0.0 | gcc/clang only |
| Qt | C++ | 5.6.1-1 | Need installation |
| RapidJSON | C++ | v1.1.0 | There are four configurations: RapidJSON (default), RapidJSON_AutoUTF (transcoding any UTF JSON), RapidJSON_Insitu (insitu parsing) & RapidJSON_FullPrec (full precision number parsing) |
| reflect-cpp | C++20 | v0.27.1 | rfl::Generic; JSON-only build with bundled yyjson. |
| sajson | C++ | ||
| SimpleJSON | C++ | ||
| sheredom/json.h | C | Not parsing number per se, so do it as post-process. | |
| udp/json | C | 1.1.0 | Actually 2 libraries: udp/json-parser & udp/json-builder. |
| taocpp/json | C++11 | 1.0.0-beta.7 | Uses PEGTL for parsing |
| tunnuz/JSON++ | C++ | ||
| ujson | C++ | 2015-04-12 | |
| ujson4c | C | ||
| V8 | C++ | 5.1.281.47 | Need installation |
| vincenthz/libjson | C | 0.8 | |
| YAJL | C | 2.1.0 | |
| ULib | C++ | v1.4.2 | Need building: (./configure --disable-shared && make) |
Libraries with Git repository are included as submodule in thirdparty path. Other libraries are add as files in thirdparty path.
The exact commit of submodule can be navigated at here.
To measure the overheads of the benchmark process, a strdup test is added for comparison. It simply allocate and copy the input string in Parse and Stringify benchmark.
Besides, some libraries was tried to integrated in this benchmark but failed:
| Library | Issue |
|---|---|
| libjson | Unable to parse UTF-8 string |
| lastjson | |
| StiX Json | |
| boost property_tree | number, true, false, null types are converted into string. |
All tested JSON data are in UTF-8.
| JSON file | Size | Description |
|---|---|---|
canada.json source |
2199KB | Contour of Canada border in GeoJSON format. Contains a lot of real numbers. |
citm_catalog.json source |
1737KB | A big benchmark file with indentation used in several Java JSON parser benchmarks. |
twitter.json |
632KB | Search "一" (character of "one" in Japanese and Chinese) in Twitter public time line for gathering some tweets with CJK characters. |
The benchmark program reads data/data.txt which contains file names of JSON to be tested.
- Execute
git submodule update --initandgit -C thirdparty/boost update --initto download all submodules (libraries). - Obtain premake5.
- Copy premake5 executable to
build/path (or system path). - Run
premake.batorpremake.shinbuild/ - On Windows, build the solution at
build/vs2015/. - On other platforms, run GNU
make -f benchmark.make config=release_x32 && make -f nativejson.make config=release_x32(orrelease_x64) atbuild/gmake/ - Optional: run
build/machine.shfor UNIX or CYGWIN to use CPU info to generate prefix of result filename. - Run the
nativejson_release_...executable is generated atbin/ - The results in CSV format will be written to
result/. - Run GNU
makeinresult/to generate results in HTML.
For simplicity, on Linux/OSX users can simply run make (or make CONFIG=release_x32) at project root to run 4-10 above.
Some libraries, such as Boost, POCO, V8, etc., need to be installed by user manually.
The charts below are retained from the upstream benchmark for historical context. They predate JESON; use the focused charts above for current JESON results.
Update on: 2016-9-9
A collection of benchmarks results can be viewed HERE. Select "Benchmark" from the menu to check available benchmark configurations. The presentation is powered by Google Charts with interactivity.
The followings are some snapshots from the results of MacBook Pro (Retina, 15-inch, Mid 2015, Corei7-4980HQ@2.80GHz) with clang 7.0 64-bit.
This is the average score of 4 conformance benchmarks. Higher is better. Details.
This is the total duration of parsing 3 JSONs to DOM representation, sorted in ascending order. Lower is better. Details
This is the total memory after parsing 3 JSONs to DOM representation, sorted in ascending order. Lower is better. Details
(Note: The results for Qt is incorrect as the benchmark failed to hook its memory allocations)
This is the total duration of stringifying 3 DOMs to JSONs, sorted in ascending order. Lower is better. Details
This is the total duration of prettifying 3 DOMs to JSONs, sorted in ascending order. Lower is better. Details
The is the size of executable program, which parses a JSON from stdin to a DOM and then computes the statistics of the DOM. Lower is better. Details
-
How to add a library?
Use
submodule add https://...xxx.git thirdparty/xxxto add the libary's repository as a submobule. If that is not possible, just copy the files into 'thirdparty/xxx'.For C libary, add a
xxx_all.cinsrc/cjsonlibs, which#includeall the necessary.cfiles of the library. And then create atests/xxxtest.cpp.itFor C++ library, just need to create a
tests/xxxtest.cpp, which#includeall the necessary.cppfiles of the library.You may find a existing library which similar to your case as a start of implementing
tests/xxxtest.cpp.Please submit a pull request if the integration work properly.
-
What if the build process is failed, or the benchmark crashes?
This happens on some platforms as not every libaray is stable for all platforms.
You can simply delete the local
tests/xxxtest.cppand re-run the build.BTW, if you are adding a library, you can remove all
tests/xxxtest.cppexcepttests/rapidjsontest.cppand your test. This make the build process fast. RapidJSON's test is needed as a reference to compare statistics results only. -
On which platform the benchmark can be run?
The author tests it on OSX/clang, Winwdows/vs2015, Ubuntu/clang3.7+gcc5.0 (via Travis CI). The benchmark may work in other platforms as well but you will need to generate the build files via premake5, and resolve any potential issues.
-
How to reduce the build process time?
You can preserve the tests of libaries that you only need, as in question 2. You can also use
make ARGS=--verify-only,make ARGS=--performance-only,make ARGS=--conformance-onlyto execute the required part.
-
Basic benchmarks for miscellaneous C++ JSON parsers and generators by Mateusz Loskot (Jun 2013)
-
JSON Parser Benchmarking by Chad Austin (Jan 2013)
-
vjson (Replaced by gason)

.png)
.png)
.png)
.png)
.png)