JSON Module
GX includes a JSON module powered by yyjson,
one of the fastest JSON libraries in C. It supports both reading (immutable API)
and building/modifying JSON (mutable API), with pretty-printing, JSON Pointer,
and file I/O.
Prerequisites
The module ships with yyjson bundled — no external dependencies needed.
The C source (yyjson.c) is compiled automatically via @cfile.
Usage
import json
Build with -I modules:
gx myapp.gx -I modules -o myapp.exe
Quick Example
import json
fn main() {
// Parse JSON
var text = "{\"name\": \"Alice\", \"age\": 30}"
var doc = json.gx_json_parse(text.cstr, text.len, 0)
defer json.gx_json_free(doc)
var r = json.gx_json_root(doc)
var name = json.gx_json_get_str(json.gx_json_obj_get(r, "name"))
var age = json.gx_json_get_int(json.gx_json_obj_get(r, "age"))
print("Name: {name}, Age: {age}\n")
// Build JSON
var mdoc = json.gx_json_new_doc()
defer json.gx_json_mut_free(mdoc)
var obj = json.gx_json_new_obj(mdoc)
json.gx_json_set_root(mdoc, obj)
json.gx_json_obj_add_str(mdoc, obj, "language", "GX")
json.gx_json_obj_add_int(mdoc, obj, "version", 2)
var result = json.gx_json_mut_write(mdoc, json.WRITE_PRETTY)
print("{result}\n")
}
Constants
Read Flags
Pass 0 for strict RFC 8259 parsing. Combine flags with |:
| Constant | Value | Description |
|---|
READ_NOFLAG | 0 | Default strict parsing |
READ_INSITU | 1 | Modify input buffer (faster, reuses memory) |
READ_STOP_WHEN_DONE | 2 | Stop after first complete JSON value |
READ_ALLOW_TRAILING_COMMAS | 4 | Accept [1, 2,] |
READ_ALLOW_COMMENTS | 8 | Accept // line and /* block */ comments |
READ_ALLOW_INF_AND_NAN | 16 | Accept Infinity and NaN |
READ_ALLOW_INVALID_UNICODE | 32 | Don’t reject invalid UTF-8 |
Write Flags
Pass 0 for minified output:
| Constant | Value | Description |
|---|
WRITE_NOFLAG | 0 | Minified (default) |
WRITE_PRETTY | 1 | Pretty-print with 4-space indent |
WRITE_PRETTY_TWO_SPACES | 64 | Pretty-print with 2-space indent |
WRITE_ESCAPE_UNICODE | 2 | Escape non-ASCII as \uXXXX |
WRITE_ESCAPE_SLASHES | 4 | Escape / as \/ |
WRITE_ALLOW_INF_AND_NAN | 8 | Write Infinity/NaN literals |
WRITE_INF_AND_NAN_AS_NULL | 16 | Convert Infinity/NaN to null |
WRITE_ALLOW_INVALID_UNICODE | 32 | Don’t reject invalid UTF-8 |
WRITE_NEWLINE_AT_END | 128 | Trailing newline |
Type Constants
Returned by gx_json_type() and gx_json_mut_type():
| Constant | Value | Description |
|---|
TYPE_NONE | 0 | Invalid/missing |
TYPE_RAW | 1 | Raw JSON string |
TYPE_NULL | 2 | null |
TYPE_BOOL | 3 | true or false |
TYPE_NUM | 4 | Number (integer or float) |
TYPE_STR | 5 | String |
TYPE_ARR | 6 | Array |
TYPE_OBJ | 7 | Object |
Immutable API — Reading JSON
The immutable API parses JSON into a read-only document. Use this when you
only need to query values.
Parsing
| Function | Signature | Description |
|---|
gx_json_parse | (cstr, c_size, c_uint) → *void | Parse JSON string with length and flags |
gx_json_parse_file | (cstr, c_uint) → *void | Parse JSON from file path |
gx_json_root | (*void) → *void | Get root value of parsed document |
gx_json_free | (*void) | Free parsed document |
gx_json_val_count | (*void) → c_size | Number of values in document |
var doc = json.gx_json_parse(text.cstr, text.len, 0)
if (doc == 0) { print("Parse error\n"); return }
defer json.gx_json_free(doc)
var root = json.gx_json_root(doc)
For lenient parsing (comments, trailing commas):
var flags = json.READ_ALLOW_COMMENTS | json.READ_ALLOW_TRAILING_COMMAS
var doc = json.gx_json_parse(text.cstr, text.len, flags)
Type Inspection
| Function | Signature | Description |
|---|
gx_json_type | (*void) → u8 | Get type constant |
gx_json_type_desc | (*void) → cstr | Human-readable type name |
gx_json_is_null | (*void) → bool | Check if null |
gx_json_is_bool | (*void) → bool | Check if boolean |
gx_json_is_num | (*void) → bool | Check if number |
gx_json_is_int | (*void) → bool | Check if integer |
gx_json_is_real | (*void) → bool | Check if floating-point |
gx_json_is_str | (*void) → bool | Check if string |
gx_json_is_arr | (*void) → bool | Check if array |
gx_json_is_obj | (*void) → bool | Check if object |
gx_json_is_true | (*void) → bool | Check if true |
gx_json_is_false | (*void) → bool | Check if false |
| Function | Signature | Description |
|---|
gx_json_get_bool | (*void) → bool | Get boolean value |
gx_json_get_int | (*void) → i64 | Get signed integer |
gx_json_get_uint | (*void) → u64 | Get unsigned integer |
gx_json_get_float | (*void) → f64 | Get floating-point number |
gx_json_get_num | (*void) → f64 | Get any number as f64 |
gx_json_get_str | (*void) → cstr | Get string value |
gx_json_get_len | (*void) → c_size | Get length (string/array/object) |
gx_json_equals_str | (*void, cstr) → bool | Compare value to string |
Object Navigation
| Function | Signature | Description |
|---|
gx_json_obj_get | (*void, cstr) → *void | Get value by key |
gx_json_obj_size | (*void) → c_size | Number of key-value pairs |
var val = json.gx_json_obj_get(root, "name")
if (val != 0) {
var name = json.gx_json_get_str(val)
print("name = {name}\n")
}
Array Navigation
| Function | Signature | Description |
|---|
gx_json_arr_get | (*void, c_size) → *void | Get element by index |
gx_json_arr_size | (*void) → c_size | Number of elements |
gx_json_arr_first | (*void) → *void | First element |
gx_json_arr_last | (*void) → *void | Last element |
var arr = json.gx_json_obj_get(root, "items")
var first = json.gx_json_arr_get(arr, 0)
var second = json.gx_json_arr_get(arr, 1)
Serialization
| Function | Signature | Description |
|---|
gx_json_write | (*void, c_uint) → cstr | Serialize document to JSON string |
gx_json_write_val | (*void, c_uint) → cstr | Serialize single value |
gx_json_free_str | (cstr) | Free string returned by write functions |
var pretty = json.gx_json_write(doc, json.WRITE_PRETTY)
print("{pretty}\n")
JSON Pointer (RFC 6901)
Navigate nested structures with a path string:
| Function | Signature | Description |
|---|
gx_json_ptr_get | (*void, cstr) → *void | Get value by JSON Pointer path |
// For {"data": {"users": [{"name": "Alice"}]}}
var name_val = json.gx_json_ptr_get(doc, "/data/users/0/name")
var name = json.gx_json_get_str(name_val)
Mutable API — Building JSON
The mutable API creates documents from scratch or modifies parsed JSON.
Document Lifecycle
| Function | Signature | Description |
|---|
gx_json_new_doc | () → *void | Create new mutable document |
gx_json_mut_free | (*void) | Free mutable document |
gx_json_set_root | (*void, *void) | Set document root value |
gx_json_mut_root | (*void) → *void | Get document root value |
var doc = json.gx_json_new_doc()
defer json.gx_json_mut_free(doc)
var root = json.gx_json_new_obj(doc)
json.gx_json_set_root(doc, root)
Creating Values
| Function | Signature | Description |
|---|
gx_json_new_null | (*void) → *void | Create null |
gx_json_new_bool | (*void, bool) → *void | Create boolean |
gx_json_new_int | (*void, i64) → *void | Create integer |
gx_json_new_uint | (*void, u64) → *void | Create unsigned integer |
gx_json_new_float | (*void, f64) → *void | Create float |
gx_json_new_str | (*void, cstr) → *void | Create string (copies) |
gx_json_new_arr | (*void) → *void | Create empty array |
gx_json_new_obj | (*void) → *void | Create empty object |
Object Mutation
Convenience functions that add a key-value pair in one call:
| Function | Signature | Description |
|---|
gx_json_obj_add_null | (doc, obj, key) | Add null field |
gx_json_obj_add_bool | (doc, obj, key, bool) | Add boolean field |
gx_json_obj_add_int | (doc, obj, key, i64) | Add integer field |
gx_json_obj_add_float | (doc, obj, key, f64) | Add float field |
gx_json_obj_add_str | (doc, obj, key, cstr) | Add string field |
gx_json_obj_add_val | (doc, obj, key, *void) | Add any value |
gx_json_obj_add_obj | (doc, obj, key) → *void | Add nested object, returns it |
gx_json_obj_add_arr | (doc, obj, key) → *void | Add nested array, returns it |
gx_json_obj_remove | (obj, key) → *void | Remove key, returns removed value |
gx_json_obj_clear | (obj) → bool | Remove all keys |
gx_json_obj_rename | (doc, obj, key, new_key) → bool | Rename a key |
json.gx_json_obj_add_str(doc, obj, "name", "Alice")
json.gx_json_obj_add_int(doc, obj, "age", 30)
// Nested object
var address = json.gx_json_obj_add_obj(doc, obj, "address")
json.gx_json_obj_add_str(doc, address, "city", "Berlin")
Array Mutation
| Function | Signature | Description |
|---|
gx_json_arr_add_null | (doc, arr) | Append null |
gx_json_arr_add_bool | (doc, arr, bool) | Append boolean |
gx_json_arr_add_int | (doc, arr, i64) | Append integer |
gx_json_arr_add_float | (doc, arr, f64) | Append float |
gx_json_arr_add_str | (doc, arr, cstr) | Append string |
gx_json_arr_add_val | (arr, *void) | Append any value |
gx_json_arr_add_obj | (doc, arr) → *void | Append new object, returns it |
gx_json_arr_add_arr | (doc, arr) → *void | Append new array, returns it |
gx_json_arr_remove | (arr, c_size) → *void | Remove by index |
gx_json_arr_clear | (arr) → bool | Remove all elements |
var tags = json.gx_json_obj_add_arr(doc, obj, "tags")
json.gx_json_arr_add_str(doc, tags, "fast")
json.gx_json_arr_add_str(doc, tags, "lightweight")
json.gx_json_arr_add_str(doc, tags, "zero-copy")
Mutable Type Inspection
| Function | Signature | Description |
|---|
gx_json_mut_type | (*void) → u8 | Get type constant |
gx_json_mut_is_null | (*void) → bool | Check if null |
gx_json_mut_is_bool | (*void) → bool | Check if boolean |
gx_json_mut_is_num | (*void) → bool | Check if number |
gx_json_mut_is_int | (*void) → bool | Check if integer |
gx_json_mut_is_real | (*void) → bool | Check if floating-point |
gx_json_mut_is_str | (*void) → bool | Check if string |
gx_json_mut_is_arr | (*void) → bool | Check if array |
gx_json_mut_is_obj | (*void) → bool | Check if object |
| Function | Signature | Description |
|---|
gx_json_mut_get_bool | (*void) → bool | Get boolean |
gx_json_mut_get_int | (*void) → i64 | Get signed integer |
gx_json_mut_get_uint | (*void) → u64 | Get unsigned integer |
gx_json_mut_get_float | (*void) → f64 | Get float |
gx_json_mut_get_str | (*void) → cstr | Get string |
gx_json_mut_get_len | (*void) → c_size | Get length |
Mutable Navigation
| Function | Signature | Description |
|---|
gx_json_mut_obj_get | (*void, cstr) → *void | Get value by key |
gx_json_mut_obj_size | (*void) → c_size | Number of pairs |
gx_json_mut_arr_get | (*void, c_size) → *void | Get by index |
gx_json_mut_arr_size | (*void) → c_size | Number of elements |
gx_json_mut_arr_first | (*void) → *void | First element |
gx_json_mut_arr_last | (*void) → *void | Last element |
Mutable Serialization
| Function | Signature | Description |
|---|
gx_json_mut_write | (*void, c_uint) → cstr | Serialize mutable document |
gx_json_mut_write_val | (*void, c_uint) → cstr | Serialize single value |
var json_str = json.gx_json_mut_write(doc, json.WRITE_PRETTY)
print("{json_str}\n")
Conversion & File I/O
Immutable ↔ Mutable Conversion
| Function | Signature | Description |
|---|
gx_json_to_mut | (*void) → *void | Convert parsed doc to mutable for editing |
gx_json_val_to_mut | (doc, val) → *void | Copy a single value into a mutable doc |
// Parse, then edit
var doc = json.gx_json_parse(text.cstr, text.len, 0)
var mdoc = json.gx_json_to_mut(doc)
json.gx_json_free(doc)
defer json.gx_json_mut_free(mdoc)
var root = json.gx_json_mut_root(mdoc)
json.gx_json_obj_add_str(mdoc, root, "modified", "true")
File I/O
| Function | Signature | Description |
|---|
gx_json_parse_file | (cstr, c_uint) → *void | Read and parse JSON file |
gx_json_write_to_file | (cstr, *void, c_uint) → bool | Write immutable doc to file |
gx_json_mut_write_to_file | (cstr, *void, c_uint) → bool | Write mutable doc to file |
// Read from file
var doc = json.gx_json_parse_file("config.json", 0)
defer json.gx_json_free(doc)
// Write to file (pretty)
json.gx_json_write_to_file("output.json", doc, json.WRITE_PRETTY)
Common Patterns
Checking for Missing Keys
gx_json_obj_get returns 0 (null pointer) if the key doesn’t exist:
var val = json.gx_json_obj_get(root, "optional_key")
if (val != 0) {
var s = json.gx_json_get_str(val)
print("Found: {s}\n")
}
Building a JSON Response
var doc = json.gx_json_new_doc()
defer json.gx_json_mut_free(doc)
var root = json.gx_json_new_obj(doc)
json.gx_json_set_root(doc, root)
json.gx_json_obj_add_bool(doc, root, "success", true)
json.gx_json_obj_add_int(doc, root, "status", 200)
var data = json.gx_json_obj_add_obj(doc, root, "data")
json.gx_json_obj_add_str(doc, data, "message", "OK")
var items = json.gx_json_obj_add_arr(doc, data, "items")
json.gx_json_arr_add_int(doc, items, 1)
json.gx_json_arr_add_int(doc, items, 2)
json.gx_json_arr_add_int(doc, items, 3)
var result = json.gx_json_mut_write(doc, json.WRITE_PRETTY)
print("{result}\n")
Output:
{
"success": true,
"status": 200,
"data": {
"message": "OK",
"items": [
1,
2,
3
]
}
}
Module Layout
modules/
json/
c/
yyjson.c yyjson source (compiled via @cfile)
yyjson.h yyjson header
gx_json.h GX helper wrappers (void* interface)
gx/
json.gx Module declarations, constants, extern functions