DOCUMENTATION

JSON & HTTP

DOCUMENTATION

XCX 4.4.1 JSON and HTTP

JSON

JSON objects in XCX 4.4.1 are mutable.

Creation

$ xcx
_ ⛶ ×
1json: config <<< {"port": 8080, "debug": false} >>>;
2json: user <<< {"name": "", "age": 0} >>>;

Values in the literal can be placeholders ("", 0, false) to be filled later via .set().

A <<< ... >>> literal produces a json value in every expression position - declarations, reassignments, and call arguments alike:

$ xcx
_ ⛶ ×
1json: config <<< {"port": 8080} >>>;
2config = <<< {"port": 9090} >>>; --- reassignment - produces a json value
3array:json: items;
4items.push(<<< {"id": 1} >>>); --- call argument - produces a json value

The literal's content must be valid JSON (strict or relaxed, the same acceptance as json.parse()). Content that is not valid JSON - for example embedded expressions, which the language does not support - keeps plain-string behavior.

Serialization from Collections

You can also create JSON objects and arrays directly from XCX collections using the .toJson() method. This is available for:

  • Maps: Returns a JSON object.
  • Tables: Returns a JSON array of objects.

See the Collections Documentation for more details on mapping and behavior.

Parsing

$ xcx
_ ⛶ ×
1json: parsed = json.parse(raw_string);

CAUTION:

Panic on Invalid JSON (R305): If parsing fails, the VM terminates immediately. Verify string content before parsing.

[!NOTE]

No nesting-depth limit: since XCX 4.3, json.parse() accepts arbitrarily deep nesting (the former 128-level recursion limit is lifted); the practical ceiling is available memory. Only malformed content triggers R305.

Mutability Pattern

Declare a schema with zero-values, then populate:

$ xcx
_ ⛶ ×
1json: resp <<< {"token": "", "role": "", "uid": 0, "ok": false} >>>;
2resp.set("token", crypto.token(32));
3resp.set("role", "admin");
4resp.set("uid", 42);
5resp.set("ok", true);
6yield net.respond(200, resp);

JSON Methods

MethodSignatureReturnsDescription
.exists(path)(s) → bbChecks if path exists and is non-null
.get(path/idx)(s/i) → jsonjsonGets element at path or index
.bind(path, var)(s, ref) → bbExtracts value into a pre-declared XCX variable
.set(path, val)(s, T) → bbSets value at path; creates key if missing
.push(val)(json) → bbAppends element to a JSON array node
.size() / .count() / .len()() → iiInterchangeable aliases; number of keys (object) or elements (array)
.keys()() → array:sarray:sReturns an array of keys for a JSON object
.toStr()() → ssSerializes to JSON string
.inject(path, map, tbl)(s, map, table) → bbBulk import of JSON array into XCX table
.first()() → jsonjsonReturns the first element of a JSON array; halt.error if empty

NOTE:

.push() on JSON arrays: .push() works exclusively on JSON nodes that are arrays ([]). Calling .push() on a JSON object ({}) results in a halt.error.

$ xcx
_ ⛶ ×
1json: data <<< {"items": []} >>>;
2json: obj <<< {"id": 1} >>>;
3data.get("items").push(obj); --- OK: "items" is an array
4data.push(obj); --- halt.error: data is an object, not an array

[!IMPORTANT]

.bind() syntax: The second argument must be a previously declared variable. You cannot declare the type inline.

$ xcx
_ ⛶ ×
1--- Wrong
2req.bind("ip", s: ip);
3
4--- Correct
5s: ip;
6req.bind("ip", ip);

Path Notation

Both dot-notation and bracket notation are supported for nested access:

$ xcx
_ ⛶ ×
1--- Nested field
2json: cfg <<< {"server": {"host": "localhost"}} >>>;
3s: host;
4cfg.bind("server.host", host);
5
6--- Array index
7json: resp <<< {"items": []} >>>;
8resp.set("items[0]", first_item);
9resp.set("items[1]", second_item);

.inject() - Bulk Import

Import a JSON array directly into an XCX table:

$ xcx
_ ⛶ ×
1json: data <<< {"users":[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]} >>>;
2table: imported { columns=[uid::i, uname::s] rows=[EMPTY] };
3map: mapping { schema=[s<->s] data=["uid"::"id", "uname"::"name"] };
4data.inject("users", mapping, imported);
5imported.show();

JSON in HTTP Requests

Inside HTTP handlers, json: req has this structure:

$ json
_ ⛶ ×
1{
2 "method": "POST",
3 "path": "/api/login",
4 "query": { "page": "1" },
5 "headers": { "authorization": "Bearer ..." },
6 "body": { ... },
7 "ip": "1.2.3.4"
8}
$ xcx
_ ⛶ ×
1s: ip;
2req.bind("ip", ip);
3
4json: body;
5req.bind("body", body);
6
7json: headers;
8req.bind("headers", headers);
9
10s: auth;
11headers.bind("authorization", auth);

HTTP Client

High-Level API

