Source:
JsonUtil.h
JsonUtil¶
Two layers, both header-only and both off the hot path, so bounded stack use is fine.
The flat helpers scan for a key over the subset we emit, and never descend into a nested object or an array. The recursive reader walks nested structure, which the persisted device and preset lists need.
Classes¶
| Name | Description |
|---|---|
JsonNode |
One value in the arena, its children linked by index so every node stays fixed-size with no per-node child array. |
JsonDoc |
The parsed document, owning the text buffer and the node arena: how both are sized and freed. |
JsonNode¶
src/core/util/JsonUtil.h:165One value in the arena, its children linked by index so every node stays fixed-size with no per-node child array.
Public Attributes¶
JsonType type = JsonType::Null
: which kind of value this node holds
const char * key = nullptr
: the member name when this node is an object member
const char * str = nullptr
long intValue = 0
: the numeric value, and a mirror for a boolean
int firstChild = -1
: the first child's index, or none
int next = -1
: the next sibling's index, or none
JsonDoc¶
src/core/util/JsonUtil.h:176The parsed document, owning the text buffer and the node arena: how both are sized and freed.
Non-copyable, owning two blocks, so a caller keeps it alive while walking.
Public Attributes¶
char * buf = nullptr
: the mutable copy of the input, which un-escaping rewrites in place
JsonNode * nodes = nullptr
: the node pool, grown as the parser allocates
int cap = 0
: allocated node slots
int count = 0
: used node slots
int root = -1
: the top value's index, or none until a parse succeeds
Public Methods¶
JsonDoc() = default
: An empty document, owning nothing until a parse fills it.
inline ~JsonDoc()
: Frees both blocks.
JsonDoc(const JsonDoc &) = delete
: Non-copyable: it owns two heap blocks.
inline bool valid() const
: Whether a parse succeeded.
inline const JsonNode * node(int i) const
: The node at an index, or nothing when it is out of range.
inline const JsonNode * rootNode() const
: The top value, or nothing until a parse succeeds.
inline bool ensureNode()
: Grow the pool when full, doubling so reallocations stay logarithmic; indices survive the move.
JsonParser¶
src/core/util/JsonUtil.h:213The cursor over the document's own buffer, which doubles as scratch: un-escaping rewrites string bytes in place.
Public Attributes¶
JsonDoc & doc
: the document being filled
char * p
: the read cursor into its buffer
bool ok = true
: cleared once the input is known to be malformed
Public Methods¶
inline explicit JsonParser(JsonDoc & d)
: A cursor at the start of the document's buffer.
inline void skipWs()
: Advance past any whitespace.
inline int alloc()
: Take the next node slot, or report failure when the pool cannot grow.
inline char * parseStringLiteral()
: Un-escape a string literal in place and terminate it, returning its first byte, or nothing when unterminated.
inline int parseValue(int depth)
: Parse any value at the cursor, recursing into an object or an array.
inline int parseObject(int depth)
inline int parseArray(int depth)
: Parse an array at the cursor, linking each element as a child.
More info¶
What the flat scan covers¶
Flat key and value pairs, with optional whitespace after the colon, and string, integer or boolean values. Many callers rely on that being a cheap search rather than a parse.
The recursive reader allocates per parse¶
The text arena and the node pool are taken from the heap, sized to the input and grown as needed, then freed with the document. Nodes are referenced by index, so growing the pool never dangles a pointer, and there is no node-count or length cap and no large standing buffer. Only the recursion is bounded, for the task stack.
Malformed or truncated input fails cleanly: the parse reports false, the accessors return safe defaults, and nothing reads out of bounds.
An absent key is not a zero¶
The flat integer and boolean readers cannot tell one from the other, so applying their result for an absent key clobbers a control's non-zero default. A load path asks whether the key is present first, or an older or partial save silently resets a control on every reboot.
Overflow needs both checks¶
The conversion reports out of range rather than saturating, because a caller that narrows the result would otherwise store a different valid number. Two checks are needed because they cover different targets. On a desktop a huge value lands inside the wide type, so only the range compare rejects it. On a device that compare is dead code, and the library's own saturation is the only signal. Testing one alone passes on the desktop and silently returns the maximum on the target this exists to protect.
Trailing text is deliberately allowed, these values being read out of a document where digits are followed by a comma or a brace, so only the leading characters decide.
The conversion is out of line, unlike its neighbors¶
As an inline its three checks were duplicated into every caller and cost 1712 bytes of flash on one chip, measured per symbol. One call instead is free in practice, every user being off the hot path.
The shared document is a function-local static¶
A list restore parses into one document rather than a stack local, since that document overflows a device task stack and boot-loops it. It lives in its own non-template function so the heavy object is one copy however many callback types instantiate the iteration, or each would multiply it. Sharing is safe because parsing is strictly serial: a boot-time load or a single control write, never concurrent.