Skip to content
Rob Dobson edited this page May 3, 2026 · 9 revisions

Raft JSON

Raft makes extensive use of JSON for configuration and message passing.

Raft provides a JSON parsing class called RaftJson and uses JSON for almost all configuration and many message payloads. RaftJson is an on-demand parser — it does not build an in-memory tree. Each accessor walks the source string from the start, which keeps net heap overhead at zero (one allocation for the document copy, nothing per access).

This page covers the basics that every Raft developer needs. For chaining, prefix scoping, NV persistence, change callbacks and JSON generation, see Advanced RaftJson. For how RaftJson is layered into the application's settings, see Configuration.


JSON paths

A JSON path is a series of one or more JSON keys separated by forward slashes (/). Array elements are accessed with [N].

For example, in the following JSON document:

{ "a": { "b": { "c": 1234, "d": [5, 6, 7, 8] } } }
Path Resolves to
"a/b/c" 1234
"a/b/d" the array [5,6,7,8]
"a/b/d[1]" 6
"" the entire document (root object)

The syntax is intentionally simpler than full XPath/JSONPath — there is no wildcard, no filter, no recursive descent. If the key contains a literal /, the path syntax cannot reach it; restructure the document.


Constructing RaftJson objects

RaftJson(const char* pJsonStr, bool makeCopy = true,
         const char* pJsonEnd = nullptr,
         RaftJsonIF* pChainedRaftJson = nullptr);

RaftJson(const String& jsonStr,        RaftJsonIF* pChainedRaftJson = nullptr);
RaftJson(const std::string& jsonStr,   RaftJsonIF* pChainedRaftJson = nullptr);

Common cases:

// 1. From a String received over the wire — copy is made
RaftJson cmd(req.getReqStr());

// 2. From a flash-resident constant — no copy needed
static const char* DEFAULTS = R"({"led":{"pin":2}})";
RaftJson defaults(DEFAULTS, /*makeCopy*/ false);

// 3. With a fallback chain (defaults are consulted if a key is missing)
RaftJson user(userJson, /*chained=*/ &defaults);

The third positional argument is an explicit end-pointer for buffers that are not NUL-terminated. The fourth positional argument attaches a chained RaftJsonIF whose document is consulted for any path not found in this one — see chaining.

If you only have a buffer and want to avoid wrapping it, use the static *Im accessors documented under Immediate static accessors.


Accessing simple values

String getString(const char* pDataPath, const char* defaultValue) const;
double getDouble(const char* pDataPath, double      defaultValue) const;
int    getInt   (const char* pDataPath, int         defaultValue) const;
long   getLong  (const char* pDataPath, long        defaultValue) const;
bool   getBool  (const char* pDataPath, bool        defaultValue) const;

Each takes an XPath-like data path and a default value. The default is returned when the key is missing, when the value cannot be coerced to the requested type, or when the key was found only in a chained document and that document was unreachable.

Numeric coercion: a JSON number can be read with getDouble, getInt or getLong interchangeably. Booleans true/false read as 1/0 via getInt, and the strings "true" / "false" are accepted by getBool.

{ "myValue": 123, "name": "weather", "enabled": true }
int    n  = config.getLong  ("myValue", -1);    // 123
String nm = config.getString("name",    "?");   // "weather"
bool   on = config.getBool  ("enabled", false); // true

Accessing arrays

As a list of strings

bool getArrayElems(const char* pDataPath, std::vector<String>& strList) const;

Returns false if the path does not resolve to an array. Otherwise each element is appended to strList as a string. For nested objects/arrays, the element strings are still JSON — feed them into a fresh RaftJson for further extraction:

std::vector<String> entries;
config.getArrayElems("DevMan/Devices", entries);
for (const String& entry : entries)
{
    RaftJson dev(entry);
    String name  = dev.getString("name", "");
    int    pin   = dev.getInt   ("pin",  -1);
    //
}

As a list of integers

bool getArrayInts(const char* pDataPath, std::vector<int>& intList) const;

Convenience for the common case of [1, 2, 3] — non-integer elements decode to 0.


Other helpful functions

contains and getKeys

bool contains(const char* pDataPath) const;
bool getKeys (const char* pDataPath, std::vector<String>& keysVector) const;

contains reports whether the path exists. getKeys populates a vector with the property names of the object at the given path (in document order). For chained documents getKeys returns the keys of the object as found in the first document in the chain that has it; it does not union keys across the chain.

getType

RaftJsonType getType(const char* pDataPath, int& arrayLen) const;

Returns the JSON type of the element at the path (and, for arrays, the element count via the out-parameter):

typedef enum {
    RAFT_JSON_UNDEFINED = 0,
    RAFT_JSON_OBJECT    = 1,
    RAFT_JSON_ARRAY     = 2,
    RAFT_JSON_STRING    = 3,
    RAFT_JSON_BOOLEAN   = 4,
    RAFT_JSON_NUMBER    = 5,
    RAFT_JSON_NULL      = 6
} RaftJsonType;

Use getType when you do not know in advance whether an optional setting is, say, a number (12) or an object ({ "value": 12, "unit": "C" }).


Generating JSON

RaftJson is primarily a reader, but a handful of static helpers help build small JSON fragments efficiently. See Advanced RaftJson — JSON generation helpers for:

  • RaftJson::getJSONFromNVPairs — stitch a vector<NameValuePair> into an object.
  • RaftJson::escapeString / unescapeString — handle \\, \", \n for arbitrary string content.
  • RaftJson::getHTMLQueryFromJSON — produce a ?a=…&b=… query string from a flat JSON object.

For ad-hoc JSON of fixed shape, manual concatenation through String is usually clearest:

String resp = "{\"ok\":true,\"value\":" + String(value) + "}";

For variable-length collections, accumulate into a String and bracket once at the end to avoid repeated reallocations.


Notes on size, performance and escaping

  • Document size. RaftJson does not impose a maximum document length, but every accessor scans from the start. For tens-of-kilobytes documents queried in tight loops, cache the values you need at startup rather than re-fetching each call.
  • Escaping. When the source comes from outside the device (REST body, MQTT message, file upload), assume strings are properly JSON-escaped — RaftJson does not require pre-validation, but getString does unescape \\, \" and \n on read. Other escape sequences (\t, \r, \u00xx etc.) are not unescaped.
  • NUL safety. All accessors operate on [pStart, pEnd) ranges and tolerate JSON without a trailing NUL. If you skip the copy (makeCopy = false), the buffer must remain valid for the lifetime of the RaftJson.
  • Thread safety. A RaftJson is safe to read concurrently after construction; mutators (setJsonDoc, setChainedRaftJson) are not.

What's beyond this page

  • Chained / cascading lookups so that defaults can be layered behind overrides.
  • RaftJsonPrefixed — wrap any RaftJsonIF with a fixed path prefix.
  • RaftJsonNVS — persist a JSON document to the device's NVS namespace and react to changes.
  • Change callbacks for runtime config updates.
  • JSON generation helpersgetJSONFromNVPairs, escaping, query-string conversion.
  • Static *Im accessors for buffer-only use without constructing a RaftJson.

All of the above are documented in Advanced RaftJson.


See also

Clone this wiki locally