THE FOUNDATION

fwJson — Zero-Copy JSON Parser

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.

What It Does

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:

Trace Levels

fwJson uses trace levels 40–79. See the full trace level table for all assignments across the fw-lib stack.

Core Types

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.

Functions

Initialization

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.

Parsing

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.

Tree Building

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);

Tree Manipulation

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);

Navigation & Lookup

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.

Rendering

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);

Rendering Configuration

KjStatus kjConfig(FjParser* kjP, KjConfigItem item, char* value);
OptionDescription
KjcMinimizedNo whitespace output
KjcIndentStepSpaces per indent level
KjcSorted / KjcSortedReverseSort object members alphabetically
KjcArraysOnOneLineCompact array rendering
KjcObjectsOnOneLineCompact object rendering
KjcShortArraysOnOneLineCompact only if under threshold
KjcNumbersAsStringsPreserve original number formatting

Utilities

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

Usage Examples

Parsing JSON

#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);

Building 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"
//   ]
// }

Memory Management

  1. User-supplied buffer — the application provides a pre-allocated buffer; the FjParser struct is the first element placed in it
  2. Node allocation — remaining buffer space is used as a pool for FtNode objects; no malloc for normal documents
  3. Overflow — when the buffer is exhausted, malloc is called for 2 MB chunks
  4. CleanupkjFree() releases dynamically allocated memory; the initial buffer is caller-managed