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 |:

ConstantValueDescription
READ_NOFLAG0Default strict parsing
READ_INSITU1Modify input buffer (faster, reuses memory)
READ_STOP_WHEN_DONE2Stop after first complete JSON value
READ_ALLOW_TRAILING_COMMAS4Accept [1, 2,]
READ_ALLOW_COMMENTS8Accept // line and /* block */ comments
READ_ALLOW_INF_AND_NAN16Accept Infinity and NaN
READ_ALLOW_INVALID_UNICODE32Don’t reject invalid UTF-8

Write Flags

Pass 0 for minified output:

ConstantValueDescription
WRITE_NOFLAG0Minified (default)
WRITE_PRETTY1Pretty-print with 4-space indent
WRITE_PRETTY_TWO_SPACES64Pretty-print with 2-space indent
WRITE_ESCAPE_UNICODE2Escape non-ASCII as \uXXXX
WRITE_ESCAPE_SLASHES4Escape / as \/
WRITE_ALLOW_INF_AND_NAN8Write Infinity/NaN literals
WRITE_INF_AND_NAN_AS_NULL16Convert Infinity/NaN to null
WRITE_ALLOW_INVALID_UNICODE32Don’t reject invalid UTF-8
WRITE_NEWLINE_AT_END128Trailing newline

Type Constants

Returned by gx_json_type() and gx_json_mut_type():

ConstantValueDescription
TYPE_NONE0Invalid/missing
TYPE_RAW1Raw JSON string
TYPE_NULL2null
TYPE_BOOL3true or false
TYPE_NUM4Number (integer or float)
TYPE_STR5String
TYPE_ARR6Array
TYPE_OBJ7Object

Immutable API — Reading JSON

The immutable API parses JSON into a read-only document. Use this when you only need to query values.

Parsing

FunctionSignatureDescription
gx_json_parse(cstr, c_size, c_uint) → *voidParse JSON string with length and flags
gx_json_parse_file(cstr, c_uint) → *voidParse JSON from file path
gx_json_root(*void) → *voidGet root value of parsed document
gx_json_free(*void)Free parsed document
gx_json_val_count(*void) → c_sizeNumber 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

FunctionSignatureDescription
gx_json_type(*void) → u8Get type constant
gx_json_type_desc(*void) → cstrHuman-readable type name
gx_json_is_null(*void) → boolCheck if null
gx_json_is_bool(*void) → boolCheck if boolean
gx_json_is_num(*void) → boolCheck if number
gx_json_is_int(*void) → boolCheck if integer
gx_json_is_real(*void) → boolCheck if floating-point
gx_json_is_str(*void) → boolCheck if string
gx_json_is_arr(*void) → boolCheck if array
gx_json_is_obj(*void) → boolCheck if object
gx_json_is_true(*void) → boolCheck if true
gx_json_is_false(*void) → boolCheck if false

Value Extraction

FunctionSignatureDescription
gx_json_get_bool(*void) → boolGet boolean value
gx_json_get_int(*void) → i64Get signed integer
gx_json_get_uint(*void) → u64Get unsigned integer
gx_json_get_float(*void) → f64Get floating-point number
gx_json_get_num(*void) → f64Get any number as f64
gx_json_get_str(*void) → cstrGet string value
gx_json_get_len(*void) → c_sizeGet length (string/array/object)
gx_json_equals_str(*void, cstr) → boolCompare value to string

Object Navigation

FunctionSignatureDescription
gx_json_obj_get(*void, cstr) → *voidGet value by key
gx_json_obj_size(*void) → c_sizeNumber 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

FunctionSignatureDescription
gx_json_arr_get(*void, c_size) → *voidGet element by index
gx_json_arr_size(*void) → c_sizeNumber of elements
gx_json_arr_first(*void) → *voidFirst element
gx_json_arr_last(*void) → *voidLast 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

FunctionSignatureDescription
gx_json_write(*void, c_uint) → cstrSerialize document to JSON string
gx_json_write_val(*void, c_uint) → cstrSerialize 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:

FunctionSignatureDescription
gx_json_ptr_get(*void, cstr) → *voidGet 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

FunctionSignatureDescription
gx_json_new_doc() → *voidCreate 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) → *voidGet 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

FunctionSignatureDescription
gx_json_new_null(*void) → *voidCreate null
gx_json_new_bool(*void, bool) → *voidCreate boolean
gx_json_new_int(*void, i64) → *voidCreate integer
gx_json_new_uint(*void, u64) → *voidCreate unsigned integer
gx_json_new_float(*void, f64) → *voidCreate float
gx_json_new_str(*void, cstr) → *voidCreate string (copies)
gx_json_new_arr(*void) → *voidCreate empty array
gx_json_new_obj(*void) → *voidCreate empty object

Object Mutation

Convenience functions that add a key-value pair in one call:

FunctionSignatureDescription
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) → *voidAdd nested object, returns it
gx_json_obj_add_arr(doc, obj, key) → *voidAdd nested array, returns it
gx_json_obj_remove(obj, key) → *voidRemove key, returns removed value
gx_json_obj_clear(obj) → boolRemove all keys
gx_json_obj_rename(doc, obj, key, new_key) → boolRename 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

FunctionSignatureDescription
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) → *voidAppend new object, returns it
gx_json_arr_add_arr(doc, arr) → *voidAppend new array, returns it
gx_json_arr_remove(arr, c_size) → *voidRemove by index
gx_json_arr_clear(arr) → boolRemove 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

FunctionSignatureDescription
gx_json_mut_type(*void) → u8Get type constant
gx_json_mut_is_null(*void) → boolCheck if null
gx_json_mut_is_bool(*void) → boolCheck if boolean
gx_json_mut_is_num(*void) → boolCheck if number
gx_json_mut_is_int(*void) → boolCheck if integer
gx_json_mut_is_real(*void) → boolCheck if floating-point
gx_json_mut_is_str(*void) → boolCheck if string
gx_json_mut_is_arr(*void) → boolCheck if array
gx_json_mut_is_obj(*void) → boolCheck if object

Mutable Value Extraction

FunctionSignatureDescription
gx_json_mut_get_bool(*void) → boolGet boolean
gx_json_mut_get_int(*void) → i64Get signed integer
gx_json_mut_get_uint(*void) → u64Get unsigned integer
gx_json_mut_get_float(*void) → f64Get float
gx_json_mut_get_str(*void) → cstrGet string
gx_json_mut_get_len(*void) → c_sizeGet length

Mutable Navigation

FunctionSignatureDescription
gx_json_mut_obj_get(*void, cstr) → *voidGet value by key
gx_json_mut_obj_size(*void) → c_sizeNumber of pairs
gx_json_mut_arr_get(*void, c_size) → *voidGet by index
gx_json_mut_arr_size(*void) → c_sizeNumber of elements
gx_json_mut_arr_first(*void) → *voidFirst element
gx_json_mut_arr_last(*void) → *voidLast element

Mutable Serialization

FunctionSignatureDescription
gx_json_mut_write(*void, c_uint) → cstrSerialize mutable document
gx_json_mut_write_val(*void, c_uint) → cstrSerialize 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

FunctionSignatureDescription
gx_json_to_mut(*void) → *voidConvert parsed doc to mutable for editing
gx_json_val_to_mut(doc, val) → *voidCopy 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

FunctionSignatureDescription
gx_json_parse_file(cstr, c_uint) → *voidRead and parse JSON file
gx_json_write_to_file(cstr, *void, c_uint) → boolWrite immutable doc to file
gx_json_mut_write_to_file(cstr, *void, c_uint) → boolWrite 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