Razen Examples

Learn Razen through practical examples and code snippets.

JSON Library (json)

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