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"])    // 30

It works with any valid JSON — objects, arrays, or even bare primitives:

numbers: any = json.decode("[1, 2, 3]")
print(numbers[0])   // 1

Nested 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)   // Tallinn

Invalid 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.

Last updated on