MethodSignatureReturns
net.get(url)(s) → jsonjson
net.post(url, body)(s, json) → jsonjson
net.put(url, body)(s, json) → jsonjson
net.delete(url)(s) → jsonjson
$ xcx
_ ⛶ ×
1json: resp = net.get("https://api.example.com/users");
2
3json: body <<< {"name": "Alice"} >>>;
4json: resp = net.post("https://api.example.com/users", body);

Response Object

$ json
_ ⛶ ×
1{
2 "status": 200,
3 "ok": true,
4 "body": { ... },
5 "headers": { "content-type": "application/json" },
6 "text": "...",
7 "error": "..."
8}
FieldTypeDescription
statusiHTTP status code
okbtrue when status >= 200 and < 300
bodyjsonParsed response body (if JSON); otherwise null
headersjsonResponse headers
textsResponse body as raw string (always available)
errorsError message if request failed; empty string if OK

ok is true when status >= 200 and < 300. Always check ok before accessing body.

$ xcx
_ ⛶ ×
1json: resp = net.get("https://api.example.com/data");
2if (resp.ok) then;
3 --- working with resp.body
4else;
5 s: err;
6 if (resp.exists("error")) then;
7 resp.bind("error", err);
8 end;
9 >! "Error: " + s(resp.get("status")) + " | " + err;
10end;

Low-Level Builder (net.request)

$ xcx
_ ⛶ ×
1net.request {
2 method = "POST",
3 url = "https://api.example.com/data",
4 headers = ["Authorization" :: "Bearer xyz", "X-App" :: "XCX"],
5 body = my_json,
6 timeout = 5000
7} as resp;
FieldTypeRequiredDefaultDescription
methodsYes-"GET", "POST", "PUT", etc.
urlsYes-Full URL with scheme
headersmap:s<->sNo{}Additional request headers
bodyjsonNonullIgnored for GET and DELETE
timeoutiNo10000Milliseconds

HTTP Server

serve Directive

$ xcx
_ ⛶ ×
1serve: app {
2 port = 8080,
3 host = "0.0.0.0",
4 workers = 4,
5 routes = [
6 "POST /api/login" :: handle_login,
7 "GET /api/user" :: handle_user,
8 "DELETE /api/users" :: handle_delete,
9 "OPTIONS *" :: handle_options,
10 "*" :: handle_404
11 ]
12};
FieldTypeRequiredDefaultDescription
portiYes-Port number (1–65535)
hostsNo"127.0.0.1""0.0.0.0" = all interfaces
workersiNo1Reserved: accepted but currently unused - handler execution is serialized
routesroute listYes-Checked top-to-bottom, first-match wins

NOTE:

serve: is a terminal statement. No code after this directive will execute.

Wildcard * matches any method or path - always place it last.

Handler Fibers

Every handler must be a fiber with the signature fiber name(json: req -> json):

$ xcx
_ ⛶ ×
1fiber handle_health(json: req -> json) {
2 yield net.respond(200, <<< {"status": "ok"} >>>);
3};

A handler that does not call yield net.respond(...) results in an automatic 500 Internal Server Error.

net.respond()

$ xcx
_ ⛶ ×
1yield net.respond(200, my_json);
2yield net.respond(201, my_json, ["Location" :: "/users/42"]);
3yield net.respond(204, <<< {} >>>);
4yield net.respond(404, <<< {"error": "not found"} >>>);
ParameterTypeRequiredDescription
statusiYesHTTP status code
body`json \s`YesJSON object or raw string; use <<< {} >>> for empty JSON
headersmap:s<->sNoAdditional response headers

CORS and Preflight

By default, the XCX engine provides automatic CORS support by adding these headers if they are missing from the response:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Methods: GET, POST, OPTIONS, DELETE, PATCH
  • Access-Control-Allow-Headers: Content-Type, Authorization, X-CSRF-TOKEN

TIP:

Recommended Practice: While the engine provides defaults, it is highly recommended to explicitly declare these headers in your code. This ensures your application remains predictable across different environments and clearly documents its security policy.

For preflight (OPTIONS) support, use a dedicated handler to explicitly set allowed methods and headers:

$ xcx
_ ⛶ ×
1fiber handle_options(json: req -> json) {
2 yield net.respond(204, <<< {} >>>, [
3 {
4 "Access-Control-Allow-Methods": "GET, POST, DELETE, PATCH, OPTIONS",
5 "Access-Control-Allow-Headers": "Content-Type, Authorization, X-CSRF-TOKEN"
6 }
7 ]);
8};

Concurrent Requests

Fibers allow overlapping I/O:

$ xcx
_ ⛶ ×
1fiber fetch(s: url -> json) {
2 yield net.get(url);
3};
4
5fiber:json: f1 = fetch("https://api.example.com/users");
6fiber:json: f2 = fetch("https://api.example.com/posts");
7json: r1 = f1.next();
8json: r2 = f2.next();

Security Constraints

ConstraintBehavior
localhost / 127.0.0.1 / ::1Allowed
169.254.x.x (link-local)halt.fatal - SSRF protection
10.x, 172.16.x – 172.31.x, 192.168.xhalt.error - private ranges blocked
file:// URLshalt.fatal
Max response body size50 MB - a larger response returns status: 413, ok: false, error: "Body too large"
Incoming request bodyNo size limit is enforced; validate input size in handlers