J++Jsonic++
Architecture

The small shape is part of the design.

Jsonic++ keeps parsing, the value model and serialization in one auditable C++17 header. That is not merely a packaging trick: the project deliberately avoids introducing a separate runtime, adapter layer, allocator framework or generated binding model.

One value type, six JSON kinds

json::Document represents JSON's six logical value kinds. Internally json::Type has seven tags because numbers have two storage modes: Number for ordinary double-backed values and StrNumber when the parser must retain a significant original numeric token. Both satisfy is_number() and expose the converted value through .num.

The remaining payloads are deliberately ordinary C++: bool, std::string, std::vector<Document>, or an insertion-ordered std::vector<std::pair<std::string, Document>> for objects. The benefit is that a debugger, maintainer or coding agent can see the actual storage model immediately.

Object and array ownership

Each document owns its strings, array children and object members through ordinary standard-library containers. Object member insertion order and duplicate members are preserved by default, matching JSON's syntax-level contract without pretending duplicate interpretation is interoperable. Configuration consumers can explicitly reject duplicates through ParseOptions.

Key lookup is intentionally simple and linear over the small ordered object vector. Jsonic++ is optimized for a compact, understandable implementation rather than pretending to be the ideal DOM for enormous lookup-heavy objects. For very large root arrays nested under a known member, the streaming for_each_array_item() path avoids constructing that entire array at once.

Parser boundary

The default parser accepts strict JSON grammar, not JavaScript object literals. Objects require quoted keys; strings use JSON escapes; numbers follow JSON number grammar. Comments and trailing commas are independent opt-ins intended for configuration files; enabling them does not enable unquoted keys, single-quoted strings or permissive recovery.

Parsing is recursive-descent and produces Document values directly. The public overloads accept std::string, null-terminated C strings and bounded std::string_view; the view path avoids copying the input buffer. Parse failures expose a message, zero-based byte offset and one-based line/column through ParseDiagnostic.

Performance without a second architecture

The recent optimization work stayed inside this same parser rather than adding a separate “fast but looser” implementation. Hot paths use bounded source views, fast ASCII/string scanning and from_chars-based numeric conversion where appropriate, with fallbacks for cases that need them. Each retained change was measured only after the 810-case conformance gate was green.

That campaign brought Jsonic++ into the same performance class as the conformant RapidJSON configuration on the project's same-host benchmark. The result matters because it was achieved without changing the public model or weakening malformed-input behavior.

Serialization boundary

dump() writes the represented document back to JSON. Indent 0 produces compact output; positive indentation produces structured output. StrNumber values serialize from their retained token so significant spellings are not discarded. String escaping is shared with append_escaped_string().

What Jsonic++ deliberately does not own

  • reflection or automatic struct mapping;
  • JSON Pointer, JSON Patch or query languages;
  • schema validation;
  • custom allocators and polymorphic memory-resource abstractions;
  • CBOR, MessagePack, BSON or other serialization formats;
  • networking, file I/O or application configuration policy.

Those can be valuable features in other libraries. Here, excluding them keeps the parser suitable as a vendored component inside tools whose real job is something else.

Vendoring and synchronization

Standalone Jsonic++ owns parser semantics. Nift and Minify++ own their integration behavior around synchronized copies. The synchronization contract prevents those embedded copies from silently becoming different parsers while still letting each consumer remain dependency-light and self-contained.