JSON
Luma provides built-in support for working with JSON — the most common data exchange format on the web and in configuration files.
The design follows Luma’s core principles:
- No new types — JSON maps directly to Luma’s existing maps, lists, and primitives
- Composable — works with files, web requests, or plain strings
- Minimal API — two core functions cover all use cases
Type Mapping
JSON values map naturally to Luma types:
| JSON | Luma |
|---|---|
{} object |
{str: any} map |
[] array |
[any] list |
"text" |
str |
123 |
int |
1.5 |
float |
true / false |
bool |
null |
nil |
There is no special “JSON object” type. A decoded JSON value is just a regular Luma value you can work with immediately.
Decoding JSON
json.decode parses a JSON string into Luma values.
data: any = json.decode('{"name": "Alice", "age": 30}')
print(data["name"]) // Alice
print(data["age"]) // 30It works with any valid JSON — objects, arrays, or even bare primitives:
numbers: any = json.decode("[1, 2, 3]")
print(numbers[0]) // 1Nested Access
Since decoded values are regular maps and lists, nested access works naturally:
data: any = json.decode('{"address": {"city": "Tallinn"}}')
city: str = data["address"]["city"]
print(city) // TallinnInvalid JSON
If the input is not valid JSON, json.decode panics with a clear error message. Future versions will integrate with Luma’s error handling system for recoverable failures.
Encoding JSON
json.encode converts Luma values into a JSON string.
user: {str: any} = {"name": "Bob", "scores": [10, 20, 30]}
output: str = json.encode(user)
print(output) // {"name":"Bob","scores":[10,20,30]}Any combination of maps, lists, strings, numbers, booleans, and nil can be encoded.
Pretty Printing
json.pretty works like json.encode but produces indented, human-readable output.
user: {str: any} = {"name": "Bob", "age": 25}
print(json.pretty(user))Output:
{
"name": "Bob",
"age": 25
}This is especially useful for writing configuration files or debugging.
Working with Files
JSON functions compose naturally with Luma’s file API.
Reading a JSON file
config: any = json.decode(file("config.json").read())
port: int = config["port"]
host: str = config["host"]
print("Starting on ${host}:${port}")Writing a JSON file
data: {str: any} = {"version": "1.0", "items": [1, 2, 3]}
file("output.json").write(json.pretty(data))There is no separate json.read_file() or json.write_file() — composition with file().read() and file().write() keeps the API small and predictable.
Working with Web Servers
JSON support integrates with Luma’s web server in both directions.
Returning JSON responses
The existing json() response helper already handles this:
fn api_status(req: Request) -> Response {
return json({"status": "ok", "version": "1.0"})
}This works exactly as before — no changes needed.
Parsing request bodies
Use json.decode with req.body to parse incoming JSON:
fn create_user(req: Request) -> Response {
body: any = json.decode(req.body)
name: str = body["name"]
return json({"created": name})
}This lets you build complete JSON APIs with a clear, readable flow.
Quick Reference
| Operation | Code |
|---|---|
| Parse JSON string | data = json.decode(str) |
| Read JSON file | data = json.decode(file("x.json").read()) |
| Create JSON string | s = json.encode(value) |
| Pretty-print JSON | s = json.pretty(value) |
| Write JSON file | file("x.json").write(json.encode(data)) |
| Parse request body | data = json.decode(req.body) |
| JSON response | return json({"key": "value"}) |
Complete Example
A small program that reads a config file, modifies it, and writes it back:
// Read existing config
config: any = json.decode(file("config.json").read())
// Update a value
config["version"] = "2.0"
config["updated"] = true
// Write back with pretty formatting
file("config.json").write(json.pretty(config))
print("Config updated to version ${config["version"]}")Summary
Luma’s JSON support is built on a simple idea: JSON is just data, and Luma already has the types to represent it. Two functions — json.decode and json.encode — handle the conversion. Everything else is composition with existing features.
Parse it, use it, encode it. No wrappers, no special types, no surprises.