A line-by-line, behavior-identical Rust port of
cJSON v1.7.19 (commit
fb16e5cf358798aabb049655975cde8427101056), including the cJSON_Utils
helpers (RFC 6901 JSON pointers, RFC 6902 JSON Patch, RFC 7396 Merge Patch,
sorting, and patch generation).
The port is proven equivalent by running the unmodified original cJSON test suite against the Rust implementation through a declarations-only C shim, plus differential tests that replay the reference C code against the port.
cJSON.c (reference) ──┐ ┌── tests/*.c (unmodified originals)
│ FFI boundary │
Rust port ───────┼─────────────────┼── C shim: cJSON.c declarations
│ #![no_mangle] │
cJSON_Utils.c (ref) ───┘ └── CMake harness
- 22/22 tests from the original suite pass against the Rust port
(
19core +3utils), and 22/22 under AddressSanitizer and 22/22 under UndefinedBehaviorSanitizer (reproduce withscripts/harness_ubsan.sh). - 40 Rust unit/integration tests pass (
cargo test, and also undercargo test --release), including differential suites that run the port and the compiled reference C library side by side — asserting identical return codes and byte-identical output — plus deterministic fuzzers. - 2,000,000+ differential fuzz cases across the release campaign
(
scripts/fuzz_differential.sh, default 1M parse + 500k print + 250k manip + 250k utils) pass with zero divergences; a lightweight 5-test always-on fuzz suite (scaled withCJSON_FUZZ_ITERS) ships incargo test. - Independent-oracle cross-check against
serde_json(an unrelated JSON implementation): every vendored corpus file plus 100k generated documents parse structurally identically; documents serde rejects but the port accepts are exactly cJSON's documented leniencies (raw control bytes in strings, lone surrogates, overflow to infinity), and are reported without failing. - 121/121 JSON-Patch corpus entries (from
json-patch-tests) apply and round-trip identically to the reference. - Coverage of the port logic (measured by
scripts/coverage.shwithcargo-llvm-cov):parse93%,utils93%,model100%,manip82%,print84%.tests/diff_print_edge.rstargets the print paths JSON text alone cannot reach: NaN/±Inf numbers, raw nodes, nesting past the 1000-item limit, invalid type flags,%1.17g-escalating doubles, the full control-byte escape table, andPrintPreallocatedbuffer truncation. The remaining gap is dominated by the ABI shims inffi.rs, which are exercised by the C harness rather than Rust tests, and by out-of-memory error paths that need hook injection.
Submission-facing docs:
DECISIONS.mdexplains the porting choices and verification strategy.BENCHMARKS.mdrecords reproducible performance numbers from the shipped benchmark harness.
| Path | Purpose |
|---|---|
src/model.rs |
cJSON struct layout matching cJSON.h exactly |
src/alloc.rs |
allocation hooks (malloc/free/realloc) |
src/parse.rs |
parser: cJSON_Parse, cJSON_ParseWithOpts, parse_value… |
src/print.rs |
printer: cJSON_Print, cJSON_PrintUnformatted, print_value… |
src/utils.rs |
cJSON_Utils: pointers, patches, merge patch, sort, generate |
src/ffi.rs |
#[no_mangle] extern "C" symbols for the whole public API |
tests/diff_*.rs |
differential tests vs. the compiled reference (scripted, fuzz, and targeted API coverage) |
vendor/cjson-ref/ |
pristine upstream reference sources (see HASHES.md/HASHES.txt) |
harness/ |
CMake harness; runs the unmodified original C test suite |
The Rust sources are a direct port of cJSON.c / cJSON_Utils.c; function
names, static helper names, comments, and control flow mirror the C files so
the mapping is auditable. Only imports, manual C-string handling, and unsafe
blocks differ (inherent to Rust), and every static C helper is pub in the
port so the differential tests can call both sides.
Pristine upstream sources are vendored at vendor/cjson-ref/ — see
HASHES.md for provenance and HASHES.txt for the
SHA-256 checksums (verify with scripts/verify_vendored.sh). They are
byte-identical to the upstream commit and are never modified by this
project. The dev-only cjson-ref-sys helper crate compiles cJSON.c +
cJSON_Utils.c from there into
libcjson_ref_bench.a with every public symbol prefixed ref_ (see
bench_ref_rename.h): the port exports the same names via #[no_mangle], so
the prefix lets the differential tests and the benchmark link the real C
alongside the port and always call the genuine implementation (verified with
otool/nm, in both debug and release builds). The reference directory can
be overridden with the CJSON_REF_DIR environment variable. harness/tests/
contains byte-identical copies of the original tests/*.c;
harness/cJSON.c is a new declarations-only shim so the originals'
#include "../cJSON.c" resolves to declarations, with all definitions coming
from the Rust staticlib. scripts/verify_harness.sh asserts the harness
suite is byte-identical to the vendored upstream tests/ tree (the full
tests/ directory is vendored, not just the implementation), and
scripts/verify_vendored.sh pins that tree to the upstream commit by checksum —
so the "unmodified original suite" claim is mechanically checkable.
Everything below runs in order with one command (VERIFY_ITERS scales the
fuzz phase, e.g. VERIFY_ITERS=100000):
./scripts/verify.shIndividually:
# Verify the vendored reference sources are pristine
./scripts/verify_vendored.sh
# Verify the harness test suite is byte-identical to the vendored originals
./scripts/verify_harness.sh
# Rust tests (includes differential tests vs. the compiled reference C)
cargo test
cargo test --release
# Run the original C test suite against the Rust port
cmake -S harness -B harness/build-utils -DCMAKE_BUILD_TYPE=Release -DENABLE_CJSON_UTILS=ON
cmake --build harness/build-utils
ctest --test-dir harness/build-utils --output-on-failure
# Same, under AddressSanitizer (leak detection is disabled: broken on macOS)
cmake -S harness -B harness/build-asan-utils -DCMAKE_BUILD_TYPE=Debug \
-DENABLE_CJSON_UTILS=ON \
"-DCMAKE_C_FLAGS=-fsanitize=address -fno-omit-frame-pointer" \
"-DCMAKE_EXE_LINKER_FLAGS=-fsanitize=address"
cmake --build harness/build-asan-utils
ASAN_OPTIONS=detect_leaks=0 ctest --test-dir harness/build-asan-utils --output-on-failure
# Same, under UndefinedBehaviorSanitizer (core + utils, via script)
./scripts/harness_ubsan.sh
# Differential fuzz campaign (default 1M parse cases; use --iters/--seed)
./scripts/fuzz_differential.sh
# Independent-oracle cross-check vs. serde_json (vendored corpus + 100k generated)
./scripts/oracle_check.sh
# Coverage summary across the test suite
./scripts/coverage.sh
# Reproducible benchmark summary vs. the reference C implementation
cargo run --release --example benchmark- Object/array member order is preserved exactly as the C code preserves it
(insertion order;
SortObject/GeneratePatchesreorder only when the C code does). cJSON_Comparecompares object members pairwise in order, exactly like the reference — the differential tests use it to mirror the original suite, which does the same.cJSONUtils_ApplyPatches/GeneratePatchesreplicate the case-insensitive quirks of the C implementation (e.g. duplicate keys differing only by case are treated as the same key); the suite's case-sensitive variants (…CaseSensitive) are also ported and used by the unmodified tests.
MIT — see LICENSE. The original cJSON copyright is retained; this port is
derived from the MIT-licensed cJSON project.