|
pjson 2.0.0
A small, owning JSON value for C++11
|
This guide maps RapidJSON DOM, Reader/Writer, Pointer, and Schema idioms to the final ByteDance::pjson API. pjson favors a compact owning DOM, RFC 8259 parsing, explicit result ownership, status-based errors, and a deliberately limited schema vocabulary.
pjson requires C++11 or newer and is a compiled library. Link pjson::pjson, which contains the decomposed implementation. Include pjson_parser.h only in translation units that parse input.
| RapidJSON | pjson | Important difference |
|---|---|---|
Document / Value | pjson | One owning mutable value type. |
SetObject() / SetArray() | resetTo(jsonObject) / resetTo(jsonArray) | Explicit empty-container reset. |
SetInt64() / SetDouble() | assignment from int64_t / double | Only signed 64-bit integer and double numeric APIs. |
IsInt64() / IsDouble() | isInt() / isDouble() | Storage-kind checks. |
GetInt64() / GetDouble() | tryGet(out) | Exact-type, status-returning extraction. |
GetString() + GetStringLength() | tryGet(std::string&) or tryGet(StringView&) | StringView is borrowed. |
HasMember() / FindMember() | hasKey() / find(key) | Returns a borrowed child pointer or nullptr. |
operator[] for lookup | find / tryGet | pjson subscripting is builder-only and may mutate. |
| member iteration | keys() + find(key) | No public raw object container. |
| array iteration | size() + find(index) | No public raw array container. |
Document::Parse(...) | pJsonParser().parse(...) | Every DOM overload returns a pjson value; pass a pJsonParser::Error for status. |
Reader + handler | parseSax(...) / parseSaxStream(...) | SAX callbacks return bool to continue. |
Writer / PrettyWriter | write(out[, options]) | Configure SerializeOptions; inspect stream state. |
StringBuffer + Writer | toString([options]) | Returns the serialized string. |
Pointer::Get | findPointer(...) | Non-vivifying RFC 6901 lookup. |
| Pointer mutation | normal building or applyPatch(...[, options]) | RFC 6902 patching is atomic and bounded. |
| Merge Patch helper code | applyMergePatch(...[, options]) | Atomic RFC 7396 with the same limits. |
SchemaDocument + SchemaValidator | pJsonSchemaValidator v(schema[, options]); v.validate(value, ...) | Compile once into the standalone validator; subset by default, required Draft 2020-12 vocabularies via Options::draft2020(). |
operator[] is the pjson builder API. String access promotes the receiver to an object and creates a missing null child. Integer access promotes it to an array and grows it with null children. Use explicit final scalar types: 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.
Supported scalar mutation types are C strings, std::string, bool, int64_t, and double. Exact vector overloads exist for std::string, bool, int64_t, and double; there are no convenience int, float, or vectors of int, float, or C strings. Convert other values explicitly and build other arrays with indexed assignment:
Every DOM parse and parseStream overload returns a pjson by value, for both default and custom allocation. There is no smart pointer and no manual delete; the value owns its subtree and frees it on destruction. The terse overloads return JSON null on failure; pass a pJsonParser::Error to distinguish failure from a successfully parsed literal null.
An allocator passed to a pjson or pJsonParser constructor is borrowed and must outlive the complete tree. The returned value is bound to that allocator. Persistent nodes and wrapper objects use it; backing storage inside standard containers and parser scratch space use their normal standard allocators. Copying a pjson is deep. Assignment preserves the destination allocator; a cross-allocator move may allocate. swap() is O(1) only when canSwap() is true.
Reporting parse and SAX overloads reset pJsonParser::Error on entry. Success leaves ok == true, code == None, offset zero, line one, column one, and an empty message. Failure sets ok == false, a stable code, and reports the first problem. Offset is a zero-based byte position; line and byte-column are one-based. A reused error never intentionally retains diagnostics from the previous call.
pjson has no permissive parse mode and no RapidJSON-style syntax feature flags. All DOM and SAX entry points reject malformed UTF-8, invalid escapes, lone surrogates, raw string controls, non-lowercase literals, comments, trailing commas, invalid numbers, NaN, Infinity, and trailing non-whitespace data.
pJsonParser::Options controls only work budgets and duplicate names:
Zero means unlimited for node and input-byte budgets. A non-positive depth means an effective one-level limit. Duplicate policy can be RejectDuplicateKeys, KeepFirstDuplicate, or KeepLastDuplicate. This choice does not weaken RFC 8259 syntax or UTF-8 validation. For SAX, keep-first suppresses later duplicate value-subtree events; keep-last emits each occurrence because emitted callbacks cannot be retracted.
parseStream() buffers the complete input before DOM parsing. Use parseSaxStream() when input must be consumed incrementally.
Do not translate RapidJSON lookup to pjson operator[]: it creates structure and can replace the receiver's type. Use find for a borrowed node and tryGet for a typed value:
tryGet supports int64_t, uint64_t, double, bool, std::string, and StringView. It performs no coercion except integer-to-double widening and the exact signed/unsigned reads described in the numeric section, and it leaves the destination unchanged on failure. StringView is valid only while the owning node remains alive and unchanged.
Iterate without exposing container internals:
find(index) supports negative end-relative indexes, but normal forward loops should convert their checked size_t position to int. keys() returns a copy in private native storage order; forEachMember() uses that same unspecified order. Child pointers are borrowed and can be invalidated by mutation of the child or an ancestor.
pjson stores numbers as signed int64_t, unsigned uint64_t (for values above INT64_MAX), or double. Use isInt()/isUInt()/isInteger()/isDouble() to inspect storage and tryGet for extraction. Reading an integer into double is allowed but may lose precision beyond 2^53; reading a double into an integer is not an implicit tryGet conversion.
SetUint64, GetUint64, and IsUint64 map directly onto operator=(uint64_t), tryGet(uint64_t&), and isUInt(); the full uint64_t range round-trips exactly. Values above UINT64_MAX, and non-finite floats, are rejected by default (pJsonParser::Options::AllowLossyNumbers and SerializeOptions::NonFinitePolicy opt out). Use explicit int64_t, uint64_t, and double at all API boundaries rather than relying on C++ overload selection.
findPointer() performs non-vivifying RFC 6901 lookup. The empty string addresses the root. Other pointers start with /; array tokens are canonical unsigned decimal indices, and - is not a lookup index. Use escapePointerToken() for keys containing ~ or /.
For general pointer mutation, apply an RFC 6902 patch:
applyPatch() supports add, remove, replace, move, copy, and test, and commits atomically. Removing the empty path succeeds and resets the target document to JSON null. applyMergePatch() implements atomic RFC 7396: object patches merge recursively, null members remove object members, and non-object patches replace the complete target. Both APIs are noexcept and offer a reporting PatchError overload.
Both APIs also 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. Zero for maxOperations, maxClonedNodes, maxClonedBytes, or maxWork retains the corresponding hard ceiling. A limit failure reports PatchError::ResourceLimit and leaves the target unchanged. 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.
Derive from pJsonParser::SaxHandler and override the callbacks of interest. Integer events use int64_t; floating events use double. Returning false cancels the parse. Callback string and key references are borrowed only for the callback duration.
Use SerializeOptions instead of Writer flags, PrettyWriter setters, or a boolean pretty argument:
The defaults are compact layout, two-space indentation, space indentation, UTF-8 output, and a 64 MiB output limit. Zero explicitly makes maxOutputBytes unlimited. Object insertion order is not retained, and serialization order is unspecified. A stored non-finite double fails serialization by default (SerializeOptions::nonFinite selects RejectNonFinite, NonFiniteToNull, or NonFiniteToString). Finite doubles use pinned Ryu shortest-round-trip conversion followed by pjson's documented fixed/scientific spelling policy.
Invalid UTF-8 in any stored string or object key is a serialization failure, regardless of escapeNonAscii: 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. This matters for programmatically built values even though the parser itself accepts only valid UTF-8.
RapidJSON 1.1 validates compiled draft-04 schemas. pjson instead compiles a schema (itself a pjson) into a standalone ByteDance::pJsonSchemaValidator (declared in <pjson_schema.h>), then validates already-built values against it, with no SAX validation. The validator is a pure consumer of pjson's public API. The error overload appends pJsonSchemaValidator::Error values; clear a reused vector first. Error paths are RFC 6901 pointers, with "" denoting the root.
Default construction implements pjson's explicitly named subset dialect, not RapidJSON's Draft 4 behavior. 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/forms |
|---|---|
| Any value | type, 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, supported format names |
| Composition | allOf, anyOf, oneOf, not, if, then, else |
| Schema values | Boolean schemas |
Unknown or unsupported schema keywords are ignored in permissive subset mode and therefore are not constraints. Options::strict() rejects unsupported standard keywords; Options::draft2020() applies the implemented official dialect and per-resource vocabulary policy. The complete optional format-assertion vocabulary, arbitrary-precision numbers, and other drafts remain out of scope. URI resources, anchors, dynamic references, and unevaluated* are available through the modern subset option. External documents require an explicit resolver callback; pjson never performs network I/O.
minLength and maxLength count Unicode code points rather than UTF-8 bytes. pattern uses ECMAScript syntax and search semantics, but the default policy is intentionally narrower: pattern and subject sizes are capped and expressions outside a conservative safe subset fail validation. pJsonSchemaValidator::Options::trustedRegex() removes only regex restrictions and is appropriate only for trusted schemas and instances. Traversal, reference, work, and collected-error budgets remain active. Known formats are checked by default; unknown format names are ignored.
pjson value plus a pJsonParser::Error, and check error.ok before use.pJsonParser::Error; account for its reset-on-entry lifecycle.operator[] with find or tryGet; keep subscripting only for construction and intentional mutation.keys()/find(key) and size()/find(index).int64_t, uint64_t, and double, including SAX callbacks and vectors.SerializeOptions, and handle invalid-UTF-8 output failure.Options::draft2020() deliberately and add accepted/rejected tests for every relied-upon constraint.