ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
json.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file json.h
6 * @brief Layer 6 (Presentation) - zero-heap JSON: a bounded writer and top-level reader.
7 *
8 * A deliberately small JSON helper for the common IoT shapes (a flat-ish object
9 * of strings / numbers / booleans, with bounded nesting). It allocates nothing:
10 * the writer formats into a caller-provided buffer, and the reader scans a
11 * NUL-terminated body in place. ArduinoJson remains the option when you need a
12 * full DOM - it heap-allocates, which this library avoids.
13 *
14 * ## Writing
15 * @code
16 * char buf[128];
17 * protocore_json_writer w;
18 * Json.init(&w, buf, sizeof(buf));
19 * Json.begin_object(&w);
20 * Json.kv_str(&w, "status", "ok");
21 * Json.kv_int(&w, "count", 3);
22 * Json.key(&w, "items"); Json.begin_array(&w);
23 * Json.put_str(&w, "a"); Json.put_str(&w, "b");
24 * Json.end_array(&w);
25 * Json.end_object(&w);
26 * if (protocore_json_ok(&w)) server.send(slot, 200, "application/json", protocore_json_c_str(&w));
27 * // -> {"status":"ok","count":3,"items":["a","b"]}
28 * @endcode
29 *
30 * ## Reading (top-level keys of an object body)
31 * @code
32 * char ssid[33];
33 * if (Json.get_str(req->body, "ssid", ssid, sizeof(ssid))) { ... }
34 * long port;
35 * if (Json.get_int(req->body, "port", &port)) { ... }
36 * @endcode
37 */
38
39#ifndef PROTOCORE_JSON_H
40#define PROTOCORE_JSON_H
41
42#include "protocore_config.h" // the entry point: protocore_types.h for the widths
43
44#if PROTOCORE_ENABLE_JSON
45
47
48// This module holds nothing between calls, so it carves no borrow and states none. An entry
49// takes one all the same, and never reads it, so every namespace in the tree is invoked the
50// same way.
51
52/**
53 * @brief Builds a JSON document into a fixed caller buffer, no heap.
54 *
55 * Commas, key quoting, and string escaping are emitted automatically. On buffer
56 * overflow or a structural error (nesting past JSON_MAX_DEPTH), writing stops
57 * and protocore_json_ok() returns false; protocore_json_c_str() still yields a NUL-terminated
58 * (truncated) string so a partial result never runs off the end.
59 *
60 * The caller owns the struct as well as the buffer, so the whole writer is one
61 * local with no allocation behind it. Its fields are the writer's business:
62 * reach them through the calls below, never directly.
63 */
64typedef struct
65{
66 char *buf;
67 size_t cap;
68 size_t len;
69 proto_bool ok;
70 proto_bool after_key; // next value follows a key(): suppress its comma
71 uint8_t depth; // open containers
72 proto_bool need_comma[JSON_MAX_DEPTH]; // per-level: has a prior item been emitted?
73} protocore_json_writer;
74
75/** @brief False after any overflow / structural error. */
76PROTOCORE_INLINE proto_bool protocore_json_ok(const protocore_json_writer *w)
77{
78 return w->ok;
79}
80
81/** @brief Bytes written so far (excludes the NUL). */
82PROTOCORE_INLINE size_t protocore_json_length(const protocore_json_writer *w)
83{
84 return w->len;
85}
86
87/** @brief NUL-terminated output (truncated if !protocore_json_ok()). */
88PROTOCORE_INLINE const char *protocore_json_c_str(const protocore_json_writer *w)
89{
90 return w->buf;
91}
92
93/** @brief What init takes: w, buf, cap. */
94typedef struct
95{
96 protocore_json_writer *w;
97 char *buf;
98 size_t cap;
99} JsonInitArgs;
100
101/** @brief What begin_object takes: w. */
102typedef struct
103{
104 protocore_json_writer *w;
105} JsonBeginObjectArgs;
106
107/** @brief What end_object takes: w. */
108typedef struct
109{
110 protocore_json_writer *w;
111} JsonEndObjectArgs;
112
113/** @brief What begin_array takes: w. */
114typedef struct
115{
116 protocore_json_writer *w;
117} JsonBeginArrayArgs;
118
119/** @brief What end_array takes: w. */
120typedef struct
121{
122 protocore_json_writer *w;
123} JsonEndArrayArgs;
124
125/** @brief What key takes: w, k. */
126typedef struct
127{
128 protocore_json_writer *w;
129 const char *k;
130} JsonKeyArgs;
131
132/** @brief What put_str takes: w, v. */
133typedef struct
134{
135 protocore_json_writer *w;
136 const char *v;
137} JsonPutStrArgs;
138
139/** @brief What put_int takes: w, v. */
140typedef struct
141{
142 protocore_json_writer *w;
143 long v;
144} JsonPutIntArgs;
145
146/** @brief What put_uint takes: w, v. */
147typedef struct
148{
149 protocore_json_writer *w;
150 unsigned long v;
151} JsonPutUintArgs;
152
153/** @brief What put_bool takes: w, v. */
154typedef struct
155{
156 protocore_json_writer *w;
157 proto_bool v;
158} JsonPutBoolArgs;
159
160/** @brief What put_null takes: w. */
161typedef struct
162{
163 protocore_json_writer *w;
164} JsonPutNullArgs;
165
166/** @brief What put_raw takes: w, literal. */
167typedef struct
168{
169 protocore_json_writer *w;
170 const char *literal;
171} JsonPutRawArgs;
172
173/** @brief What kv_str takes: w, k, v. */
174typedef struct
175{
176 protocore_json_writer *w;
177 const char *k;
178 const char *v;
179} JsonKvStrArgs;
180
181/** @brief What kv_int takes: w, k, v. */
182typedef struct
183{
184 protocore_json_writer *w;
185 const char *k;
186 long v;
187} JsonKvIntArgs;
188
189/** @brief What kv_uint takes: w, k, v. */
190typedef struct
191{
192 protocore_json_writer *w;
193 const char *k;
194 unsigned long v;
195} JsonKvUintArgs;
196
197/** @brief What kv_bool takes: w, k, v. */
198typedef struct
199{
200 protocore_json_writer *w;
201 const char *k;
202 proto_bool v;
203} JsonKvBoolArgs;
204
205/** @brief What kv_null takes: w, k. */
206typedef struct
207{
208 protocore_json_writer *w;
209 const char *k;
210} JsonKvNullArgs;
211
212/** @brief What kv_raw takes: w, k, literal. */
213typedef struct
214{
215 protocore_json_writer *w;
216 const char *k;
217 const char *literal;
218} JsonKvRawArgs;
219
220/** @brief What get_str takes: json, key, out, out_cap. */
221typedef struct
222{
223 const char *json;
224 const char *key;
225 char *out;
226 size_t out_cap;
227} JsonGetStrArgs;
228
229/** @brief What get_int takes: json, key, out. */
230typedef struct
231{
232 const char *json;
233 const char *key;
234 long *out;
235} JsonGetIntArgs;
236
237/** @brief What get_bool takes: json, key, out. */
238typedef struct
239{
240 const char *json;
241 const char *key;
242 proto_bool *out;
243} JsonGetBoolArgs;
244
245/**
246 * @brief Layer 6 (Presentation) - zero-heap JSON: a bounded writer and top-level reader.
247 *
248 * A caller sets the members a call takes, invokes it through ::Json with the bytes it runs
249 * out of, and reads the outcome off the same handle.
250 *
251 * Json.init_args.w = ...;
252 * Json.init_args.buf = ...;
253 * Json.init_args.cap = ...;
254 * Json.init(work);
255 *
256 * @var JsonNs::init_args what init takes: w, buf, cap
257 * @var JsonNs::begin_object_args what begin_object takes: w
258 * @var JsonNs::end_object_args what end_object takes: w
259 * @var JsonNs::begin_array_args what begin_array takes: w
260 * @var JsonNs::end_array_args what end_array takes: w
261 * @var JsonNs::key_args what key takes: w, k
262 * @var JsonNs::put_str_args what put_str takes: w, v
263 * @var JsonNs::put_int_args what put_int takes: w, v
264 * @var JsonNs::put_uint_args what put_uint takes: w, v
265 * @var JsonNs::put_bool_args what put_bool takes: w, v
266 * @var JsonNs::put_null_args what put_null takes: w
267 * @var JsonNs::put_raw_args what put_raw takes: w, literal
268 * @var JsonNs::kv_str_args what kv_str takes: w, k, v
269 * @var JsonNs::kv_int_args what kv_int takes: w, k, v
270 * @var JsonNs::kv_uint_args what kv_uint takes: w, k, v
271 * @var JsonNs::kv_bool_args what kv_bool takes: w, k, v
272 * @var JsonNs::kv_null_args what kv_null takes: w, k
273 * @var JsonNs::kv_raw_args what kv_raw takes: w, k, literal
274 * @var JsonNs::get_str_args what get_str takes: json, key, out, out_cap
275 * @var JsonNs::get_int_args what get_int takes: json, key, out
276 * @var JsonNs::get_bool_args what get_bool takes: json, key, out
277 * @var JsonNs::ok a call's true/false outcome
278 * @var JsonNs::init bind the writer to a caller buffer, capacity including the NUL
279 * @var JsonNs::begin_object open `{`, as a value or an element where that applies
280 * @var JsonNs::end_object close `}`
281 * @var JsonNs::begin_array open `[`
282 * @var JsonNs::end_array close `]`
283 * @var JsonNs::key an object member name (`"k":`); one value call follows it
284 * @var JsonNs::put_str a quoted, escaped string value
285 * @var JsonNs::put_int a signed integer value
286 * @var JsonNs::put_uint an unsigned integer value
287 * @var JsonNs::put_bool `true` or `false`
288 * @var JsonNs::put_null `null`
289 * @var JsonNs::put_raw a pre-formatted literal, verbatim
290 * @var JsonNs::kv_str `"k":"v"`, escaped
291 * @var JsonNs::kv_int `"k":<int>`
292 * @var JsonNs::kv_uint `"k":<uint>`
293 * @var JsonNs::kv_bool `"k":true|false`
294 * @var JsonNs::kv_null `"k":null`
295 * @var JsonNs::kv_raw `"k":<literal>`
296 * @var JsonNs::get_str a top-level string member, unescaped into out and bounded by
297 * @var JsonNs::get_int a top-level member that parses as an integer
298 * @var JsonNs::get_bool a top-level member that is a JSON boolean
299 *
300 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
301 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
302 * a caller drives every namespace the same way.
303 */
304typedef struct
305{
306 JsonInitArgs init_args;
307 JsonBeginObjectArgs begin_object_args;
308 JsonEndObjectArgs end_object_args;
309 JsonBeginArrayArgs begin_array_args;
310 JsonEndArrayArgs end_array_args;
311 JsonKeyArgs key_args;
312 JsonPutStrArgs put_str_args;
313 JsonPutIntArgs put_int_args;
314 JsonPutUintArgs put_uint_args;
315 JsonPutBoolArgs put_bool_args;
316 JsonPutNullArgs put_null_args;
317 JsonPutRawArgs put_raw_args;
318 JsonKvStrArgs kv_str_args;
319 JsonKvIntArgs kv_int_args;
320 JsonKvUintArgs kv_uint_args;
321 JsonKvBoolArgs kv_bool_args;
322 JsonKvNullArgs kv_null_args;
323 JsonKvRawArgs kv_raw_args;
324 JsonGetStrArgs get_str_args;
325 JsonGetIntArgs get_int_args;
326 JsonGetBoolArgs get_bool_args;
327 proto_bool ok;
328} JsonVars;
329
330/** @brief The operands and the outcome. */
331extern JsonVars JsonV;
332
333/** @brief The entries. */
334typedef struct
335{
336 void (*const init)(uint8_t *work);
337 void (*const begin_object)(uint8_t *work);
338 void (*const end_object)(uint8_t *work);
339 void (*const begin_array)(uint8_t *work);
340 void (*const end_array)(uint8_t *work);
341 void (*const key)(uint8_t *work);
342 void (*const put_str)(uint8_t *work);
343 void (*const put_int)(uint8_t *work);
344 void (*const put_uint)(uint8_t *work);
345 void (*const put_bool)(uint8_t *work);
346 void (*const put_null)(uint8_t *work);
347 void (*const put_raw)(uint8_t *work);
348 void (*const kv_str)(uint8_t *work);
349 void (*const kv_int)(uint8_t *work);
350 void (*const kv_uint)(uint8_t *work);
351 void (*const kv_bool)(uint8_t *work);
352 void (*const kv_null)(uint8_t *work);
353 void (*const kv_raw)(uint8_t *work);
354 void (*const get_str)(uint8_t *work);
355 void (*const get_int)(uint8_t *work);
356 void (*const get_bool)(uint8_t *work);
357} JsonNs;
358
359// What the table binds, defined once in the .c and taking one parameter each: everything
360// else an entry needs is an operand in JsonV or a region of the borrow at a fixed offset.
361void protocore_json_init(uint8_t *work);
362void protocore_json_begin_object(uint8_t *work);
363void protocore_json_end_object(uint8_t *work);
364void protocore_json_begin_array(uint8_t *work);
365void protocore_json_end_array(uint8_t *work);
366void protocore_json_key(uint8_t *work);
367void protocore_json_put_str(uint8_t *work);
368void protocore_json_put_int(uint8_t *work);
369void protocore_json_put_uint(uint8_t *work);
370void protocore_json_put_bool(uint8_t *work);
371void protocore_json_put_null(uint8_t *work);
372void protocore_json_put_raw(uint8_t *work);
373void protocore_json_kv_str(uint8_t *work);
374void protocore_json_kv_int(uint8_t *work);
375void protocore_json_kv_uint(uint8_t *work);
376void protocore_json_kv_bool(uint8_t *work);
377void protocore_json_kv_null(uint8_t *work);
378void protocore_json_kv_raw(uint8_t *work);
379void protocore_json_get_str(uint8_t *work);
380void protocore_json_get_int(uint8_t *work);
381void protocore_json_get_bool(uint8_t *work);
382
383// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
384// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
385// `Json.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
386// leaves the call indirect and the symbol live at every level, -O2 -flto included.
387static const JsonNs Json __attribute__((unused)) = {
388 .init = protocore_json_init,
389 .begin_object = protocore_json_begin_object,
390 .end_object = protocore_json_end_object,
391 .begin_array = protocore_json_begin_array,
392 .end_array = protocore_json_end_array,
393 .key = protocore_json_key,
394 .put_str = protocore_json_put_str,
395 .put_int = protocore_json_put_int,
396 .put_uint = protocore_json_put_uint,
397 .put_bool = protocore_json_put_bool,
398 .put_null = protocore_json_put_null,
399 .put_raw = protocore_json_put_raw,
400 .kv_str = protocore_json_kv_str,
401 .kv_int = protocore_json_kv_int,
402 .kv_uint = protocore_json_kv_uint,
403 .kv_bool = protocore_json_kv_bool,
404 .kv_null = protocore_json_kv_null,
405 .kv_raw = protocore_json_kv_raw,
406 .get_str = protocore_json_get_str,
407 .get_int = protocore_json_get_int,
408 .get_bool = protocore_json_get_bool,
409};
410
412
413#endif // PROTOCORE_ENABLE_JSON
414
415#endif // PROTOCORE_JSON_H
#define JSON_MAX_DEPTH
Maximum object/array nesting depth for the JsonWriter (see json.h).
#define PROTOCORE_INLINE
Linkage for a leaf primitive whose body is cheaper than the call that reaches it.
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97