Cookbook Recipes
Real-world examples and patterns for common JSON tasks.
Recipe 1: Configuration File Reader
Load and access application configuration from JSON.
class
APP_CONFIG
create
make_from_file
feature {NONE} -- Initialization
make_from_file (a_path: STRING)
local
json: SIMPLE_JSON
do
create json
if attached json.parse_file (a_path) as v then
load_from_json (v)
else
-- Use defaults or raise error
set_defaults
if json.has_errors then
io.put_string ("Config error: " + json.errors_as_string)
end
end
end
feature -- Access
database_host: STRING
database_port: INTEGER
debug_mode: BOOLEAN
allowed_origins: ARRAYED_LIST [STRING]
feature {NONE} -- Implementation
load_from_json (v: SIMPLE_JSON_VALUE)
local
json: SIMPLE_JSON
do
create json
create allowed_origins.make (0)
-- Use JSONPath for nested access
if attached json.query_string (v, "$.database.host") as h then
database_host := h
else
database_host := "localhost"
end
database_port := json.query_integer (v, "$.database.port").to_integer
if database_port = 0 then
database_port := 5432
end
-- Boolean with default
if v.is_object then
debug_mode := v.as_object.boolean_item ("debug")
end
-- Array of strings
across json.query_strings (v, "$.cors.origins[*]") as origin loop
allowed_origins.force (origin)
end
end
set_defaults
do
database_host := "localhost"
database_port := 5432
debug_mode := False
create allowed_origins.make (0)
end
end
Example config.json
{
"database": {
"host": "db.example.com",
"port": 5432
},
"debug": true,
"cors": {
"origins": ["http://localhost:3000", "https://app.example.com"]
}
}
Recipe 2: REST API Response Builder
Build consistent JSON responses for web APIs.
class
API_RESPONSE_BUILDER
feature -- Response Building
success (a_data: SIMPLE_JSON_VALUE): STRING
-- Build success response
do
Result := json.new_object
.put_boolean ("success", True)
.put_value ("data", a_data)
.put_null ("error")
.to_json
end
success_with_meta (a_data: SIMPLE_JSON_VALUE; a_page, a_total: INTEGER): STRING
-- Build paginated response
do
Result := json.new_object
.put_boolean ("success", True)
.put_value ("data", a_data)
.put_object ("meta", json.new_object
.put_integer ("page", a_page)
.put_integer ("total", a_total)
.put_integer ("per_page", 20))
.put_null ("error")
.to_json
end
error (a_code: INTEGER; a_message: STRING): STRING
-- Build error response
do
Result := json.new_object
.put_boolean ("success", False)
.put_null ("data")
.put_object ("error", json.new_object
.put_integer ("code", a_code)
.put_string ("message", a_message))
.to_json
end
validation_error (a_errors: ARRAYED_LIST [TUPLE [field, message: STRING]]): STRING
-- Build validation error response
local
errors_array: SIMPLE_JSON_ARRAY
do
errors_array := json.new_array
across a_errors as e loop
errors_array.add_object (json.new_object
.put_string ("field", e.field)
.put_string ("message", e.message))
end
Result := json.new_object
.put_boolean ("success", False)
.put_null ("data")
.put_object ("error", json.new_object
.put_integer ("code", 422)
.put_string ("message", "Validation failed")
.put_array ("details", errors_array))
.to_json
end
feature {NONE} -- Implementation
json: SIMPLE_JSON
once
create Result
end
end
Usage
local
response: API_RESPONSE_BUILDER
users: SIMPLE_JSON_ARRAY
do
create response
-- Success response
users := json.new_array
.add_object (json.new_object.put_string ("name", "Alice"))
.add_object (json.new_object.put_string ("name", "Bob"))
print (response.success_with_meta (users.to_value, 1, 42))
-- {"success":true,"data":[...],"meta":{"page":1,"total":42}}
-- Error response
print (response.error (404, "User not found"))
-- {"success":false,"data":null,"error":{"code":404,"message":"User not found"}}
end
Recipe 3: Request Validation with Schema
Validate incoming JSON requests against a schema.
class
USER_VALIDATOR
feature -- Validation
validate_create_user (a_json: STRING): TUPLE [valid: BOOLEAN; errors: STRING; data: detachable SIMPLE_JSON_VALUE]
-- Validate user creation request
local
json: SIMPLE_JSON
validator: SIMPLE_JSON_SCHEMA_VALIDATOR
result: SIMPLE_JSON_SCHEMA_VALIDATION_RESULT
error_text: STRING
do
create json
create validator.make
-- First, parse the JSON
if attached json.parse (a_json) as v then
-- Then validate against schema
result := validator.validate (v, user_schema)
if result.is_valid then
Result := [True, "", v]
else
-- Collect error messages
create error_text.make_empty
across result.errors as e loop
if not error_text.is_empty then
error_text.append ("; ")
end
error_text.append (e.message)
end
Result := [False, error_text, Void]
end
else
Result := [False, "Invalid JSON: " + json.errors_as_string, Void]
end
end
feature {NONE} -- Schemas
user_schema: SIMPLE_JSON_SCHEMA
-- Schema for user creation
once
create Result.make_from_string ("[
{
"type": "object",
"required": ["email", "name"],
"properties": {
"email": {
"type": "string",
"pattern": "^[^@]+@[^@]+\\.[^@]+$"
},
"name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
}
}
}
]")
end
end
Usage in Web Handler
local
validator: USER_VALIDATOR
validation: TUPLE [valid: BOOLEAN; errors: STRING; data: detachable SIMPLE_JSON_VALUE]
do
create validator
validation := validator.validate_create_user (request_body)
if validation.valid and attached validation.data as data then
-- Process valid request
create_user (data)
else
-- Return validation errors
send_error (422, validation.errors)
end
end
Recipe 4: Document Diff and Patch
Track and apply changes to JSON documents.
class
DOCUMENT_TRACKER
feature -- Tracking
track_changes (a_original, a_modified: SIMPLE_JSON_VALUE): SIMPLE_JSON_PATCH
-- Generate patch representing changes from original to modified
local
patch: SIMPLE_JSON_PATCH
do
patch := json.create_patch
if a_original.is_object and a_modified.is_object then
diff_objects (a_original.as_object, a_modified.as_object, "", patch)
end
Result := patch
end
apply_and_log (a_document: SIMPLE_JSON_VALUE; a_patch: SIMPLE_JSON_PATCH): TUPLE [doc: SIMPLE_JSON_VALUE; log: STRING]
-- Apply patch and return result with change log
local
result: SIMPLE_JSON_PATCH_RESULT
log: STRING
do
result := a_patch.apply (a_document)
create log.make_empty
log.append ("Applied " + a_patch.operations.count.out + " operations%N")
if result.is_success then
log.append ("Status: Success")
Result := [result.result_document, log]
else
log.append ("Status: Failed - " + result.error_message)
Result := [a_document, log] -- Return original on failure
end
end
feature {NONE} -- Implementation
diff_objects (a_orig, a_mod: SIMPLE_JSON_OBJECT; a_path: STRING; a_patch: SIMPLE_JSON_PATCH)
-- Recursively diff objects
local
key, path: STRING
do
-- Check for removed keys
across a_orig.keys as k loop
key := k
path := a_path + "/" + key
if not a_mod.has (key) then
a_patch.add_remove (path)
end
end
-- Check for added/changed keys
across a_mod.keys as k loop
key := k
path := a_path + "/" + key
if not a_orig.has (key) then
-- New key
if attached a_mod.item (key) as v then
a_patch.add_add (path, v)
end
elseif attached a_orig.item (key) as ov and attached a_mod.item (key) as mv then
-- Check if changed
if not values_equal (ov, mv) then
a_patch.add_replace (path, mv)
end
end
end
end
values_equal (a, b: SIMPLE_JSON_VALUE): BOOLEAN
do
Result := a.to_json.same_string (b.to_json)
end
json: SIMPLE_JSON
once
create Result
end
end
Recipe 5: Entity Serialization
Serialize domain objects to/from JSON.
class
ORDER
inherit
SIMPLE_JSON_SERIALIZABLE
create
make, make_from_json
feature {NONE} -- Initialization
make (a_id: INTEGER; a_customer: STRING)
do
id := a_id
customer := a_customer
create items.make (0)
status := "pending"
created_at := create {DATE_TIME}.make_now
end
make_from_json (a_value: SIMPLE_JSON_VALUE)
do
from_json (a_value)
end
feature -- Access
id: INTEGER
customer: STRING
items: ARRAYED_LIST [ORDER_ITEM]
status: STRING
created_at: DATE_TIME
feature -- Serialization
to_json_object: SIMPLE_JSON_OBJECT
local
items_array: SIMPLE_JSON_ARRAY
do
items_array := json.new_array
across items as item loop
items_array.add_object (item.to_json_object)
end
Result := json.new_object
.put_integer ("id", id)
.put_string ("customer", customer)
.put_array ("items", items_array)
.put_string ("status", status)
.put_string ("created_at", created_at.formatted_out ("yyyy-mm-dd hh:mi:ss"))
end
from_json (a_value: SIMPLE_JSON_VALUE)
local
item: ORDER_ITEM
do
if a_value.is_object then
id := a_value.as_object.integer_item ("id").to_integer
if attached a_value.as_object.string_item ("customer") as c then
customer := c
else
customer := ""
end
if attached a_value.as_object.string_item ("status") as s then
status := s
else
status := "pending"
end
-- Parse items array
create items.make (0)
if attached a_value.as_object.array_item ("items") as arr then
across 1 |..| arr.count as i loop
create item.make_from_json (arr.item (i))
items.force (item)
end
end
end
end
feature {NONE}
json: SIMPLE_JSON
once
create Result
end
end
Recipe 6: Processing Large JSON Files
Stream large JSON files without loading entirely into memory.
class
LOG_ANALYZER
feature -- Analysis
count_errors_in_log (a_file: STRING): INTEGER
-- Count error entries in large JSON log file
-- Format: [{"level": "error", ...}, {"level": "info", ...}, ...]
local
stream: SIMPLE_JSON_STREAM
cursor: SIMPLE_JSON_STREAM_CURSOR
in_object: BOOLEAN
current_level: detachable STRING
do
create stream.make_from_file (a_file)
cursor := stream.new_cursor
from
until
cursor.after
loop
if cursor.is_object_start then
in_object := True
current_level := Void
elseif cursor.is_object_end then
if attached current_level as lv and then lv.same_string ("error") then
Result := Result + 1
end
in_object := False
elseif in_object and cursor.is_key then
if cursor.key_name.same_string ("level") then
cursor.forth
if cursor.is_string then
current_level := cursor.string_value
end
end
end
cursor.forth
end
end
extract_timestamps (a_file: STRING; a_output: ARRAYED_LIST [STRING])
-- Extract all timestamp fields from large file
local
stream: SIMPLE_JSON_STREAM
cursor: SIMPLE_JSON_STREAM_CURSOR
looking_for_value: BOOLEAN
do
create stream.make_from_file (a_file)
cursor := stream.new_cursor
from
until
cursor.after
loop
if cursor.is_key and then cursor.key_name.same_string ("timestamp") then
looking_for_value := True
elseif looking_for_value and cursor.is_string then
a_output.force (cursor.string_value)
looking_for_value := False
end
cursor.forth
end
end
end
Recipe 7: Merging Configuration Files
Merge default config with user overrides using JSON Merge Patch.
class
CONFIG_MERGER
feature -- Merging
merge_configs (a_default_file, a_user_file: STRING): SIMPLE_JSON_VALUE
-- Merge user config over default config
local
json: SIMPLE_JSON
merge: SIMPLE_JSON_MERGE_PATCH
result: SIMPLE_JSON_MERGE_PATCH_RESULT
do
create json
create merge
if attached json.parse_file (a_default_file) as default_config then
if attached json.parse_file (a_user_file) as user_config then
-- Merge user settings over defaults
result := merge.apply (default_config, user_config)
if result.is_success then
Result := result.result_document
else
Result := default_config
end
else
-- No user config, use defaults
Result := default_config
end
else
-- Return empty object on failure
Result := json.new_object.to_value
end
end
end
Example
-- default.json
{
"theme": "light",
"font_size": 14,
"auto_save": true,
"plugins": {
"spell_check": true,
"syntax_highlight": true
}
}
-- user.json (overrides)
{
"theme": "dark",
"font_size": 16,
"plugins": {
"spell_check": false
}
}
-- Result after merge
{
"theme": "dark", -- from user
"font_size": 16, -- from user
"auto_save": true, -- from default
"plugins": {
"spell_check": false, -- from user
"syntax_highlight": true -- from default
}
}
Recipe 8: JSON-to-JSON Transformation
Transform JSON structure for API version compatibility.
class
API_TRANSFORMER
feature -- Transformation
v1_to_v2 (a_v1: SIMPLE_JSON_VALUE): SIMPLE_JSON_VALUE
-- Transform V1 API format to V2
-- V1: {"first_name": "...", "last_name": "..."}
-- V2: {"name": {"first": "...", "last": "..."}}
local
first, last: STRING
do
if a_v1.is_object then
if attached a_v1.as_object.string_item ("first_name") as f then
first := f
else
first := ""
end
if attached a_v1.as_object.string_item ("last_name") as l then
last := l
else
last := ""
end
-- Build V2 structure
Result := json.new_object
.put_object ("name", json.new_object
.put_string ("first", first)
.put_string ("last", last))
.to_value
-- Copy other fields
across a_v1.as_object.keys as k loop
if not k.same_string ("first_name") and not k.same_string ("last_name") then
if attached a_v1.as_object.item (k) as v then
Result.as_object.put_value (k, v)
end
end
end
else
Result := a_v1
end
end
feature {NONE}
json: SIMPLE_JSON
once
create Result
end
end
Recipe 9: Comprehensive Error Handling
Handle all JSON error cases gracefully.
class
SAFE_JSON_PROCESSOR
feature -- Processing
process_safely (a_json: STRING): TUPLE [success: BOOLEAN; data: detachable SIMPLE_JSON_VALUE; error: STRING]
-- Process JSON with comprehensive error handling
local
json: SIMPLE_JSON
error_detail: STRING
do
create json
-- Check for empty input
if a_json.is_empty then
Result := [False, Void, "Empty JSON input"]
elseif attached json.parse (a_json) as v then
-- Success
Result := [True, v, ""]
else
-- Parse failed - build detailed error
create error_detail.make_empty
if json.has_errors then
if attached json.first_error as err then
error_detail.append ("Parse error at line ")
error_detail.append (err.line.out)
error_detail.append (", column ")
error_detail.append (err.column.out)
error_detail.append (": ")
error_detail.append (err.message)
-- Add context if available
if not err.to_detailed_string.is_empty then
error_detail.append ("%N")
error_detail.append (err.to_detailed_string)
end
else
error_detail.append ("Unknown parse error")
end
else
error_detail.append ("Parse returned null without error")
end
Result := [False, Void, error_detail]
end
rescue
-- Catch any unexpected exceptions
Result := [False, Void, "Unexpected error processing JSON"]
end
safe_query (a_value: SIMPLE_JSON_VALUE; a_path: STRING; a_default: STRING): STRING
-- Query with default value on failure
local
json: SIMPLE_JSON
do
create json
if attached json.query_string (a_value, a_path) as s then
Result := s
else
Result := a_default
end
end
end
Recipe 10: Pretty Printing for Debugging
Format JSON for human readability.
class
JSON_DEBUGGER
feature -- Debugging
debug_print (a_value: SIMPLE_JSON_VALUE)
-- Print JSON with type annotations
do
io.put_string ("=== JSON Debug ===%N")
io.put_string ("Type: " + type_name (a_value) + "%N")
io.put_string ("Pretty:%N")
io.put_string (a_value.to_pretty_json)
io.put_string ("%N================%N")
end
compare_json (a_label1: STRING; a_json1: SIMPLE_JSON_VALUE; a_label2: STRING; a_json2: SIMPLE_JSON_VALUE)
-- Side-by-side comparison
do
io.put_string ("=== " + a_label1 + " ===%N")
io.put_string (a_json1.to_pretty_json)
io.put_string ("%N%N=== " + a_label2 + " ===%N")
io.put_string (a_json2.to_pretty_json)
io.put_string ("%N")
end
feature {NONE}
type_name (a_value: SIMPLE_JSON_VALUE): STRING
do
if a_value.is_object then
Result := "object (" + a_value.as_object.count.out + " keys)"
elseif a_value.is_array then
Result := "array (" + a_value.as_array.count.out + " items)"
elseif a_value.is_string then
Result := "string"
elseif a_value.is_integer then
Result := "integer"
elseif a_value.is_number then
Result := "number"
elseif a_value.is_boolean then
Result := "boolean"
elseif a_value.is_null then
Result := "null"
else
Result := "unknown"
end
end
end