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

json::Document represents all six JSON value kinds. A json::Type tag records the active kind while public fields hold the corresponding C++ representation: bool, double, std::string, std::vector<Document>, or an insertion-ordered std::vector<std::pair<std::string, Document>> for objects.

This is intentionally less abstract than variant/proxy-heavy APIs. The benefit is that a debugger, maintainer or coding agent can see the actual storage model immediately. The trade-off is equally explicit: callers are expected to check types when input is not already trusted by schema or surrounding application logic.

Object and array ownership

Each document owns its strings, array children and object members through ordinary standard-library containers. Object member insertion order is preserved. Parsing rejects duplicate object keys instead of silently choosing the first or last value, because duplicate handling is otherwise easy for different consumers to interpret differently.

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 parser accepts JSON grammar, not JavaScript object literals. Objects require quoted keys; strings use JSON escapes; numbers follow JSON number grammar. The implementation rejects trailing commas, duplicate keys, malformed numbers, invalid escapes, unpaired UTF-16 surrogates and non-finite/out-of-range number forms encountered by the parser.

Parsing is recursive-descent and produces Document values directly. Parse failures are internally represented with a position, then converted at the public boundary into a stable error string containing line and column context.

Serialization boundary

dump() writes the represented document back to JSON. Indent 0 produces compact output; positive indentation produces structured output. String escaping is shared with append_escaped_string(), so callers writing custom streaming output can reuse the same escaping policy without reaching into parser internals.

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.