ply-json.h
JSON Support
The JSON module parses text into a mutable tree of Node objects and serializes those trees back to JSON. The parser
can enforce strict JSON or independently allow, warn about or reject several relaxed syntax extensions. All functions
and types in this module are defined in the ply::json namespace.
Parsing JSON
Create a Parser with the desired options, optionally install a diagnostic callback, then call parse(). Parsers are
intended for a single input document.
json::Parser::Options options = json::Parser::Options::makeStrict();
Owned<json::Parser> parser = json::Parser::create(options);
parser->setDiagnosticCallback([&](const json::Diagnostic& diagnostic) {
parser->printDiagnostic(diagnostic, getStdErr());
});
json::ParseResult result = parser->parse("settings.json", sourceText);
if (result.root) {
StringView name = result.root.get("name").text();
}
Parser::Options
Each syntax option uses one of the following handling policies:
Parser::Options::Policy |
Description |
|---|---|
Permissive |
Accept the extension without reporting a diagnostic. |
WarnAndContinue |
Report a warning, accept the extension and continue parsing. |
FatalError |
Report an error and stop parsing. |
Parser::Options is permissive by default. Set individual fields to select a policy for each extension, or use
makeStrict() to reject all of them.
| Option | Relaxed syntax controlled by the option |
|---|---|
u32 tabSize |
Sets the tab width used when converting byte offsets to diagnostic columns. Default is 4; a value of 0 is treated as 1. |
Policy trailingInput |
Allows non-whitespace input after parsing the root JSON expression. |
Policy unquotedKeys |
Allows object property names without quotes, as in {name: "Ada"}. |
Policy unquotedStrings |
Treats an unquoted word that is not true, false, null or a number as a string. |
Policy equalsSign |
Allows = instead of : after an object property name. |
Policy alternateSeparators |
Allows semicolons ; instead of commas , between object properties and array values. |
Policy looseSeparators |
Allows missing commas, including newline-only separators, and redundant separators. |
Policy singleQuotedStrings |
Allows strings and property names enclosed in single quotes. |
Policy nonUTF8Strings |
Allows invalid UTF-8 byte sequences in strings. |
Policy unescapedControlChars |
Allows unescaped control characters in strings. |
Policy arbitraryEscapeChars |
Allows nonstandard escape sequences by discarding the backslash and retaining the escaped character. |
Policy duplicateKeys |
Allows an object to contain the same property name more than once. |
bool duplicateKeysOverrideEarlier |
If true and a property name occurs more than once in an object, the later property overrides the first one. |
static Options Parser::Options::makeStrict()Returns options that set every
Policyfield toFatalError.
Numbers such as 1. are always rejected: a decimal point must be followed by a digit.
Malformed Unicode escapes and unpaired surrogates are always errors. In single-quoted strings,
\' escapes the closing quote independently of arbitraryEscapeChars.
Parser
static Owned<Parser> Parser::create(const Options& options)Creates a parser with a copy of
options. Retain the returnedOwned<Parser>until parsing and diagnostic handling are complete.void Parser::destroy()Destroys a parser created by
create().Owned<Parser>calls this automatically.void Parser::setDiagnosticCallback(Functor<void(const Diagnostic& diagnostic)>&& callback)Installs the callback used for warnings and errors. The
Diagnosticobject is only valid for the duration of the callback.void Parser::setEventHandler(EventHandler* handler)Installs an event handler, or disables events when passed
nullptr. The handler must continue to exist for the lifetime of the parser.void Parser::discardReturnValue()Call before
parse()to discard parsed values after their events are emitted instead of retaining a result tree. Ignore the returnedParseResultand useanyError()to check for failure. Events are unchanged. Property names are retained for duplicate detection only whenduplicateKeysOverrideEarlieris false. When it is true, duplicate-key diagnostics are skipped.ParseResult Parser::parse(StringView path, StringView srcView)Parses the root JSON expression from
srcView. A fatal error returns an invalid root.ParseResultcontains the following data members:
| Member | Description |
|---|---|
Node root |
The parsed root value, or an invalid node after a fatal error. |
TokenLocationMap tokenLocMap |
Maps source byte offsets to line and column locations. |
u32 numBytes |
Number of input bytes belonging to the accepted parse. When non-whitespace trailing input is accepted, this marks the end of the root value; otherwise, it includes consumed trailing whitespace. |
void Parser::begin()Starts a chunked-input session and clears any prior chunk buffer and error state. Configure callbacks and call
discardReturnValue()before parsing as usual.void Parser::write(StringView data)Appends the next contiguous input chunk. The parser copies the bytes, so the caller may release or reuse
dataafter the call. Events for complete values are delivered as parsing advances. A primitive token ending at the chunk boundary is held until a delimiter arrives orfinish()confirms the end of input.ParseResult Parser::finish(StringView path)Marks the input complete and validates the accumulated bytes as one document. Call exactly once after
begin(); writes afterfinish()and a secondfinish()are invalid.pathis accepted for consistency withparse(); diagnostics report source locations without using it.
Chunked input retains the source until finish() produces the result. Events already delivered cannot be retracted if a
later chunk makes the document invalid. Chunks must be supplied in source order without gaps or overlap. Use parse()
when the complete source is already available.
bool Parser::anyError() constReturns true if the parser reported a fatal error. Warnings do not set this flag.
void Parser::printDiagnostic(const Diagnostic& diagnostic, Stream& out) constWrites a diagnostic with its line, column, severity, message and nested parse context. Call this while the diagnostic callback is active.
Diagnostic
| Member | Description |
|---|---|
Diagnostic::Level errorLevel |
Either Diagnostic::Warning or Diagnostic::Error. |
u32 fileOfs |
Byte offset of the warning or error in the input. |
String message |
Human-readable diagnostic message. |
const Array<Diagnostic::Scope>& context |
Nested object, property, duplicate-property and array scopes active at the diagnostic location. |
The diagnostic callback receives errors for malformed JSON as well as warnings or errors selected by Options.
printDiagnostic() uses the parser's TokenLocationMap to turn these byte offsets into line and column numbers.
Node
Node stores one JSON value in its public var member and records its source byte offset in fileOfs. A parsed tree
uses the following alternatives:
| JSON value | Node alternative |
|---|---|
| Boolean | Node::Bool |
| Number | Node::Number containing a double |
| String | Node::Text |
| Array | Node::Array |
| Object | Node::Object |
| Null | Node::Null |
A default-constructed Node is invalid. Invalid nodes are used for parse failures and failed lookups; they are
distinct from a valid Node::Null value.
json::Node root{json::Node::Object{}};
root.set("name", json::Node::Text{"Ada"});
root.set("active", json::Node::Bool{true});
json::Node numbers{json::Node::Array{}};
numbers.array().append(json::Node::Number{1});
numbers.array().append(json::Node::Number{2});
root.set("numbers", std::move(numbers));
Construction and validity
Node::Node()Creates an invalid node.
Node::Node(const Bool& value, u32 fileOfs)
Node::Node(const Number& value, u32 fileOfs)
Node::Node(Text&& value, u32 fileOfs)
Node::Node(Array&& value, u32 fileOfs)
Node::Node(Object&& value, u32 fileOfs)
Node::Node(const Null& value, u32 fileOfs)Creates a node containing the selected alternative. Programmatically constructed nodes use offset 0 unless another offset is supplied.
bool Node::isValid() const
explicit Node::operator bool() constReturns whether the node contains a value. JSON
nullis valid and therefore returns true.
Booleans, numbers, strings and null
bool Node::isBool() constReturns whether the node contains
Node::Bool.bool Node::getBool() constReturns the stored Boolean, or false when the node has another type.
void Node::setBool(bool value)Replaces the node with a Boolean value.
bool Node::isNumber() constReturns whether the node contains
Node::Number.double Node::getNumber() constReturns the stored number, or 0 when the node has another type.
void Node::setNumber(double value)Replaces the node with a number.
bool Node::isText() constReturns whether the node contains
Node::Text.StringView Node::text() constReturns the stored string, or an empty view when the node has another type.
void Node::setText(String&& text)Replaces the node with a string, taking ownership of
text.bool Node::isNull() constReturns whether the node contains a valid JSON null value.
void Node::setNull()Replaces the node with JSON null.
Arrays
bool Node::isArray() constReturns whether the node contains
Node::Array.Node& Node::get(u32 index)
const Node& Node::get(u32 index) constReturns the array element at
index. A wrong node type or out-of-range index returns the shared invalid node.ArrayView<const Node> Node::arrayView() constReturns a read-only view of the array elements, or an empty view when the node has another type.
Array<Node>& Node::array()Returns the mutable array storage. The node must contain
Node::Array.
Objects
bool Node::isObject() constReturns whether the node contains
Node::Object.Node& Node::get(StringView key)
const Node& Node::get(StringView key) constReturns the property named
key. A wrong node type or missing property returns the shared invalid node.void Node::set(StringView key, Node&& value)Adds or replaces a property when the node contains an object. The function has no effect on another node type.
void Node::remove(StringView key)Removes a property when the node contains an object. The function has no effect on another node type.
Object& Node::object()
const Object& Node::object() constReturns the underlying object storage. The mutable overload requires an object node; the const overload returns an empty object for another node type.
EventHandler
A virtual interface for transmitting structured JSON data. The write() function uses this internally.
EventHandler defines several virtual member functions that are meant to be overridden by subclasses:
virtual void EventHandler::beginObject()
virtual void EventHandler::endObject()Enters and exits the scope of an object.
virtual void EventHandler::beginProperty(StringView name)
virtual void EventHandler::endProperty()Begins and ends an object property. The property's value is transmitted between these calls.
virtual void EventHandler::beginArray()
virtual void EventHandler::endArray()Enters and exits the scope of an array. Its elements are transmitted in order between these calls.
virtual void EventHandler::string(StringView str)Transmits a string value.
strcontains the decoded text, without surrounding quotes or JSON escape sequences, and is only valid for the duration of the call.virtual void EventHandler::bool_(bool value)Transmits a Boolean value:
trueorfalse.virtual void EventHandler::number(double value)Transmits a numeric value as a
double.virtual void EventHandler::null_()Transmits a JSON null value. When writing a tree, an invalid
Nodealso invokes this function.
Writing JSON
void write(Stream& out, const Node& node, const WriteOptions& options)Writes formatted JSON data to an output stream.
WriteOptionshas the following data members:Name Description bool includeWhitespaceWrites indented, multi-line JSON when true and compact JSON when false. Default is true. Owned<EventHandler> createWriter(Stream* out, const WriteOptions& options)Creates an
EventHandlerfor writing formatted JSON data toout. The stream must exist for the lifetime of theEventHandler.void write(EventHandler* handler, const Node& node)Writes formatted JSON data to an
EventHandler.String toString(const Node& node, const WriteOptions& options)Writes formatted JSON data to a string and returns it.
String compact = json::toString(root, {false});
Stream out = getStdOut();
json::write(out, root);