|
pjson 1.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 or compile pjsonlib/src/pjson.cpp with the application in addition to including pjson.h. Those two files are the canonical API and behavior sources; generated documentation and examples are explanatory.
| nlohmann/json | pjson | Important difference |
|---|---|---|
json j; | pjson j; | Both start as JSON null. |
json::object() / json::array() | j.resetTo(pjson::jsonObject) / j.resetTo(pjson::jsonArray) | pjson has no object/array factory. |
json::parse(text) | pjson::parse(text) | Returns pjson::unique_ptr; failure is an empty pointer. |
json::parse(text, nullptr, false) | pjson::parse(text, error) | Inspect the pointer and optional ParseError; there is no discarded value. |
input >> j or json::parse(input) | pjson::parseStream(input) | 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 distinguishes signed int64_t and double; there is no unsigned kind. |
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.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.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[static_cast<int>(j.size())] = value | Indexed builder access grows an array. |
j.erase(key/index) | j.erase(key/index) | Returns bool; an array index is size_t. |
| range iteration | size() + find(index), or keys() + find(key) | No public raw-container access. |
json::sax_parse(...) | pjson::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 | value.validate(schema[, errors][, options]) | Implements only the documented subset. |
All DOM parse and stream-parse overloads return pjson::unique_ptr, including those using the default allocator. An empty pointer means failure. The custom deleter destroys the complete tree through the allocator recorded by its root. Do not call delete on the pointer or convert it to a differently-deletered smart pointer.
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 move the pjson::unique_ptr to transfer root ownership.
Allocator-aware overloads take a borrowed pjson::Allocator&. That allocator must outlive the returned root and every descendant. A directly constructed root remains caller-owned; a parser-created root is owned by pjson::unique_ptr. SAX parsing builds no persistent DOM and has no allocator overload.
ParseError::offset is a zero-based byte offset. line and column are one-based, and column counts bytes. Every parse overload that accepts a ParseError& resets all fields before doing work. Success leaves:
Failure sets ok == false and describes the first error. It is safe to reuse one error object across calls; never infer failure from an old message. Test the returned pointer first, or ok for SAX parsing.
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.
ParseOptions 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.
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 unsigned numeric representation and no convenience int or float API. Use int64_t and double explicitly in assignments, appends, vectors, SAX callbacks, and tryGet calls:
Before narrowing an unsigned source, perform an application-level range check. 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, ascending keys, and a 64 MiB output limit. Set maxOutputBytes = 0 only when explicitly requesting unlimited output. Objects are inherently map-ordered; insertion order is unavailable. Non-finite stored doubles serialize as JSON null. Finite doubles use locale-independent, stable round-trip formatting with 15–17 significant digits; shortest spelling is not part of the contract.
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 ParseError when supplied.
pjson validates a value directly against another pjson value. It does not compile a schema or validate against a meta-schema. The collecting overload appends SchemaError entries; clear a reused vector first. Error paths are RFC 6901 pointers, with the empty string denoting the root.
The documented pjson subset is the complete enforced vocabulary; it is not a complete JSON Schema draft implementation:
| Area | Supported keywords and forms |
|---|---|
| General | type (string or array), enum, const, local-fragment $ref |
| Objects | properties, patternProperties, propertyNames, required, dependentRequired, dependencies, additionalProperties, minProperties, maxProperties |
| Arrays | single-schema or tuple-array items, minItems, maxItems, uniqueItems |
| Numbers | minimum, maximum, numeric exclusiveMinimum, numeric exclusiveMaximum, multipleOf |
| Strings | minLength, maxLength, pattern, and supported format values |
| Composition | allOf, anyOf, oneOf, not |
| Schema values | Boolean schemas |
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 producing an error. Audit schemas against the table above and retain an external validator when another vocabulary is required. Remote references are unsupported; $ref resolves only local URI-fragment JSON Pointers.
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. SchemaOptions::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::unique_ptr and check every result before dereferencing it.ParseError, 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 and double, with explicit range checks at unsigned boundaries.SerializeOptions, and handle invalid-UTF-8 serialization failure.