In-place zero-copy JSON parsing. Null-terminates in the read buffer. Zero allocation for string values. Produces a FtNode tree ready for immediate use.
fwJson parses JSON in place: it modifies the input buffer by inserting null terminators at token boundaries, making string values directly addressable without any copy or allocation. The result is a FtNode tree allocated from the request fwAlloc arena, with string pointers pointing directly into the original read buffer.
Benchmark results → — fwJson beats RapidJSON by 15–97% across all tested files. See the full comparison against simdjson, yyjson, RapidJSON, and cJSON.
Beyond parsing, fwJson includes a full set of companion tools:
FtNode trees programmaticallyfwJson) — command-line JSON beautifier and validator for development and debuggingfwJson uses trace levels 40–79. See the full trace level table for all assignments across the fw-lib stack.
typedef enum KjValueType {
KjNone, KjString, KjInt, KjFloat, KjBoolean, KjNull, KjObject, KjArray
} KjValueType;
typedef struct FtNode {
char* name; // Field name ("" for array elements)
KjValueType type; // Value type
KjValue value; // Union: b (bool), i (int64), f (double),
// s (string), firstChildP
struct FtNode* next; // Next sibling
} FtNode;
Parsed JSON is represented as a linked list of FtNode siblings. Objects and arrays use value.firstChildP to point to their first child; children are linked via next.
FjParser* fjInit(char* buf, int bufSize, bool zero);
FjParser* fjBufferCreate(FjParser* kjP, FaAlloc* kaP);
fjInit creates a context using a user-supplied buffer. fjBufferCreate creates a context backed by a fwAlloc pool.
FtNode* kjParse(FjParser* kjP, char* json);
Parses JSON in-place, returning the root node or NULL on error. Error details are available in kjP->errorString, kjP->errorPos, and kjP->lineNo. The input buffer is modified during parsing (quotes replaced with null terminators), so pass a writable copy.
FtNode* kjObject(FjParser* kjP, const char* name);
FtNode* kjArray(FjParser* kjP, const char* name);
FtNode* kjString(FjParser* kjP, const char* name, const char* value);
FtNode* kjInteger(FjParser* kjP, const char* name, long long value);
FtNode* kjFloat(FjParser* kjP, const char* name, double value);
FtNode* fjBoolean(FjParser* kjP, const char* name, bool value);
FtNode* kjNull(FjParser* kjP, const char* name);
void kjChildAdd(FtNode* container, FtNode* child); // Append child
void kjChildAddSorted(FtNode* container, FtNode* child); // Add alphabetically
void kjChildPrepend(FtNode* container, FtNode* child); // Add at start
void kjChildRemove(FtNode* container, FtNode* child); // Remove child
void kjChildReplace(FtNode* container, FtNode* old, FtNode* new);
int kjChildCount(FtNode* containerP);
FtNode* kjLookup(FtNode* container, const char* name);
FtNode* kjNavigate(FtNode* treeP, const char** pathCompV, ...);
kjLookup finds a direct child by name. kjNavigate descends multiple levels using a path component array.
void kjRender(FjParser* kjP, FtNode* nodeP, char* buf, int bufLen);
void kjFastRender(FtNode* nodeP, char* buf); // Minimal, no config
int kjRenderSize(FjParser* kjP, FtNode* nodeP); // Calculate required buffer size
int kjFastRenderSize(FtNode* nodeP);
KjStatus kjConfig(FjParser* kjP, KjConfigItem item, char* value);
| Option | Description |
|---|---|
KjcMinimized | No whitespace output |
KjcIndentStep | Spaces per indent level |
KjcSorted / KjcSortedReverse | Sort object members alphabetically |
KjcArraysOnOneLine | Compact array rendering |
KjcObjectsOnOneLine | Compact object rendering |
KjcShortArraysOnOneLine | Compact only if under threshold |
KjcNumbersAsStrings | Preserve original number formatting |
const char* kjValueType(KjValueType vt); // "String", "Int", etc.
FtNode* kjClone(FjParser* kjP, FtNode* nodeP); // Deep copy
void kjFree(FtNode* kNodeP); // Free dynamically-allocated memory
void kjSort(FtNode* nodeP); // Sort object members in-place
#include "fwJson/fwJson.h"
char* json = strdup("{\"name\": \"Alice\", \"age\": 30}");
char buf[4096];
FjParser* kjP = fjInit(buf, sizeof(buf), false);
FtNode* root = kjParse(kjP, json);
if (root == NULL) {
fprintf(stderr, "Parse error: %s\n", kjP->errorString);
return 1;
}
// Iterate children
for (FtNode* n = root->value.firstChildP; n != NULL; n = n->next)
printf("%s: %s\n", n->name, kjValueType(n->type));
// Lookup specific field
FtNode* name = kjLookup(root, "name");
if (name && name->type == KjString)
printf("Name: %s\n", name->value.s);
free(json);
#include "fwJson/fwJson.h"
#include "fwJson/kjBuilder.h"
char buf[4096];
FjParser* kjP = fjInit(buf, sizeof(buf), false);
FtNode* root = kjObject(kjP, NULL);
kjChildAdd(root, kjString(kjP, "name", "Bob"));
kjChildAdd(root, kjInteger(kjP, "age", 25));
FtNode* tags = kjArray(kjP, "tags");
kjChildAdd(tags, kjString(kjP, NULL, "developer"));
kjChildAdd(tags, kjString(kjP, NULL, "musician"));
kjChildAdd(root, tags);
char out[1024];
kjConfig(kjP, KjcIndentStep, "2");
kjRender(kjP, root, out, sizeof(out));
// Output:
// {
// "name": "Bob",
// "age": 25,
// "tags": [
// "developer",
// "musician"
// ]
// }
FjParser struct is the first element placed in itFtNode objects; no malloc for normal documentsmalloc is called for 2 MB chunkskjFree() releases dynamically allocated memory; the initial buffer is caller-managed