-
Notifications
You must be signed in to change notification settings - Fork 3
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.
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.
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.
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.
int n = config.getLong ("myValue", -1); // 123
String nm = config.getString("name", "?"); // "weather"
bool on = config.getBool ("enabled", false); // truebool 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);
// …
}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.
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.
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" }).
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 avector<NameValuePair>into an object. -
RaftJson::escapeString/unescapeString— handle\\,\",\nfor 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.
- 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
getStringdoes unescape\\,\"and\non read. Other escape sequences (\t,\r,\u00xxetc.) 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 theRaftJson. -
Thread safety. A
RaftJsonis safe to read concurrently after construction; mutators (setJsonDoc,setChainedRaftJson) are not.
- Chained / cascading lookups so that defaults can be layered behind overrides.
-
RaftJsonPrefixed— wrap anyRaftJsonIFwith 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 helpers —
getJSONFromNVPairs, escaping, query-string conversion. -
Static
*Imaccessors for buffer-only use without constructing aRaftJson.
All of the above are documented in Advanced RaftJson.
- Advanced RaftJson — chaining, prefixed views, NVS, change callbacks, generation helpers.
- Configuration — how RaftJson powers the three-layer SysMod configuration model.
- Source:
components/core/RaftJson/.
Getting Started
- Quick Start
- Architecture at a Glance
- Writing Your First SysMod
- Adding a Comms Channel
- Adding an I2C Device Type
- PlatformIO / Arduino
Scaffolding & Building
- Raft CLI
- SysTypes
- Top-Level SysType
- Build Process
- WebUI Build Pipeline
- File System
- Partitions & Flash
- Local Dev Libraries
- Library Developer Guide
Architecture
Built-in SysMods
- NetworkManager
- BLEManager
- WebServer
- MQTTManager
- SerialConsole
- CommandSerial
- CommandSocket
- CommandFile
- FileManager
- LogManager
- ESPOTAUpdate
- StatePublisher
- Remote Logging
- Data Source Registration
Comms & Protocols
- Stack Overview
- Comms Channels
- ProtocolExchange
- RICREST Protocol
- Real-Time Streams
- Adding REST Endpoints
- Built-in REST Endpoints
- File Download (OKTO)
- OTA Update Flow
Devices & Buses
- DeviceManager
- Device Manager REST API
- Device Factory & Classes
- Device Type Records
- Adding an I2C Device Type
- Device Data Publishing
- Data Logger
- I2C Bus
- I2C Device Scanning
- I2C ID & Polling
- MotorControl Overview
- MotorControl Config
- MotorControl Commands
Helpers
Reference
{ "myValue": 123, "name": "weather", "enabled": true }