Written from a line-by-line source review; every example output is from a real run.
Introduction
The json plugin lets you parse, navigate, and build JSON data. It parses a JSON text into an internal tree (Parse), from which you can read values by path, navigate nested objects and arrays, or build a structure from scratch and serialize it to JSON text. The plugin is built on the well-known cJSON library.
A handle-based model
The plugin is handle-based: Parse, NewObject and NewArray return an integer identifier (a handle) that refers to an internal JSON node. Every further operation takes this handle as its first argument. There are two kinds of handle: a ROOT handle (returned by Parse/NewObject/NewArray) owns the whole tree, and Free releases all of it; a SUB-handle (returned by GetObject/GetArray/ArrayGet) refers to a child node and is valid as long as the root lives. Freeing a sub-handle is harmless (it does not break up the tree). IMPORTANT: after the root is freed, its sub-handles also become invalid.
Path syntax
The read functions accept a dot-separated path to an object's members: “user.name”, “config.db.host”. The empty path (“”) means the handle's own node. Array indexing is NOT allowed in the path — for arrays use the GetArray + ArrayGet (then another Get* if needed) combination. A missing path segment (except for HasKey) raises a runtime error.
Type codes (GetType)
GetType returns an integer type code. The values are: 0 = invalid, 1 = false, 2 = true, 4 = null, 8 = number, 16 = string, 32 = array, 64 = object. So for a boolean, true and false get separate codes (2 and 1).
Loading the plugin
plugin "../plugins/print/PrintPlugin"; plugin "../plugins/json/JsonPlugin";
A typical flow — reading
i32 $h;
$h = json.Parse("{\"user\":{\"name\":\"Ada\"}}");
printf("%s\n", json.GetString($h, "user.name"));
json.Free($h);Error location on malformed JSON
When the parsed JSON is malformed, Parse (and ParseLen) does not merely report that parsing failed — it also tells you the exact location of the error: the line, the column, and the byte offset. Most embedded JSON handlers lack this, yet it greatly helps debugging. For example, for the following input (on line 3 the value after the colon is missing):
json.Parse("{\n \"a\": 1,\n \"b\": ,\n}");the runtime error message gives the precise location:
json.Parse: parse error at line 3, column 8 (byte 19).
What to know about every function (the basics)
json follows strict error handling (like csv/queue): a missing path, a type mismatch (for example GetString on a number), an invalid or already-freed handle, and an out-of-range array index raise a runtime error, and the script stops.
HasKey is the exception: for a missing key (or intermediate segment) it returns 0, it does not fail. So use HasKey before reading an uncertain key with Get*.
The type must match the function: GetString takes only a string, GetInt/GetDouble only a number, GetBool only a boolean — a mismatch is an error.
Set* sets an object's keys (overwriting if the key exists); Add* appends to an array's end. Set* expects an object handle, Add* an array handle.
A sub-handle (GetObject/GetArray/ArrayGet) is tied to the root's lifetime; do not use it after the root's Free.
The plugin loads the cJSON library with dlopen on first use; if libcjson is not installed, loading still succeeds, but the json.* calls fail.
How to read the signatures
handle is the JSON node's identifier, path is the dot-separated path. The type after the -> arrow is the return type. For example, json.GetInt(handle, path) -> int takes two arguments and returns an integer.
Parsing and freeing
Parsing JSON text into a tree, and freeing the tree.
json.Parse
json.Parse(text) -> int
Parses a JSON text into an internal tree and returns the root handle.
| Parameter | Type | Description |
|---|---|---|
| text | string | The JSON text to parse. |
The root handle (a positive integer). Invalid JSON or a full handle registry causes a runtime error; on malformed JSON the message gives the error location (line, column, byte).
i32 $h;
$h = json.Parse("{\"name\":\"Ada\"}");
printf("%s\n", json.GetString($h, "name"));
json.Free($h);Ada
The first step of processing any JSON data (an API response, a config file, a message). Free the returned handle at the end. On malformed JSON the error message tells you where parsing gave up — for example “parse error at line 3, column 8 (byte 19)” — which greatly helps debugging.
json.ParseLen
json.ParseLen(text, len) -> int
Like Parse, but it parses only the first len bytes of the text.
| Parameter | Type | Description |
|---|---|---|
| text | string | The JSON text to parse. |
| len | int | The number of bytes to process (cannot be negative). |
The root handle. A negative length, invalid JSON, or a full registry causes a runtime error.
i32 $h;
// parse only the first 8 bytes
$h = json.ParseLen("{\"n\":1} extra", 8);
printf("%d\n", json.GetInt($h, "n"));
json.Free($h);1
When the JSON is in a larger buffer and you want to process only a given-length part (for example the start of a network message), without a copy.
json.Free
json.Free(handle) -> int
Frees the handle. For a root, the whole tree; for a sub-handle, only the script-level identifier (the tree is untouched).
| Parameter | Type | Description |
|---|---|---|
| handle | int | The handle to free. |
Always 1.
i32 $h;
$h = json.Parse("{}");
printf("%d\n", json.Free($h));1
Always free the root handle when you are done, so the tree's memory is released. After freeing the root, do not use the sub-handles obtained from it.
Reading (values)
Reading a value by path. The type must match the function (apart from HasKey, every mismatch/absence is an error).
json.GetType
json.GetType(handle, path) -> int
Gives the type of the node at the path as an integer type code.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path (empty = the handle itself). |
The type code: 1=false, 2=true, 4=null, 8=number, 16=string, 32=array, 64=object.
i32 $h;
$h = json.Parse("{\"user\":{},\"tags\":[]}");
printf("%d\n", json.GetType($h, "user"));
printf("%d\n", json.GetType($h, "tags"));
json.Free($h);64 32
When you do not know a field's type in advance and want to pick the matching Get*. It is worth putting the type codes into named constants in the script for readability.
json.GetString
json.GetString(handle, path) -> string
Reads the string value at the path.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path. |
The string value. If the node is not a string (or the path is missing), a runtime error.
i32 $h;
$h = json.Parse("{\"user\":{\"name\":\"Ada\"}}");
printf("%s\n", json.GetString($h, "user.name"));
json.Free($h);Ada
For reading text fields. If you are not sure the key exists or is a string, check first with HasKey or GetType.
json.GetInt
json.GetInt(handle, path) -> int
Reads the number at the path as an integer.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path. |
The integer value. If the node is not a number (or the path is missing), a runtime error.
i32 $h;
$h = json.Parse("{\"user\":{\"age\":36}}");
printf("%d\n", json.GetInt($h, "user.age"));
json.Free($h);36
For reading integers (age, count, identifier). For a fractional value, GetDouble is the right one.
json.GetDouble
json.GetDouble(handle, path) -> double
Reads the number at the path as a floating-point (double) value.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path. |
The floating-point value. If the node is not a number (or the path is missing), a runtime error.
i32 $h;
$h = json.Parse("{\"ratio\":0.5}");
printf("%f\n", json.GetDouble($h, "ratio"));
json.Free($h);0.500000
For reading fractional numbers (a ratio, a price, a measurement). JSON does not distinguish integer from fractional, so pick the Get* that fits your purpose.
json.GetBool
json.GetBool(handle, path) -> int
Reads the boolean value at the path.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path. |
1 if true; 0 if false. If the node is not a boolean (or the path is missing), a runtime error.
i32 $h;
$h = json.Parse("{\"user\":{\"admin\":true}}");
printf("%d\n", json.GetBool($h, "user.admin"));
json.Free($h);1
For reading logical flags (enabled, active, admin). With GetType you can tell a missing value apart from an explicit false, if that matters.
json.HasKey
json.HasKey(handle, path) -> int
Tells whether the key at the path exists. The only read function that does not fail on a missing key.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path. |
1 if the key exists; 0 if not (or if an intermediate segment is missing).
i32 $h;
$h = json.Parse("{\"user\":{\"name\":\"Ada\"}}");
printf("%d\n", json.HasKey($h, "user.name"));
printf("%d\n", json.HasKey($h, "user.xxx"));
json.Free($h);1 0
For a safe check before Get* calls, to avoid a runtime error from a missing key on optional fields.
Navigation (objects and arrays)
Accessing nested objects and arrays through sub-handles.
json.GetObject
json.GetObject(handle, path) -> int
Returns a sub-handle to the nested object at the path.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path to the nested object. |
A sub-handle to the object. If the node is not an object (or is missing), a runtime error.
i32 $h;
i32 $sub;
$h = json.Parse("{\"user\":{\"name\":\"Ada\"}}");
$sub = json.GetObject($h, "user");
printf("%s\n", json.GetString($sub, "name"));
json.Free($h);Ada
For navigating a deeply nested structure: branch into the sub-object once, then read on from there with a shorter path. The sub-handle is valid for the root's lifetime.
json.GetArray
json.GetArray(handle, path) -> int
Returns a sub-handle to the array at the path.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The JSON node's handle. |
| path | string | The dot-separated path to the array. |
A sub-handle to the array. If the node is not an array (or is missing), a runtime error.
i32 $h;
i32 $arr;
$h = json.Parse("{\"tags\":[\"x\",\"y\",\"z\"]}");
$arr = json.GetArray($h, "tags");
printf("%d\n", json.ArrayLength($arr));
json.Free($h);3
For accessing arrays, since the path cannot index an array. Walk the returned handle with ArrayLength and ArrayGet.
json.ArrayLength
json.ArrayLength(arrayHandle) -> int
Gives the number of elements of an array handle.
| Parameter | Type | Description |
|---|---|---|
| arrayHandle | int | A handle to an array (returned by GetArray or ArrayGet). |
The number of elements. If the handle is not an array, a runtime error.
i32 $h;
i32 $arr;
$h = json.Parse("{\"tags\":[\"x\",\"y\",\"z\"]}");
$arr = json.GetArray($h, "tags");
printf("%d\n", json.ArrayLength($arr));
json.Free($h);3
For iterating over the array: from 0 to ArrayLength-1, with each element given by ArrayGet.
json.ArrayGet
json.ArrayGet(arrayHandle, index) -> int
Returns a sub-handle to the array element at the given index.
| Parameter | Type | Description |
|---|---|---|
| arrayHandle | int | A handle to an array. |
| index | int | The element's 0-based index. |
A sub-handle to the element. If the handle is not an array or the index is out of range, a runtime error.
i32 $h;
i32 $arr;
i32 $elem;
$h = json.Parse("{\"tags\":[\"x\",\"y\",\"z\"]}");
$arr = json.GetArray($h, "tags");
$elem = json.ArrayGet($arr, 1);
// empty path = the element itself
printf("%s\n", json.GetString($elem, ""));
json.Free($h);y
For accessing one element of the array. The element itself can be an object or array — on the returned sub-handle you continue with the matching Get*/GetObject/GetArray. Read a scalar element with an empty path (“”).
Building objects
Creating a new object and setting its keys. Set* expects an object handle and overwrites if the key already exists.
json.NewObject
json.NewObject() -> int
Creates a new, empty JSON object and returns the root handle.
This function takes no arguments.
The root handle of the new object.
i32 $o;
$o = json.NewObject();
json.SetString($o, "name", "Bob");
printf("%s\n", json.Serialize($o));
json.Free($o);{"name":"Bob"}The first step of building a JSON object (an API request body, a config, a response). At the end you turn it into text with Serialize and free it with Free.
json.SetString
json.SetString(obj, key, val) -> int
Sets a string-valued key on the object.
| Parameter | Type | Description |
|---|---|---|
| obj | int | An object handle. |
| key | string | The key name. |
| val | string | The text to set. |
Always 1.
i32 $o;
$o = json.NewObject();
json.SetString($o, "name", "Bob");
printf("%s\n", json.Serialize($o));
json.Free($o);{"name":"Bob"}For adding a text field. If the key already exists, it is overwritten — so you can also update an existing value with it.
json.SetInt
json.SetInt(obj, key, val) -> int
Sets an integer-valued key on the object.
| Parameter | Type | Description |
|---|---|---|
| obj | int | An object handle. |
| key | string | The key name. |
| val | int | The integer to set. |
Always 1.
i32 $o;
$o = json.NewObject();
json.SetInt($o, "age", 41);
printf("%s\n", json.Serialize($o));
json.Free($o);{"age":41}For adding an integer field (count, identifier, age).
json.SetDouble
json.SetDouble(obj, key, val) -> int
Sets a floating-point-valued key on the object.
| Parameter | Type | Description |
|---|---|---|
| obj | int | An object handle. |
| key | string | The key name. |
| val | double | The floating-point value to set. |
Always 1.
i32 $o;
$o = json.NewObject();
json.SetDouble($o, "score", 9.5);
printf("%s\n", json.Serialize($o));
json.Free($o);{"score":9.5}For adding a fractional field (a price, a ratio, a measurement).
json.SetBool
json.SetBool(obj, key, val) -> int
Sets a boolean-valued key on the object.
| Parameter | Type | Description |
|---|---|---|
| obj | int | An object handle. |
| key | string | The key name. |
| val | int | 0 = false, anything else = true. |
Always 1.
i32 $o;
$o = json.NewObject();
json.SetBool($o, "active", 1);
printf("%s\n", json.Serialize($o));
json.Free($o);{"active":true}For adding a logical flag. 0 maps to false, every other value to true.
json.SetNull
json.SetNull(obj, key) -> int
Sets a null-valued key on the object.
| Parameter | Type | Description |
|---|---|---|
| obj | int | An object handle. |
| key | string | The key name. |
Always 1.
i32 $o;
$o = json.NewObject();
json.SetNull($o, "note");
printf("%s\n", json.Serialize($o));
json.Free($o);{"note":null}For signaling an explicit null value (for example “the field exists but has no value”), distinguished from the key being absent entirely.
Building arrays
Creating a new array and appending elements. Add* expects an array handle and appends to the array's end.
json.NewArray
json.NewArray() -> int
Creates a new, empty JSON array and returns the root handle.
This function takes no arguments.
The root handle of the new array.
i32 $a;
$a = json.NewArray();
json.AddInt($a, 7);
printf("%s\n", json.Serialize($a));
json.Free($a);[7]
The first step of building a JSON array. You append elements with the Add* functions, then Serialize + Free at the end.
json.AddString
json.AddString(arr, val) -> int
Appends a string value to the end of the array.
| Parameter | Type | Description |
|---|---|---|
| arr | int | An array handle. |
| val | string | The text to append. |
Always 1.
i32 $a;
$a = json.NewArray();
json.AddString($a, "alpha");
printf("%s\n", json.Serialize($a));
json.Free($a);["alpha"]
For adding a text element to an array (a list of tags, names, lines).
json.AddInt
json.AddInt(arr, val) -> int
Appends an integer value to the end of the array.
| Parameter | Type | Description |
|---|---|---|
| arr | int | An array handle. |
| val | int | The integer to append. |
Always 1.
i32 $a;
$a = json.NewArray();
json.AddInt($a, 7);
printf("%s\n", json.Serialize($a));
json.Free($a);[7]
For adding an integer element (a list of identifiers, numeric values).
json.AddDouble
json.AddDouble(arr, val) -> int
Appends a floating-point value to the end of the array.
| Parameter | Type | Description |
|---|---|---|
| arr | int | An array handle. |
| val | double | The floating-point value to append. |
Always 1.
i32 $a;
$a = json.NewArray();
json.AddDouble($a, 1.5);
printf("%s\n", json.Serialize($a));
json.Free($a);[1.5]
For adding a fractional element (a measurement series, a list of ratios).
json.AddBool
json.AddBool(arr, val) -> int
Appends a boolean value to the end of the array.
| Parameter | Type | Description |
|---|---|---|
| arr | int | An array handle. |
| val | int | 0 = false, anything else = true. |
Always 1.
i32 $a;
$a = json.NewArray();
json.AddBool($a, 0);
printf("%s\n", json.Serialize($a));
json.Free($a);[false]
For adding a logical element. 0 maps to false, every other value to true.
Serializing
Turning the JSON tree into text in compact or indented form.
json.Serialize
json.Serialize(handle) -> string
Turns the JSON tree under the handle into compact (single-line, no spaces) JSON text.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The handle of the node to serialize. |
The compact JSON text.
i32 $o;
$o = json.NewObject();
json.SetString($o, "name", "Bob");
json.SetInt($o, "age", 41);
printf("%s\n", json.Serialize($o));
json.Free($o);{"name":"Bob","age":41}For transmitting or storing data where size matters (network, file). The compact form is the shortest.
json.SerializePretty
json.SerializePretty(handle) -> string
Turns the JSON tree under the handle into human-readable, indented (formatted) JSON text.
| Parameter | Type | Description |
|---|---|---|
| handle | int | The handle of the node to serialize. |
The indented JSON text.
i32 $o;
$o = json.NewObject();
json.SetString($o, "name", "Bob");
json.SetInt($o, "age", 41);
printf("%s\n", json.SerializePretty($o));
json.Free($o);{
"name": "Bob",
"age": 41
}For human-readable output: a log, debugging, a config file. Longer because of the indentation, but more legible than the compact form.
Practical notes
What the json plugin is good for
Reading API responses and config files, and getting values out by path.
Navigating nested structures (an object within an object, arrays) through sub-handles.
Building output JSON from scratch (a request body, a response, a message), then turning it into text.
Emitting data in compact (network) or indented (readable) form.
Reading safely
Since the Get* functions raise a runtime error both for a missing key and for a type mismatch, check optional or uncertain fields first with HasKey (existence) and GetType (type). You can read required, well-known fields directly — there an error precisely signals bad input.
Handle lifetime
The root handle (Parse/NewObject/NewArray) is freed by Free, together with the whole tree. The sub-handles (GetObject/GetArray/ArrayGet) are tied to the root's lifetime: after the root is freed they can no longer be used. In a long-running script, close every root with Free so the handle registry does not run out.
Common pitfalls
An array in the path: the path can only navigate object members; an array needs GetArray + ArrayGet.
Type mismatch: GetString on a number (or vice versa) is an error — use GetType first if the type is uncertain.
Stale handle: after the root's Free, the sub-handles obtained from it are invalid.
Missing libcjson: if cJSON is not installed on the system, the json.* calls fail (while loading still succeeds).
Error handling
A wrong argument count or type, a missing path, a type mismatch, an invalid or stale handle, an out-of-range array index, and bad JSON input are reported by the runtime as a runtime error, and the script stops. On malformed JSON the Parse and ParseLen message gives the exact error location (line, column, and byte offset), which makes it easier to find the broken part in the input. HasKey is the only read function that signals a missing key not as an error but with a 0 return value — so use it to check optional fields.
The sqlite database plugin
A thin wrapper over the SQLite C-API — complete function reference