Practical Project: Console Application with JSON Persistence
Previous: Concurrency and channels. Next: Complete web project. Technical reference: Command line (CLI), Project structure.
What Will You Build Here?
A console application (CLI) is a program that runs directly in the terminal without a graphical interface. It is the type of software used in automations, batch processing, server maintenance scripts, and developer utilities.
In this guided tutorial we will build a purchasing and inventory manager:
1. Create the official project structure with joss new console.
2. Structure a list of items with quantities and exact decimal prices.
3. Save the data to a physical file on disk (compras.json) in structured JSON format.
4. Read the file from disk, validate its syntactic integrity (json_verify), and reconstruct the data structures in memory (json_decode).
5. Process and total the amounts with financial decimal precision using modular functions.
6. Compile the project into a standalone native executable with joss build.
1. Prepare the Working Environment with joss new console
Open your terminal and generate an official console project with the Joss CLI:
joss new console proyecto-compras
cd proyecto-compras
Joss will have created the base structure of the project (including main.joss, env.joss, joss.yaml, and the app/ folder). Open main.joss in your editor to write the logic of our application.
2. The Complete Program Code
Replace the contents of main.joss with the following complete program:
public func totalizar(array $compras): decimal {
decimal $total = 0m
foreach ($compras as $compra) {
$total = $total + decimal($compra["precio"]) * intval($compra["cantidad"])
}
return $total
}
var $compras = [
{"nombre": "Cuaderno", "precio": "12.50", "cantidad": 2},
{"nombre": "Lapiz", "precio": "3.50", "cantidad": 5}
]
var $guardado = file_put_contents("compras.json", json_encode($compras))
$guardado ? {} : { throw "No se pudo guardar compras.json" }
var $texto = file_get_contents("compras.json")
$texto == null ? { throw "No se pudo leer compras.json" } : {}
json_verify($texto) ? {} : { throw "El archivo no contiene JSON valido" }
var $leidas = json_decode($texto)
print("Articulos: " . count($leidas))
print("Total: " . totalizar($leidas))
print("Archivo guardado")
3. Step-by-Step Architecture Explanation
Let’s analyze how the different language subsystems interact in this program:
1. The Calculation Function (totalizar)
public func totalizar(array $compras): decimal {
decimal $total = 0m
foreach ($compras as $compra) {
$total = $total + decimal($compra["precio"]) * intval($compra["cantidad"])
}
return $total
}
- Receives an
arrayof purchases and promises to return a value of typedecimal. - Initializes an exact accumulator:
decimal $total = 0m. - Iterates through each element with
foreach ($compras as $compra). - Extracts
"precio"and"cantidad"using map keys. Notice how we convert explicitly:decimal($compra["precio"]): Converts the numeric string to a fixed-point decimal.intval($compra["cantidad"]): Converts the quantity to a 64-bit integer.
- Multiplies both values and adds them to
$total.
2. In-Memory Data Structure
var $compras = [
{"nombre": "Cuaderno", "precio": "12.50", "cantidad": 2},
{"nombre": "Lapiz", "precio": "3.50", "cantidad": 5}
]
- We define an array whose elements are associative maps (
{"clave": valor}). - Storing prices as text (
"12.50") inside JSON is good accounting practice: it prevents standard JSON decoders from introducing binary inaccuracies when reading floating-point numbers.
3. Disk Persistence with JSON
var $guardado = file_put_contents("compras.json", json_encode($compras))
$guardado ? {} : { throw "No se pudo guardar compras.json" }
json_encode($compras): Transforms the in-memory Joss structure into a standard JSON text string.file_put_contents("compras.json", ...): Writes that text into the physical file on the hard drive. Returnstrueif it succeeded orfalseif it failed due to permissions or lack of space.- The ternary expression acts as a safeguard: if
$guardadois false, it throws an error withthrow.
4. Reading and Security Validation
var $texto = file_get_contents("compras.json")
$texto == null ? { throw "No se pudo leer compras.json" } : {}
json_verify($texto) ? {} : { throw "El archivo no contiene JSON valido" }
file_get_contents(...): Retrieves the file bytes into a text string. If the file does not exist, it returnsnull.json_verify($texto): Native Joss function that checks whether a string complies with valid JSON specification without parsing the entire tree into memory. If the file is corrupted or was edited improperly by a user, it detects it immediately.json_decode($texto): Reconstructs the JSON data into native Joss arrays and maps ready to be processed.
4. Analysis, Execution, and Native Compilation
First, let’s run static analysis to ensure there are no inconsistencies:
joss analyze main.joss
If everything is correct, run the program with the interpreter:
joss run main.joss
Output produced in the console:
Articulos: 2
Total: 42.5
Archivo guardado
If you check your folder with ls (or dir on Windows) or the file explorer, you will see that the physical file compras.json has been created:
[{"cantidad":2,"nombre":"Cuaderno","precio":"12.50"},{"cantidad":5,"nombre":"Lapiz","precio":"3.50"}]
Compile to a Standalone Native Binary (joss build)
When you want to distribute your console tool to other machines without requiring Joss to be installed or handing over your source code, compile it with:
joss build main.joss
Joss will generate a native executable inside the build/ folder (build/main.exe on Windows or ./build/main on Linux/macOS) ready to be executed directly.
5. Colored Visual Output: The Native Console Module
So that your console application offers an attractive and professional visual experience, you can color and highlight messages in the terminal using the native Console class:
print(Console::green("✓ Archivo guardado con éxito"))
print(Console::yellow("⚠ Advertencia: El stock es bajo"))
print(Console::red("✗ Error al procesar datos"))
print(Console::bold("Total a pagar: S/ 42.50"))
Available Console Methods:
| Method | Purpose and Color | Typical Use |
|---|---|---|
Console::green($t) |
Green | Successful operations, confirmations (✓ OK). |
Console::red($t) |
Red | Errors, validation failures, exceptions. |
Console::yellow($t) |
Yellow | Warnings, notices requiring attention. |
Console::blue($t) / Console::cyan($t) |
Blue / Cyan | Titles, links, descriptive information. |
Console::bold($t) |
Bold | Numeric totals, highlighted names. |
Console::clear() |
Clear screen | Resets the terminal before displaying a menu. |
6. Practical Exercises
Level 1 — Quick Test
Add a third item to the $compras array (for example, {"nombre": "Mochila", "precio": "35.00", "cantidad": 1}), run joss run main.joss again, and verify that the total updates to 77.5 and that compras.json reflects all 3 items.
Level 2 — Guided Exercise
- Create a function
public func articulosCaros(array $compras, decimal $umbral): arraythat iterates over$compraswithforeachand returns a new array containing only the products whosedecimal($compra["precio"])exceeds$umbral. - Print how many items exceed
10.00musingcount(articulosCaros($leidas, 10.00m)). - Color the final
"Archivo guardado"message usingConsole::green("Archivo guardado").
Level 3 — Challenge
Modify the program so that before saving the file it prompts the user for an additional item via keyboard:
1. Declare string $nuevoNombre = "", string $nuevoPrecio = "", and int $nuevaCantidad = 0.
2. Prompt for each piece of data using cin >> $nuevoNombre, cin >> $nuevoPrecio, and cin >> $nuevaCantidad.
3. Append it to $compras with $compras[] = {"nombre": $nuevoNombre, "precio": $nuevoPrecio, "cantidad": $nuevaCantidad}, save the JSON, and compile your application with joss build main.joss.
Next Step
Now that you have mastered disk data persistence and console utility development, it is time to explore Joss’s strongest area: high-performance web application development with routes, controllers, databases, and HTML views.
Continue with: Build a complete web application with the native stack.