# JSON Library (json)
# Difficulty: 2
# Description: Examples of using the JSON Library in Razen
## Introduction
The JSON Library (`json`) provides functions for working with JSON (JavaScript Object Notation) data in Razen. This library helps you parse JSON strings into Razen data structures and stringify Razen objects into JSON format.
## Library Overview
### JSON Library Functions
- `parse` → Parses a JSON string into a Razen object
- `stringify` → Converts a Razen object into a JSON string
- `pretty` → Converts a Razen object into a formatted JSON string with indentation
## How to Use the JSON Library
### Library Import
To use the JSON Library in Razen, you first need to import it using the `lib` keyword:
```razen
lib json;
```
### Function Calling
Once imported, you can call functions from the library using the double colon `::` syntax:
```razen
json::functionName(parameters);
```
**Note about bracket notation (Legacy):**
In older versions of Razen (before beta v0.1.75), you could also use bracket notation for library functions:
```razen
# DEPRECATED - Only works in Razen before beta v0.1.75
json[functionName](parameters);
```
This syntax is no longer supported in newer versions of Razen.
## Basic Examples
### Parsing JSON Strings
```razen
# Import the json library
lib json;
# Parse a simple JSON string into a Razen object
str jsonString = '{"name": "John", "age": 30, "city": "New York"}';
map parsedObject = json::parse(jsonString);
show "Parsed object: " + parsedObject;
show "Name: " + parsedObject["name"];
show "Age: " + parsedObject["age"];
show "City: " + parsedObject["city"];
```
### Converting Objects to JSON Strings
```razen
# Import the json library
lib json;
# Create a Razen object
map person = {
"name": "Alice",
"age": 25,
"isStudent": true,
"hobbies": ["reading", "swimming", "coding"]
};
# Convert the object to a JSON string
str jsonString = json::stringify(person);
show "JSON string: " + jsonString;
# Convert the object to a pretty-printed JSON string
str prettyJson = json::pretty(person);
show "Pretty JSON:\n" + prettyJson;
```
### Working with Nested JSON
```razen
# Import the json library
lib json;
# Parse a nested JSON string
str nestedJsonString = '{"user": {"name": "Bob", "details": {"age": 28, "address": {"city": "Boston", "zipcode": "02108"}}}}';
map nestedObject = json::parse(nestedJsonString);
# Access nested properties
show "User name: " + nestedObject["user"]["name"];
show "User age: " + nestedObject["user"]["details"]["age"];
show "User city: " + nestedObject["user"]["details"]["address"]["city"];
show "User zipcode: " + nestedObject["user"]["details"]["address"]["zipcode"];
# Modify nested properties
nestedObject["user"]["details"]["age"] = 29;
nestedObject["user"]["details"]["address"]["city"] = "Cambridge";
# Convert back to JSON
str updatedJson = json::pretty(nestedObject);
show "Updated JSON:\n" + updatedJson;
```
### Working with JSON Arrays
```razen
# Import the json library
lib json;
# Parse a JSON array
str jsonArrayString = '["apple", "banana", "cherry", "date"]';
str fruitArray = json::parse(jsonArrayString);
show "Parsed array: " + fruitArray;
show "First fruit: " + fruitArray[0];
show "Last fruit: " + fruitArray[fruitArray.length - 1];
# Create an array of objects
str people = [
{"name": "John", "age": 30},
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 28}
];
# Convert to JSON
str peopleJson = json::stringify(people);
show "People JSON: " + peopleJson;
# Pretty print
str prettyPeopleJson = json::pretty(people);
show "Pretty People JSON:\n" + prettyPeopleJson;
```
## Advanced Examples
### JSON Configuration Manager
```razen
# Import the json library
lib json;
lib filesystem;
# JSON Configuration Manager class
class ConfigManager {
# Constructor
init(str configPath) {
this.configPath = configPath;
this.config = {};
# Load configuration if file exists
if (filesystem::exists(configPath)) {
this.loadConfig();
} else {
# Create default config
this.config = {
"appName": "Razen App",
"version": "1.0.0",
"settings": {
"theme": "light",
"fontSize": 12,
"notifications": true
},
"recentFiles": []
};
# Save default config
this.saveConfig();
}
}
# Load configuration from file
fun loadConfig() {
try {
str jsonContent = filesystem::read_file(this.configPath);
this.config = json::parse(jsonContent);
return true;
} catch (err) {
show "Error loading config: " + err;
return false;
}
}
# Save configuration to file
fun saveConfig() {
try {
str jsonContent = json::pretty(this.config);
filesystem::write_file(this.configPath, jsonContent, false);
return true;
} catch (err) {
show "Error saving config: " + err;
return false;
}
}
# Get a configuration value
fun get(str key, val defaultValue) {
# Handle nested keys with dot notation
if (key.indexOf(".") > -1) {
str parts = key.split(".");
map current = this.config;
for (num i = 0; i < parts.length - 1; i = i + 1) {
if (current[parts[i]] == null) {
return defaultValue;
}
current = current[parts[i]];
}
str lastKey = parts[parts.length - 1];
return current[lastKey] != null ? current[lastKey] : defaultValue;
}
return this.config[key] != null ? this.config[key] : defaultValue;
}
# Set a configuration value
fun set(str key, val value) {
# Handle nested keys with dot notation
if (key.indexOf(".") > -1) {
str parts = key.split(".");
map current = this.config;
for (num i = 0; i < parts.length - 1; i = i + 1) {
if (current[parts[i]] == null) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
str lastKey = parts[parts.length - 1];
current[lastKey] = value;
} else {
this.config[key] = value;
}
# Save after each change
this.saveConfig();
return value;
}
# Add an item to an array configuration
fun addToArray(str key, val item) {
str array = this.get(key, []);
if (!Array.isArray(array)) {
array = [array];
}
array.push(item);
this.set(key, array);
return array;
}
# Get the entire configuration
fun getAll() {
return this.config;
}
}
# Create a configuration manager
ConfigManager config = new ConfigManager("./app_config.json");
# Get configuration values
show "App name: " + config.get("appName", "Unknown App");
show "Theme: " + config.get("settings.theme", "default");
show "Font size: " + config.get("settings.fontSize", 10);
# Set configuration values
config.set("settings.theme", "dark");
config.set("settings.fontSize", 14);
# Add to an array
config.addToArray("recentFiles", "document1.txt");
config.addToArray("recentFiles", "document2.txt");
# Show the entire configuration
show "Updated configuration:";
show json::pretty(config.getAll());
```
### JSON Data Validator
```razen
# Import the json library
lib json;
# JSON Schema Validator class
class JsonValidator {
# Validate a JSON object against a schema
fun validate(map data, map schema) {
str errors = [];
# Check required fields
if (schema["required"] != null && Array.isArray(schema["required"])) {
for (num i = 0; i < schema["required"].length; i = i + 1) {
str field = schema["required"][i];
if (data[field] == null) {
errors.push("Missing required field: " + field);
}
}
}
# Check property types
if (schema["properties"] != null) {
for (str field in schema["properties"]) {
if (data[field] != null) {
str propSchema = schema["properties"][field];
# Check type
if (propSchema["type"] != null) {
str expectedType = propSchema["type"];
str actualType = typeof(data[field]);
# Handle special cases
if (expectedType == "array" && !Array.isArray(data[field])) {
errors.push("Field '" + field + "' should be an array");
} else if (expectedType == "object" && actualType != "object") {
errors.push("Field '" + field + "' should be an object");
} else if (expectedType == "number" && actualType != "number") {
errors.push("Field '" + field + "' should be a number");
} else if (expectedType == "string" && actualType != "string") {
errors.push("Field '" + field + "' should be a string");
} else if (expectedType == "boolean" && actualType != "boolean") {
errors.push("Field '" + field + "' should be a boolean");
}
}
# Check enum values
if (propSchema["enum"] != null && Array.isArray(propSchema["enum"])) {
bool found = false;
for (num i = 0; i < propSchema["enum"].length; i = i + 1) {
if (data[field] == propSchema["enum"][i]) {
found = true;
break;
}
}
if (!found) {
errors.push("Field '" + field + "' has invalid value. Expected one of: " + propSchema["enum"]);
}
}
# Check min/max for numbers
if (typeof(data[field]) == "number") {
if (propSchema["minimum"] != null && data[field] < propSchema["minimum"]) {
errors.push("Field '" + field + "' should be at least " + propSchema["minimum"]);
}
if (propSchema["maximum"] != null && data[field] > propSchema["maximum"]) {
errors.push("Field '" + field + "' should be at most " + propSchema["maximum"]);
}
}
# Check min/max length for strings
if (typeof(data[field]) == "string") {
if (propSchema["minLength"] != null && data[field].length < propSchema["minLength"]) {
errors.push("Field '" + field + "' should have at least " + propSchema["minLength"] + " characters");
}
if (propSchema["maxLength"] != null && data[field].length > propSchema["maxLength"]) {
errors.push("Field '" + field + "' should have at most " + propSchema["maxLength"] + " characters");
}
}
# Check array items
if (Array.isArray(data[field]) && propSchema["items"] != null) {
for (num i = 0; i < data[field].length; i = i + 1) {
str itemErrors = this.validate(data[field][i], propSchema["items"]);
for (num j = 0; j < itemErrors.length; j = j + 1) {
errors.push("In " + field + "[" + i + "]: " + itemErrors[j]);
}
}
}
# Check nested objects
if (typeof(data[field]) == "object" && !Array.isArray(data[field]) && propSchema["properties"] != null) {
str nestedErrors = this.validate(data[field], propSchema);
for (num i = 0; i < nestedErrors.length; i = i + 1) {
errors.push("In " + field + ": " + nestedErrors[i]);
}
}
}
}
}
return errors;
}
# Check if data is valid according to schema
fun isValid(map data, map schema) {
str errors = this.validate(data, schema);
return errors.length == 0;
}
# Get validation errors
fun getErrors(map data, map schema) {
return this.validate(data, schema);
}
}
# Define a user schema
map userSchema = {
"required": ["name", "email", "age"],
"properties": {
"name": {
"type": "string",
"minLength": 2,
"maxLength": 50
},
"email": {
"type": "string"
},
"age": {
"type": "number",
"minimum": 18,
"maximum": 120
},
"role": {
"type": "string",
"enum": ["admin", "user", "guest"]
},
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipcode": { "type": "string" }
}
},
"hobbies": {
"type": "array",
"items": {
"type": "string"
}
}
}
};
# Create a validator
JsonValidator validator = new JsonValidator();
# Valid user data
map validUser = {
"name": "John Doe",
"email": "john@example.com",
"age": 30,
"role": "admin",
"address": {
"street": "123 Main St",
"city": "Boston",
"zipcode": "02108"
},
"hobbies": ["reading", "hiking", "coding"]
};
# Invalid user data
map invalidUser = {
"name": "J", # Too short
"email": "john@example.com",
# Missing age
"role": "superuser", # Not in enum
"address": {
"street": "123 Main St",
"zipcode": "02108"
# Missing city
},
"hobbies": ["reading", 123] # Number in string array
};
# Validate the users
show "Valid user is valid: " + validator.isValid(validUser, userSchema);
show "Invalid user is valid: " + validator.isValid(invalidUser, userSchema);
# Show validation errors for invalid user
str errors = validator.getErrors(invalidUser, userSchema);
show "Validation errors:";
for (num i = 0; i < errors.length; i = i + 1) {
show "- " + errors[i];
}
```
## Practical Application: JSON API Client
```razen
# Import the json library
lib json;
# Simple JSON API Client class
class ApiClient {
# Constructor
init(str baseUrl) {
this.baseUrl = baseUrl;
}
# Simulate HTTP GET request
fun get(str endpoint) {
# In a real application, this would make an actual HTTP request
# For this example, we'll simulate responses
show "GET request to " + this.baseUrl + endpoint;
# Simulate different endpoints
if (endpoint == "/users") {
return this.simulateResponse(200, [
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"},
{"id": 3, "name": "Bob Johnson", "email": "bob@example.com"}
]);
} else if (endpoint.startsWith("/users/")) {
# Extract user ID from endpoint
str parts = endpoint.split("/");
num userId = Number(parts[parts.length - 1]);
if (userId == 1) {
return this.simulateResponse(200, {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"createdAt": "2023-01-15"
});
} else if (userId == 2) {
return this.simulateResponse(200, {
"id": 2,
"name": "Jane Smith",
"email": "jane@example.com",
"role": "user",
"createdAt": "2023-02-20"
});
} else {
return this.simulateResponse(404, {"error": "User not found"});
}
} else if (endpoint == "/posts") {
return this.simulateResponse(200, [
{"id": 1, "title": "First Post", "author": "John Doe"},
{"id": 2, "title": "Second Post", "author": "Jane Smith"},
{"id": 3, "title": "Third Post", "author": "John Doe"}
]);
} else {
return this.simulateResponse(404, {"error": "Endpoint not found"});
}
}
# Simulate HTTP POST request
fun post(str endpoint, map data) {
# In a real application, this would make an actual HTTP request
# For this example, we'll simulate responses
show "POST request to " + this.baseUrl + endpoint;
show "Request data: " + json::stringify(data);
# Simulate different endpoints
if (endpoint == "/users") {
# Validate required fields
if (!data["name"] || !data["email"]) {
return this.simulateResponse(400, {"error": "Name and email are required"});
}
# Simulate successful user creation
return this.simulateResponse(201, {
"id": 4,
"name": data["name"],
"email": data["email"],
"createdAt": "2023-06-15"
});
} else if (endpoint == "/login") {
# Validate credentials
if (!data["email"] || !data["password"]) {
return this.simulateResponse(400, {"error": "Email and password are required"});
}
# Simulate successful login
if (data["email"] == "john@example.com" && data["password"] == "password123") {
return this.simulateResponse(200, {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
});
} else {
return this.simulateResponse(401, {"error": "Invalid credentials"});
}
} else {
return this.simulateResponse(404, {"error": "Endpoint not found"});
}
}
# Simulate HTTP response
fun simulateResponse(num statusCode, val data) {
return {
"statusCode": statusCode,
"data": data,
"headers": {
"Content-Type": "application/json",
"Server": "Razen API Simulator"
}
};
}
# Process response
fun processResponse(map response) {
show "Response status: " + response["statusCode"];
if (response["statusCode"] >= 200 && response["statusCode"] < 300) {
show "Success! Response data:";
show json::pretty(response["data"]);
return response["data"];
} else {
show "Error! " + response["statusCode"];
if (response["data"] && response["data"]["error"]) {
show "Error message: " + response["data"]["error"];
}
return null;
}
}
}
# Create an API client
ApiClient client = new ApiClient("https://api.example.com");
# Make GET requests
map response1 = client.get("/users");
client.processResponse(response1);
map response2 = client.get("/users/1");
client.processResponse(response2);
map response3 = client.get("/users/999");
client.processResponse(response3);
# Make POST requests
map loginData = {
"email": "john@example.com",
"password": "password123"
};
map response4 = client.post("/login", loginData);
map loginResult = client.processResponse(response4);
if (loginResult != null) {
show "User logged in with token: " + loginResult["token"];
}
# Create a new user
map newUserData = {
"name": "Alice Brown",
"email": "alice@example.com"
};
map response5 = client.post("/users", newUserData);
client.processResponse(response5);
```
## Summary
The JSON Library in Razen provides essential functions for working with JSON data:
- `parse` for converting JSON strings to Razen objects
- `stringify` for converting Razen objects to JSON strings
- `pretty` for converting Razen objects to formatted JSON strings with indentation
These functions help you work with JSON data efficiently in your Razen programs, enabling interoperability with web APIs, configuration files, and data storage.
# JSON Library (json)
# Difficulty: 2
# Description: Examples of using the JSON Library in Razen
## Introduction
The JSON Library (`json`) provides functions for working with JSON (JavaScript Object Notation) data in Razen. This library helps you parse JSON strings into Razen data structures and stringify Razen objects into JSON format.
## Library Overview
### JSON Library Functions
- `parse` → Parses a JSON string into a Razen object
- `stringify` → Converts a Razen object into a JSON string
- `pretty` → Converts a Razen object into a formatted JSON string with indentation
## How to Use the JSON Library
### Library Import
To use the JSON Library in Razen, you first need to import it using the `lib` keyword:
```razen
lib json;
```
### Function Calling
Once imported, you can call functions from the library using the double colon `::` syntax:
```razen
json::functionName(parameters);
```
**Note about bracket notation (Legacy):**
In older versions of Razen (before beta v0.1.75), you could also use bracket notation for library functions:
```razen
# DEPRECATED - Only works in Razen before beta v0.1.75
json[functionName](parameters);
```
This syntax is no longer supported in newer versions of Razen.
## Basic Examples
### Parsing JSON Strings
```razen
# Import the json library
lib json;
# Parse a simple JSON string into a Razen object
str jsonString = '{"name": "John", "age": 30, "city": "New York"}';
map parsedObject = json::parse(jsonString);
show "Parsed object: " + parsedObject;
show "Name: " + parsedObject["name"];
show "Age: " + parsedObject["age"];
show "City: " + parsedObject["city"];
```
### Converting Objects to JSON Strings
```razen
# Import the json library
lib json;
# Create a Razen object
map person = {
"name": "Alice",
"age": 25,
"isStudent": true,
"hobbies": ["reading", "swimming", "coding"]
};
# Convert the object to a JSON string
str jsonString = json::stringify(person);
show "JSON string: " + jsonString;
# Convert the object to a pretty-printed JSON string
str prettyJson = json::pretty(person);
show "Pretty JSON:\n" + prettyJson;
```
### Working with Nested JSON
```razen
# Import the json library
lib json;
# Parse a nested JSON string
str nestedJsonString = '{"user": {"name": "Bob", "details": {"age": 28, "address": {"city": "Boston", "zipcode": "02108"}}}}';
map nestedObject = json::parse(nestedJsonString);
# Access nested properties
show "User name: " + nestedObject["user"]["name"];
show "User age: " + nestedObject["user"]["details"]["age"];
show "User city: " + nestedObject["user"]["details"]["address"]["city"];
show "User zipcode: " + nestedObject["user"]["details"]["address"]["zipcode"];
# Modify nested properties
nestedObject["user"]["details"]["age"] = 29;
nestedObject["user"]["details"]["address"]["city"] = "Cambridge";
# Convert back to JSON
str updatedJson = json::pretty(nestedObject);
show "Updated JSON:\n" + updatedJson;
```
### Working with JSON Arrays
```razen
# Import the json library
lib json;
# Parse a JSON array
str jsonArrayString = '["apple", "banana", "cherry", "date"]';
str fruitArray = json::parse(jsonArrayString);
show "Parsed array: " + fruitArray;
show "First fruit: " + fruitArray[0];
show "Last fruit: " + fruitArray[fruitArray.length - 1];
# Create an array of objects
str people = [
{"name": "John", "age": 30},
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 28}
];
# Convert to JSON
str peopleJson = json::stringify(people);
show "People JSON: " + peopleJson;
# Pretty print
str prettyPeopleJson = json::pretty(people);
show "Pretty People JSON:\n" + prettyPeopleJson;
```
## Advanced Examples
### JSON Configuration Manager
```razen
# Import the json library
lib json;
lib filesystem;
# JSON Configuration Manager class
class ConfigManager {
# Constructor
init(str configPath) {
this.configPath = configPath;
this.config = {};
# Load configuration if file exists
if (filesystem::exists(configPath)) {
this.loadConfig();
} else {
# Create default config
this.config = {
"appName": "Razen App",
"version": "1.0.0",
"settings": {
"theme": "light",
"fontSize": 12,
"notifications": true
},
"recentFiles": []
};
# Save default config
this.saveConfig();
}
}
# Load configuration from file
fun loadConfig() {
try {
str jsonContent = filesystem::read_file(this.configPath);
this.config = json::parse(jsonContent);
return true;
} catch (err) {
show "Error loading config: " + err;
return false;
}
}
# Save configuration to file
fun saveConfig() {
try {
str jsonContent = json::pretty(this.config);
filesystem::write_file(this.configPath, jsonContent, false);
return true;
} catch (err) {
show "Error saving config: " + err;
return false;
}
}
# Get a configuration value
fun get(str key, val defaultValue) {
# Handle nested keys with dot notation
if (key.indexOf(".") > -1) {
str parts = key.split(".");
map current = this.config;
for (num i = 0; i < parts.length - 1; i = i + 1) {
if (current[parts[i]] == null) {
return defaultValue;
}
current = current[parts[i]];
}
str lastKey = parts[parts.length - 1];
return current[lastKey] != null ? current[lastKey] : defaultValue;
}
return this.config[key] != null ? this.config[key] : defaultValue;
}
# Set a configuration value
fun set(str key, val value) {
# Handle nested keys with dot notation
if (key.indexOf(".") > -1) {
str parts = key.split(".");
map current = this.config;
for (num i = 0; i < parts.length - 1; i = i + 1) {
if (current[parts[i]] == null) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
str lastKey = parts[parts.length - 1];
current[lastKey] = value;
} else {
this.config[key] = value;
}
# Save after each change
this.saveConfig();
return value;
}
# Add an item to an array configuration
fun addToArray(str key, val item) {
str array = this.get(key, []);
if (!Array.isArray(array)) {
array = [array];
}
array.push(item);
this.set(key, array);
return array;
}
# Get the entire configuration
fun getAll() {
return this.config;
}
}
# Create a configuration manager
ConfigManager config = new ConfigManager("./app_config.json");
# Get configuration values
show "App name: " + config.get("appName", "Unknown App");
show "Theme: " + config.get("settings.theme", "default");
show "Font size: " + config.get("settings.fontSize", 10);
# Set configuration values
config.set("settings.theme", "dark");
config.set("settings.fontSize", 14);
# Add to an array
config.addToArray("recentFiles", "document1.txt");
config.addToArray("recentFiles", "document2.txt");
# Show the entire configuration
show "Updated configuration:";
show json::pretty(config.getAll());
```
### JSON Data Validator
```razen
# Import the json library
lib json;
# JSON Schema Validator class
class JsonValidator {
# Validate a JSON object against a schema
fun validate(map data, map schema) {
str errors = [];
# Check required fields
if (schema["required"] != null && Array.isArray(schema["required"])) {
for (num i = 0; i < schema["required"].length; i = i + 1) {
str field = schema["required"][i];
if (data[field] == null) {
errors.push("Missing required field: " + field);
}
}
}
# Check property types
if (schema["properties"] != null) {
for (str field in schema["properties"]) {
if (data[field] != null) {
str propSchema = schema["properties"][field];
# Check type
if (propSchema["type"] != null) {
str expectedType = propSchema["type"];
str actualType = typeof(data[field]);
# Handle special cases
if (expectedType == "array" && !Array.isArray(data[field])) {
errors.push("Field '" + field + "' should be an array");
} else if (expectedType == "object" && actualType != "object") {
errors.push("Field '" + field + "' should be an object");
} else if (expectedType == "number" && actualType != "number") {
errors.push("Field '" + field + "' should be a number");
} else if (expectedType == "string" && actualType != "string") {
errors.push("Field '" + field + "' should be a string");
} else if (expectedType == "boolean" && actualType != "boolean") {
errors.push("Field '" + field + "' should be a boolean");
}
}
# Check enum values
if (propSchema["enum"] != null && Array.isArray(propSchema["enum"])) {
bool found = false;
for (num i = 0; i < propSchema["enum"].length; i = i + 1) {
if (data[field] == propSchema["enum"][i]) {
found = true;
break;
}
}
if (!found) {
errors.push("Field '" + field + "' has invalid value. Expected one of: " + propSchema["enum"]);
}
}
# Check min/max for numbers
if (typeof(data[field]) == "number") {
if (propSchema["minimum"] != null && data[field] < propSchema["minimum"]) {
errors.push("Field '" + field + "' should be at least " + propSchema["minimum"]);
}
if (propSchema["maximum"] != null && data[field] > propSchema["maximum"]) {
errors.push("Field '" + field + "' should be at most " + propSchema["maximum"]);
}
}
# Check min/max length for strings
if (typeof(data[field]) == "string") {
if (propSchema["minLength"] != null && data[field].length < propSchema["minLength"]) {
errors.push("Field '" + field + "' should have at least " + propSchema["minLength"] + " characters");
}
if (propSchema["maxLength"] != null && data[field].length > propSchema["maxLength"]) {
errors.push("Field '" + field + "' should have at most " + propSchema["maxLength"] + " characters");
}
}
# Check array items
if (Array.isArray(data[field]) && propSchema["items"] != null) {
for (num i = 0; i < data[field].length; i = i + 1) {
str itemErrors = this.validate(data[field][i], propSchema["items"]);
for (num j = 0; j < itemErrors.length; j = j + 1) {
errors.push("In " + field + "[" + i + "]: " + itemErrors[j]);
}
}
}
# Check nested objects
if (typeof(data[field]) == "object" && !Array.isArray(data[field]) && propSchema["properties"] != null) {
str nestedErrors = this.validate(data[field], propSchema);
for (num i = 0; i < nestedErrors.length; i = i + 1) {
errors.push("In " + field + ": " + nestedErrors[i]);
}
}
}
}
}
return errors;
}
# Check if data is valid according to schema
fun isValid(map data, map schema) {
str errors = this.validate(data, schema);
return errors.length == 0;
}
# Get validation errors
fun getErrors(map data, map schema) {
return this.validate(data, schema);
}
}
# Define a user schema
map userSchema = {
"required": ["name", "email", "age"],
"properties": {
"name": {
"type": "string",
"minLength": 2,
"maxLength": 50
},
"email": {
"type": "string"
},
"age": {
"type": "number",
"minimum": 18,
"maximum": 120
},
"role": {
"type": "string",
"enum": ["admin", "user", "guest"]
},
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipcode": { "type": "string" }
}
},
"hobbies": {
"type": "array",
"items": {
"type": "string"
}
}
}
};
# Create a validator
JsonValidator validator = new JsonValidator();
# Valid user data
map validUser = {
"name": "John Doe",
"email": "john@example.com",
"age": 30,
"role": "admin",
"address": {
"street": "123 Main St",
"city": "Boston",
"zipcode": "02108"
},
"hobbies": ["reading", "hiking", "coding"]
};
# Invalid user data
map invalidUser = {
"name": "J", # Too short
"email": "john@example.com",
# Missing age
"role": "superuser", # Not in enum
"address": {
"street": "123 Main St",
"zipcode": "02108"
# Missing city
},
"hobbies": ["reading", 123] # Number in string array
};
# Validate the users
show "Valid user is valid: " + validator.isValid(validUser, userSchema);
show "Invalid user is valid: " + validator.isValid(invalidUser, userSchema);
# Show validation errors for invalid user
str errors = validator.getErrors(invalidUser, userSchema);
show "Validation errors:";
for (num i = 0; i < errors.length; i = i + 1) {
show "- " + errors[i];
}
```
## Practical Application: JSON API Client
```razen
# Import the json library
lib json;
# Simple JSON API Client class
class ApiClient {
# Constructor
init(str baseUrl) {
this.baseUrl = baseUrl;
}
# Simulate HTTP GET request
fun get(str endpoint) {
# In a real application, this would make an actual HTTP request
# For this example, we'll simulate responses
show "GET request to " + this.baseUrl + endpoint;
# Simulate different endpoints
if (endpoint == "/users") {
return this.simulateResponse(200, [
{"id": 1, "name": "John Doe", "email": "john@example.com"},
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"},
{"id": 3, "name": "Bob Johnson", "email": "bob@example.com"}
]);
} else if (endpoint.startsWith("/users/")) {
# Extract user ID from endpoint
str parts = endpoint.split("/");
num userId = Number(parts[parts.length - 1]);
if (userId == 1) {
return this.simulateResponse(200, {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"role": "admin",
"createdAt": "2023-01-15"
});
} else if (userId == 2) {
return this.simulateResponse(200, {
"id": 2,
"name": "Jane Smith",
"email": "jane@example.com",
"role": "user",
"createdAt": "2023-02-20"
});
} else {
return this.simulateResponse(404, {"error": "User not found"});
}
} else if (endpoint == "/posts") {
return this.simulateResponse(200, [
{"id": 1, "title": "First Post", "author": "John Doe"},
{"id": 2, "title": "Second Post", "author": "Jane Smith"},
{"id": 3, "title": "Third Post", "author": "John Doe"}
]);
} else {
return this.simulateResponse(404, {"error": "Endpoint not found"});
}
}
# Simulate HTTP POST request
fun post(str endpoint, map data) {
# In a real application, this would make an actual HTTP request
# For this example, we'll simulate responses
show "POST request to " + this.baseUrl + endpoint;
show "Request data: " + json::stringify(data);
# Simulate different endpoints
if (endpoint == "/users") {
# Validate required fields
if (!data["name"] || !data["email"]) {
return this.simulateResponse(400, {"error": "Name and email are required"});
}
# Simulate successful user creation
return this.simulateResponse(201, {
"id": 4,
"name": data["name"],
"email": data["email"],
"createdAt": "2023-06-15"
});
} else if (endpoint == "/login") {
# Validate credentials
if (!data["email"] || !data["password"]) {
return this.simulateResponse(400, {"error": "Email and password are required"});
}
# Simulate successful login
if (data["email"] == "john@example.com" && data["password"] == "password123") {
return this.simulateResponse(200, {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
});
} else {
return this.simulateResponse(401, {"error": "Invalid credentials"});
}
} else {
return this.simulateResponse(404, {"error": "Endpoint not found"});
}
}
# Simulate HTTP response
fun simulateResponse(num statusCode, val data) {
return {
"statusCode": statusCode,
"data": data,
"headers": {
"Content-Type": "application/json",
"Server": "Razen API Simulator"
}
};
}
# Process response
fun processResponse(map response) {
show "Response status: " + response["statusCode"];
if (response["statusCode"] >= 200 && response["statusCode"] < 300) {
show "Success! Response data:";
show json::pretty(response["data"]);
return response["data"];
} else {
show "Error! " + response["statusCode"];
if (response["data"] && response["data"]["error"]) {
show "Error message: " + response["data"]["error"];
}
return null;
}
}
}
# Create an API client
ApiClient client = new ApiClient("https://api.example.com");
# Make GET requests
map response1 = client.get("/users");
client.processResponse(response1);
map response2 = client.get("/users/1");
client.processResponse(response2);
map response3 = client.get("/users/999");
client.processResponse(response3);
# Make POST requests
map loginData = {
"email": "john@example.com",
"password": "password123"
};
map response4 = client.post("/login", loginData);
map loginResult = client.processResponse(response4);
if (loginResult != null) {
show "User logged in with token: " + loginResult["token"];
}
# Create a new user
map newUserData = {
"name": "Alice Brown",
"email": "alice@example.com"
};
map response5 = client.post("/users", newUserData);
client.processResponse(response5);
```
## Summary
The JSON Library in Razen provides essential functions for working with JSON data:
- `parse` for converting JSON strings to Razen objects
- `stringify` for converting Razen objects to JSON strings
- `pretty` for converting Razen objects to formatted JSON strings with indentation
These functions help you work with JSON data efficiently in your Razen programs, enabling interoperability with web APIs, configuration files, and data storage.