|
pjson 2.0.0
A small, owning JSON value for C++11
|
This guide maps common nlohmann::json idioms to the final ByteDance::pjson API. Both libraries provide a mutable JSON DOM, but pjson uses explicit ownership, exact typed reads, builder-only subscripting, and status-based parse and patch errors. A mechanical type rename is therefore not a safe migration strategy.
The examples assume:
pjson requires C++11 or newer. It is a compiled library; link pjson::pjson, which contains the core, parser, serialization, Pointer, Patch, and schema translation units. Include pjson_parser.h only where parsing is needed.
| nlohmann/json | pjson | Important difference |
|---|---|---|
json j; | pjson j; | Both start as JSON null. |
json::object() / json::array() | pjson::object() / pjson::array() | Factories return an empty object/array value. |
json::parse(text) | pJsonParser().parse(text) | Returns a pjson value; failure yields JSON null. |
json::parse(text, nullptr, false) | pJsonParser().parse(text, error) | Pass a pJsonParser::Error to detect failure vs. a real null. |
input >> j or json::parse(input) | pJsonParser().parseStream(input, error) | Builds a DOM and buffers the complete input. |
j.dump() | j.toString() | Compact output. |
j.dump(indent, ch, ensure_ascii) | j.toString(options) | Configure a SerializeOptions value explicitly. |
out << j | j.write(out[, options]) | Returns void; inspect stream state. |
j.is_null(), is_string(), ... | j.isNull(), isString(), ... | pjson has signed int64_t, unsigned uint64_t, and double numeric kinds. |
j.get<T>() | j.tryGet(out) | Exact-type extraction writes an out-parameter and returns false on mismatch. |
j.get_ref<const std::string&>() | j.tryGet(pjson::StringView&) | The view is borrowed and mutation-sensitive. |
j.contains(key) | j.contains(key) / j.hasKey(key) | Non-mutating; false on a non-object. |
j.find(key) | j.find(key) | pjson returns a borrowed pointer or nullptr, not an iterator. |
j.at(key) | j.at(key) | Checked, non-vivifying; throws std::out_of_range when absent. |
j.value(key, fallback) | tryGet, then choose the fallback | The fallback remains application logic. |
j[key] = value | j[key] = value | pjson operator[] is a builder and may replace the receiver's type. |
j.push_back(value) | j.pushBack(value) | Promotes to an array and appends a value. |
j.erase(key/index) | j.erase(key/index) | Returns bool; an array index is size_t. |
| range iteration | forEachMember/forEachElement, or size() + find(index) | No public raw-container access. |
json::sax_parse(...) | pJsonParser().parseSax(...) / parseSaxStream(...) | parseSaxStream() is the incremental stream path. |
j = j.patch(patch) | j.applyPatch(patch[, error][, options]) | Mutates atomically; PatchOptions bounds amplification. |
j.merge_patch(patch) | j.applyMergePatch(patch[, error][, options]) | Atomic RFC 7396 with the same limits. |
| external JSON Schema library | pJsonSchemaValidator v(schema[, options]); v.validate(value[, errors]) | Standalone validator; subset by default, with required Draft 2020-12 vocabularies via Options::draft2020(). |
All DOM parse and stream-parse overloads return a pjson by value that owns its subtree and frees it on destruction — no smart pointer, no delete. The terse overloads return JSON null on failure; pass a pJsonParser::Error to tell failure apart from a successfully parsed literal null. Move the value to transfer ownership into another document.
The (const char*, size_t) overload parses exactly the supplied byte span, including embedded NUL bytes. parseStream() buffers one complete document. Pass pjson& or const pjson& when code only borrows the parsed document, and std::move the returned value to transfer ownership into another tree.
An allocator-aware pJsonParser constructor takes a borrowed pjson::Allocator&. That allocator must outlive the returned value and every descendant. A directly constructed root remains caller-owned; a parser-created value is bound to, and freed through, the allocator selected by the parser. SAX parsing builds no persistent DOM and does not use the parser's allocator.
pJsonParser::Error::offset is a zero-based byte offset. line and column are one-based, and column counts bytes. code is a stable machine-facing category. Every parse overload that accepts a pJsonParser::Error& resets all fields before doing work. Success leaves:
Failure sets ok == false, a stable code, and describes the first error. It is safe to reuse one error object across calls; never infer failure from an old message. Test error.ok after every parse.
There is no permissive parsing mode. DOM and SAX parsing reject unknown escapes, unpaired UTF-16 surrogates, upper- or mixed-case keywords, raw control characters in strings, malformed UTF-8, invalid number grammar, comments, trailing commas, NaN, Infinity, and trailing non-whitespace content.
pJsonParser::Options contains resource budgets and duplicate-key policy only:
maxNodes == 0 and maxInputBytes == 0 mean unlimited. A non-positive maxDepth selects an effective one-level limit, not unlimited parsing. The duplicate policies are:
| Policy | DOM behavior | SAX behavior |
|---|---|---|
RejectDuplicateKeys | Fail at the second key. | Fail at the second key. |
KeepFirstDuplicate | Keep the first value. | Suppress later duplicate value-subtree events. |
KeepLastDuplicate | Replace the earlier value. | Emit each occurrence because prior events cannot be retracted. |
To preserve nlohmann/json's usual keep-last behavior without weakening RFC 8259 validation:
operator[] is only a mutable builder API. A string subscript changes a non-object into an object and creates a missing null member. An integer subscript changes a non-array into an array and grows it with null elements. Do not translate checked or observational nlohmann access into pjson subscripting. One indexed access that would create more than 1,000,000 children throws std::length_error before mutation. Valid negative int indexes count from the end; an index before the beginning throws std::out_of_range. Prefer the size_t overload for non-negative builder indexes.
Use find(key) and find(index) for borrowed node access. Both return nullptr for the wrong container type or a missing child and never mutate the document. Negative indexes count from the end.
tryGet supports int64_t, double, bool, std::string, and pjson::StringView, both on a node and through key/index child overloads. It returns false on absence or type mismatch and leaves the output unchanged. An integer may widen to double; no other coercion occurs. A StringView borrows bytes and is invalidated when its node or an ancestor is modified or destroyed.
pjson has no convenience int/float API, but as of 2.0 it does have a distinct unsigned kind. Use int64_t, uint64_t, and double explicitly in assignments, appends, vectors, SAX callbacks, and tryGet calls:
Integer tokens above INT64_MAX (up to UINT64_MAX) become the unsigned kind. Tokens beyond UINT64_MAX, and non-finite floats, are rejected by default (see pJsonParser::Options::AllowLossyNumbers and SerializeOptions::NonFinitePolicy). An integer read as double may lose precision beyond 2^53.
Use operator[] to build or deliberately mutate paths and scalar assignment for std::string, C strings, bool, int64_t, and double. Exact vector overloads exist for std::string, bool, int64_t, and double; there are no convenience vectors of int, float, or C strings. Build other arrays with indexed assignment.
findPointer() performs non-vivifying RFC 6901 lookup. The empty pointer addresses the root. Non-empty pointers begin with /; array indices are canonical unsigned decimal tokens, and - is reserved for JSON Patch add. Use escapePointerToken() when constructing paths from object keys.
applyPatch() supports RFC 6902 add, remove, replace, move, copy, and test. It applies the complete operation array to a scratch document and commits only on success. The reporting overload fills PatchError. A remove operation whose path is the empty string succeeds and resets the target to JSON null. This is the pjson representation of removing the document root.
applyMergePatch() provides the same atomic status-based model for RFC 7396. An object patch recursively merges, null members remove object members, and a non-object patch replaces the complete target. Both patch APIs are noexcept.
Both APIs accept a trailing PatchOptions. Defaults allow 10,000 operations, 1,000,000 cloned nodes, 64 MiB of cloned node/string/key bytes, and 1,000,000 work units through maxOperations, maxClonedNodes, maxClonedBytes, and maxWork. Zero retains the corresponding hard ceiling. A budget failure returns false, reports PatchError::ResourceLimit, and preserves the target. maxOperations counts RFC 6902 array entries or Merge Patch members. Moving the document root beneath itself reports PatchError::MoveRootNotAllowed; root removal remains valid and produces null.
Use SerializeOptions for every non-default serialization choice; the legacy boolean pretty-print overloads are not part of the final API.
Default construction selects compact output, two-space indentation, a space indent character, UTF-8 output, and a 64 MiB output limit. Set maxOutputBytes = 0 only when explicitly requesting unlimited output. Objects use unspecified native storage order for traversal and serialization; insertion order is not retained. Non-finite stored doubles fail serialization unless SerializeOptions::nonFinite explicitly selects NonFiniteToNull or NonFiniteToString. Finite doubles use pinned Ryu shortest-round-trip conversion followed by pjson's documented fixed/scientific spelling policy.
Every stored string value and object key must contain valid UTF-8. Invalid stored UTF-8 is a serialization failure even when escapeNonAscii is false: toString() throws std::invalid_argument. Output-budget or indentation/size overflow throws std::length_error. write() detects all three logical failures before emission and sets failbit; only a physical stream failure can leave a partial prefix or propagate an enabled stream exception. Check stream state after write().
parse() and parseStream() build an owning DOM. parseStream() bounds input with maxInputBytes but buffers the document. parseSaxStream() reads incrementally and retains no DOM. SAX callbacks receive borrowed string/key references valid only for the duration of the callback. Returning false from a callback cancels parsing; the public call then returns false and populates pJsonParser::Error when supplied.
pjson compiles a schema (itself a pjson value) into a standalone ByteDance::pJsonSchemaValidator (declared in <pjson_schema.h>) and validates values against it. The validator is a pure consumer of pjson's public API. The collecting overload appends pJsonSchemaValidator::Error entries; clear a reused vector first. Error paths are RFC 6901 pointers, with the empty string denoting the root.
Default construction implements pjson's explicitly named subset dialect. Options::draft2020() selects the official Draft 2020-12 URI, bundled standard meta-schema validation, modern $ref behavior, and per-resource vocabulary activation. Custom meta-schema URIs and external resources are loaded only through the explicit resolver. An unsupported root $schema or required $vocabulary makes isSchemaValid() false; inspect schemaErrors() before trusting validation. Unknown optional vocabularies are accepted as annotations.
The following keywords form the default subset and the required Draft 2020-12 vocabularies implemented by the opt-in mode:
| Area | Supported keywords and forms |
|---|---|
| General | type (string or array), enum, const |
| References | $id, $anchor, $dynamicAnchor, $ref, $dynamicRef; explicit resolver for external documents |
| Objects | properties, patternProperties, propertyNames, required, dependentRequired, dependencies, dependentSchemas, additionalProperties, unevaluatedProperties, minProperties, maxProperties |
| Arrays | items, prefixItems, contains, minContains, maxContains, unevaluatedItems, minItems, maxItems, uniqueItems |
| Numbers | minimum, maximum, numeric exclusiveMinimum, numeric exclusiveMaximum, multipleOf |
| Strings | minLength, maxLength, pattern, and supported format values |
| Composition | allOf, anyOf, oneOf, not, if, then, else |
| Schema values | Boolean schemas |
In permissive subset mode, unknown or unsupported schema keywords are ignored and therefore impose no constraint. This is a compatibility hazard: a typo or unsupported security rule can make validation less restrictive without an error. Use Options::strict() for a fail-closed subset boundary or Options::draft2020() for the implemented official dialect. The complete optional format-assertion vocabulary, arbitrary-precision numbers, and other drafts remain out of scope. $ref resolves URI resources, pointers, and anchors; external documents are available only through an explicit resolver callback, so pjson never performs network I/O. $dynamicRef/$dynamicAnchor and both unevaluated* keywords are supported by the modern subset option.
minLength and maxLength count Unicode code points, not UTF-8 bytes. pattern uses ECMAScript regular-expression syntax with search semantics. The default policy caps pattern and subject sizes and rejects expressions outside a conservative safe subset. pJsonSchemaValidator::Options::trustedRegex() removes only those regex restrictions and should be used only when both schema and instance are trusted. Validation depth, reference, work, and error-count budgets remain in effect. Known format checks run by default; unknown format names are ignored.
pjson value plus a pJsonParser::Error, and check error.ok before using the value.pJsonParser::Error, remembering that reporting calls reset it on entry.find or tryGet; reserve operator[] for building.size() plus find(index), or keys() plus find(key).int64_t, uint64_t, and double, with explicit range checks at signed/unsigned boundaries.SerializeOptions, and handle invalid-UTF-8 serialization failure.Options::draft2020() deliberately and test both accepted and rejected instances.