JexLang: Write Once, Validate Everywhere β€” A Platform-Agnostic Expression Language

JexLang: Write Once, Validate Everywhere β€” A Platform-Agnostic Expression Language

Source: Dev.to

🎯 The Problem We All Face ## 😫 The Real Pain Points ## 1. Code Duplication Nightmare ## 2. Subtle Platform Differences ## 3. Hardcoded Logic = Deployment Pain ## 4. Testing Overhead ## πŸ’‘ What If There Was a Better Way? ## πŸš€ Introducing JexLang ## Why JavaScript-Like Syntax? ## πŸ› οΈ How JexLang Works ## 1. Lexer (Tokenizer) ## 2. Parser ## 3. Interpreter ## πŸ“– Real-World Use Cases ## 1. Dynamic Form Validations ## 2. Business Rule Engines ## 3. Conditional UI Rendering ## 4. Workflow Automation ## 5. Feature Flags with Complex Logic ## ⚑ Quick Examples ## Basic Expressions ## Working with Context ## Built-in Functions ## Ternary Expressions ## πŸ”§ Getting Started ## JavaScript/TypeScript ## πŸ†š How JexLang Compares ## πŸ—ΊοΈ Roadmap ## πŸ™ Call to Action ## πŸ“š Resources ## 🎬 Final Thoughts Picture this: You're building a multi-platform application. A React web app, an Android mobile app, a Spring Boot backend, and maybe an iOS app in the pipeline. Everything seems manageable until you hit a familiar wall β€” validation logic. Your product manager walks in and says: "We need to validate that users are 18+ years old, their email matches the company domain, and if they select 'Premium' plan, they must have a valid credit card." Simple enough, right? Now multiply that across platforms: Now imagine you have 50+ validation rules across your application. Each platform has its own implementation. Each implementation needs to be tested. Each implementation can have subtle bugs. Welcome to validation hell. πŸ”₯ Every validation rule exists in multiple places. When a business rule changes, you're updating code in 3-4 different repositories. Miss one? Congratulations, you now have inconsistent behavior that will haunt your debugging sessions. Each language handles things slightly differently: These subtle differences lead to bugs that only appear on specific platforms β€” the worst kind of bugs. Need to change a validation rule? That's a code change. Code change means PR review, testing, staging, and deployment. For each platform. Separately. What if the validation logic could live in your database or configuration? What if non-developers could modify simple rules without touching code? 50 validation rules Γ— 4 platforms = 200 test cases (at minimum). And you need to ensure they all behave identically. Good luck with that. What if you could write your validation logic once in a simple, familiar syntax: And have it execute identically on: No platform-specific quirks. No code duplication. No deployment overhead. This is exactly why I built JexLang. JexLang is a lightweight, platform-agnostic expression language with JavaScript-like syntax. It's designed to solve one problem exceptionally well: write logic once, run it everywhere with consistent behavior. Let's be honest β€” most developers are familiar with JavaScript syntax. Even if you're primarily a Java or Swift developer, you've likely encountered JavaScript at some point. By using a familiar syntax, JexLang has virtually zero learning curve. If you can write this: You already know JexLang. JexLang consists of three core components: Breaks down your expression into tokens: Converts tokens into an Abstract Syntax Tree (AST): Evaluates the AST against your context data and returns the result. The magic? The same AST structure and evaluation logic is implemented identically in each language. Same parser rules. Same operator precedence. Same type coercion. Same results. Store validation rules in your backend and share them across all platforms: Your React app, Android app, and backend all fetch these rules and evaluate them using JexLang. One source of truth. Configure business logic without code deployments: Marketing team wants to change the Gold tier threshold from 100 to 150? Update the database. No deployment needed. Show/hide UI elements based on configurable rules: Build workflow engines where conditions are user-configurable: Go beyond simple boolean flags: Future considerations: JexLang is open source and in its early days. I've built it to solve real problems I've faced, but I know the community will find use cases I never imagined. Here's how you can help: ⭐ Star the repository β€” It helps with visibility and motivates continued development. πŸ§ͺ Try it in your projects β€” Put it through real-world scenarios and see how it holds up. πŸ› Report issues β€” Found a bug? Edge case that doesn't work? Let me know! πŸ’‘ Suggest features β€” What would make JexLang more useful for your use case? πŸ“’ Share with your network β€” Know someone who might benefit? Spread the word! Cross-platform development is hard enough without having to maintain identical logic in multiple languages. JexLang aims to eliminate that pain point by providing a single, consistent way to express logic that works everywhere. Whether you're building form validations, business rule engines, workflow automation, or config-driven applications, JexLang gives you the power to write once and run everywhere. I'm excited to see what you build with it. Have questions or feedback? Drop a comment below or reach out on GitHub. I'd love to hear from you! #OpenSource #JavaScript #TypeScript #Java #Swift #ExpressionLanguage #FormValidation #CrossPlatform #DeveloperTools #Programming Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: // JavaScript (Web/React Native/NodeJS) if (user.age >= 18 && user.email.endsWith('@company.com') && (user.plan !== 'Premium' || user.hasValidCard)) { // valid } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // JavaScript (Web/React Native/NodeJS) if (user.age >= 18 && user.email.endsWith('@company.com') && (user.plan !== 'Premium' || user.hasValidCard)) { // valid } CODE_BLOCK: // JavaScript (Web/React Native/NodeJS) if (user.age >= 18 && user.email.endsWith('@company.com') && (user.plan !== 'Premium' || user.hasValidCard)) { // valid } CODE_BLOCK: // Java (Backend/Android) if (user.getAge() >= 18 && user.getEmail().endsWith("@company.com") && (!user.getPlan().equals("Premium") || user.hasValidCard())) { // valid } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Java (Backend/Android) if (user.getAge() >= 18 && user.getEmail().endsWith("@company.com") && (!user.getPlan().equals("Premium") || user.hasValidCard())) { // valid } CODE_BLOCK: // Java (Backend/Android) if (user.getAge() >= 18 && user.getEmail().endsWith("@company.com") && (!user.getPlan().equals("Premium") || user.hasValidCard())) { // valid } CODE_BLOCK: // Swift (iOS) if user.age >= 18 && user.email.hasSuffix("@company.com") && (user.plan != "Premium" || user.hasValidCard) { // valid } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Swift (iOS) if user.age >= 18 && user.email.hasSuffix("@company.com") && (user.plan != "Premium" || user.hasValidCard) { // valid } CODE_BLOCK: // Swift (iOS) if user.age >= 18 && user.email.hasSuffix("@company.com") && (user.plan != "Premium" || user.hasValidCard) { // valid } CODE_BLOCK: user.age >= 18 && endsWith(user.email, "@company.com") && (user.plan != "Premium" || user.hasValidCard) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: user.age >= 18 && endsWith(user.email, "@company.com") && (user.plan != "Premium" || user.hasValidCard) CODE_BLOCK: user.age >= 18 && endsWith(user.email, "@company.com") && (user.plan != "Premium" || user.hasValidCard) CODE_BLOCK: user.age >= 18 && user.active === true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: user.age >= 18 && user.active === true CODE_BLOCK: user.age >= 18 && user.active === true CODE_BLOCK: "user.age >= 18" β†’ [IDENTIFIER:user, DOT, IDENTIFIER:age, GTE, NUMBER:18] Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: "user.age >= 18" β†’ [IDENTIFIER:user, DOT, IDENTIFIER:age, GTE, NUMBER:18] CODE_BLOCK: "user.age >= 18" β†’ [IDENTIFIER:user, DOT, IDENTIFIER:age, GTE, NUMBER:18] CODE_BLOCK: BinaryExpression { left: MemberExpression { object: "user", property: "age" }, operator: ">=", right: NumericLiteral { value: 18 } } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: BinaryExpression { left: MemberExpression { object: "user", property: "age" }, operator: ">=", right: NumericLiteral { value: 18 } } CODE_BLOCK: BinaryExpression { left: MemberExpression { object: "user", property: "age" }, operator: ">=", right: NumericLiteral { value: 18 } } CODE_BLOCK: { "fields": [ { "name": "email", "validations": [ { "rule": "required(email)", "message": "Email is required" }, { "rule": "contains(email, '@')", "message": "Please enter a valid email" } ] }, { "name": "age", "validations": [ { "rule": "age >= 18", "message": "You must be 18 or older" } ] } ] } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: { "fields": [ { "name": "email", "validations": [ { "rule": "required(email)", "message": "Email is required" }, { "rule": "contains(email, '@')", "message": "Please enter a valid email" } ] }, { "name": "age", "validations": [ { "rule": "age >= 18", "message": "You must be 18 or older" } ] } ] } CODE_BLOCK: { "fields": [ { "name": "email", "validations": [ { "rule": "required(email)", "message": "Email is required" }, { "rule": "contains(email, '@')", "message": "Please enter a valid email" } ] }, { "name": "age", "validations": [ { "rule": "age >= 18", "message": "You must be 18 or older" } ] } ] } COMMAND_BLOCK: // Rules stored in database const discountRules = [ { condition: "customer.tier === 'Gold' && cart.total > 100", discount: 20 }, { condition: "customer.tier === 'Silver' && cart.total > 200", discount: 10 }, { condition: "cart.items.length > 5", discount: 5 } ]; // Evaluate at runtime const applicableRule = discountRules.find(rule => jexlang.evaluate(rule.condition, { customer, cart }) ); Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: // Rules stored in database const discountRules = [ { condition: "customer.tier === 'Gold' && cart.total > 100", discount: 20 }, { condition: "customer.tier === 'Silver' && cart.total > 200", discount: 10 }, { condition: "cart.items.length > 5", discount: 5 } ]; // Evaluate at runtime const applicableRule = discountRules.find(rule => jexlang.evaluate(rule.condition, { customer, cart }) ); COMMAND_BLOCK: // Rules stored in database const discountRules = [ { condition: "customer.tier === 'Gold' && cart.total > 100", discount: 20 }, { condition: "customer.tier === 'Silver' && cart.total > 200", discount: 10 }, { condition: "cart.items.length > 5", discount: 5 } ]; // Evaluate at runtime const applicableRule = discountRules.find(rule => jexlang.evaluate(rule.condition, { customer, cart }) ); COMMAND_BLOCK: { "component": "PremiumBanner", "showWhen": "user.subscription === 'free' && user.daysActive > 30" } Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: { "component": "PremiumBanner", "showWhen": "user.subscription === 'free' && user.daysActive > 30" } COMMAND_BLOCK: { "component": "PremiumBanner", "showWhen": "user.subscription === 'free' && user.daysActive > 30" } COMMAND_BLOCK: IF order.total > 1000 AND customer.verified === true β†’ Route to priority fulfillment IF order.items.length > 10 β†’ Apply bulk processing IF contains(order.shippingAddress.country, 'EU') β†’ Apply VAT calculation Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: IF order.total > 1000 AND customer.verified === true β†’ Route to priority fulfillment IF order.items.length > 10 β†’ Apply bulk processing IF contains(order.shippingAddress.country, 'EU') β†’ Apply VAT calculation COMMAND_BLOCK: IF order.total > 1000 AND customer.verified === true β†’ Route to priority fulfillment IF order.items.length > 10 β†’ Apply bulk processing IF contains(order.shippingAddress.country, 'EU') β†’ Apply VAT calculation COMMAND_BLOCK: const featureRules = { "newCheckout": "user.region === 'US' && user.accountAge > 30 && !user.hasActiveIssues", "betaFeatures": "user.role === 'developer' || contains(user.email, '@internal.com')" }; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: const featureRules = { "newCheckout": "user.region === 'US' && user.accountAge > 30 && !user.hasActiveIssues", "betaFeatures": "user.role === 'developer' || contains(user.email, '@internal.com')" }; COMMAND_BLOCK: const featureRules = { "newCheckout": "user.region === 'US' && user.accountAge > 30 && !user.hasActiveIssues", "betaFeatures": "user.role === 'developer' || contains(user.email, '@internal.com')" }; CODE_BLOCK: // Arithmetic jexlang.evaluate("10 + 5 * 2") // 20 // Comparisons jexlang.evaluate("100 >= 50") // true // Logical operators jexlang.evaluate("true && false || true") // true // String operations jexlang.evaluate("'Hello' + ' ' + 'World'") // "Hello World" Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // Arithmetic jexlang.evaluate("10 + 5 * 2") // 20 // Comparisons jexlang.evaluate("100 >= 50") // true // Logical operators jexlang.evaluate("true && false || true") // true // String operations jexlang.evaluate("'Hello' + ' ' + 'World'") // "Hello World" CODE_BLOCK: // Arithmetic jexlang.evaluate("10 + 5 * 2") // 20 // Comparisons jexlang.evaluate("100 >= 50") // true // Logical operators jexlang.evaluate("true && false || true") // true // String operations jexlang.evaluate("'Hello' + ' ' + 'World'") // "Hello World" CODE_BLOCK: const context = { user: { name: "John Doe", age: 28, email: "[email protected]", roles: ["admin", "editor"] }, settings: { maxUploadSize: 100, allowedFormats: ["jpg", "png", "pdf"] } }; // Access nested properties jexlang.evaluate("user.name", context) // "John Doe" // Complex conditions jexlang.evaluate("user.age >= 18 && contains(user.email, '@company.com')", context) // true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: const context = { user: { name: "John Doe", age: 28, email: "[email protected]", roles: ["admin", "editor"] }, settings: { maxUploadSize: 100, allowedFormats: ["jpg", "png", "pdf"] } }; // Access nested properties jexlang.evaluate("user.name", context) // "John Doe" // Complex conditions jexlang.evaluate("user.age >= 18 && contains(user.email, '@company.com')", context) // true CODE_BLOCK: const context = { user: { name: "John Doe", age: 28, email: "[email protected]", roles: ["admin", "editor"] }, settings: { maxUploadSize: 100, allowedFormats: ["jpg", "png", "pdf"] } }; // Access nested properties jexlang.evaluate("user.name", context) // "John Doe" // Complex conditions jexlang.evaluate("user.age >= 18 && contains(user.email, '@company.com')", context) // true CODE_BLOCK: // String functions jexlang.evaluate("length('Hello')") // 5 jexlang.evaluate("upper('hello')") // "HELLO" jexlang.evaluate("startsWith('Hello World', 'Hello')") // true // Array functions jexlang.evaluate("length(items)", { items: [1, 2, 3] }) // 3 // Type checking jexlang.evaluate("isNumber(42)") // true jexlang.evaluate("isString('hello')") // true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: // String functions jexlang.evaluate("length('Hello')") // 5 jexlang.evaluate("upper('hello')") // "HELLO" jexlang.evaluate("startsWith('Hello World', 'Hello')") // true // Array functions jexlang.evaluate("length(items)", { items: [1, 2, 3] }) // 3 // Type checking jexlang.evaluate("isNumber(42)") // true jexlang.evaluate("isString('hello')") // true CODE_BLOCK: // String functions jexlang.evaluate("length('Hello')") // 5 jexlang.evaluate("upper('hello')") // "HELLO" jexlang.evaluate("startsWith('Hello World', 'Hello')") // true // Array functions jexlang.evaluate("length(items)", { items: [1, 2, 3] }) // 3 // Type checking jexlang.evaluate("isNumber(42)") // true jexlang.evaluate("isString('hello')") // true CODE_BLOCK: jexlang.evaluate("user.age >= 18 ? 'Adult' : 'Minor'", { user: { age: 25 } }) // "Adult" Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: jexlang.evaluate("user.age >= 18 ? 'Adult' : 'Minor'", { user: { age: 25 } }) // "Adult" CODE_BLOCK: jexlang.evaluate("user.age >= 18 ? 'Adult' : 'Minor'", { user: { age: 25 } }) // "Adult" COMMAND_BLOCK: npm install jexlang-ts Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: npm install jexlang-ts COMMAND_BLOCK: npm install jexlang-ts CODE_BLOCK: import { JexEvaluator } from "jexlang-ts"; const jexlang = new JexEvaluator(); // Simple expression const result = jexlang.evaluate("1 + 2 * 3"); // 7 // With context const context = { user: { name: "John", age: 30 } }; const isAdult = jexlang.evaluate("user.age >= 18", context); // true Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: import { JexEvaluator } from "jexlang-ts"; const jexlang = new JexEvaluator(); // Simple expression const result = jexlang.evaluate("1 + 2 * 3"); // 7 // With context const context = { user: { name: "John", age: 30 } }; const isAdult = jexlang.evaluate("user.age >= 18", context); // true CODE_BLOCK: import { JexEvaluator } from "jexlang-ts"; const jexlang = new JexEvaluator(); // Simple expression const result = jexlang.evaluate("1 + 2 * 3"); // 7 // With context const context = { user: { name: "John", age: 30 } }; const isAdult = jexlang.evaluate("user.age >= 18", context); // true CODE_BLOCK: implementation 'io.github.vinaykumar0339:jexlang-java:1.0.0' Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: implementation 'io.github.vinaykumar0339:jexlang-java:1.0.0' CODE_BLOCK: implementation 'io.github.vinaykumar0339:jexlang-java:1.0.0' CODE_BLOCK: <dependency> <groupId>io.github.vinaykumar0339</groupId> <artifactId>jexlang-java</artifactId> <version>1.0.0</version> </dependency> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: <dependency> <groupId>io.github.vinaykumar0339</groupId> <artifactId>jexlang-java</artifactId> <version>1.0.0</version> </dependency> CODE_BLOCK: <dependency> <groupId>io.github.vinaykumar0339</groupId> <artifactId>jexlang-java</artifactId> <version>1.0.0</version> </dependency> COMMAND_BLOCK: import io.github.vinaykumar0339.evaluator.JexEvaluator; JexEvaluator jexlang = new JexEvaluator(); // Simple expression Object result = jexlang.evaluate("1 + 2 * 3"); // 7 // With context Map<String, Object> user = Map.of("name", "John", "age", 30); Map<String, Object> context = Map.of("user", user); Boolean isAdult = (Boolean) jexlang.evaluate("user.age >= 18", context); // true Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: import io.github.vinaykumar0339.evaluator.JexEvaluator; JexEvaluator jexlang = new JexEvaluator(); // Simple expression Object result = jexlang.evaluate("1 + 2 * 3"); // 7 // With context Map<String, Object> user = Map.of("name", "John", "age", 30); Map<String, Object> context = Map.of("user", user); Boolean isAdult = (Boolean) jexlang.evaluate("user.age >= 18", context); // true COMMAND_BLOCK: import io.github.vinaykumar0339.evaluator.JexEvaluator; JexEvaluator jexlang = new JexEvaluator(); // Simple expression Object result = jexlang.evaluate("1 + 2 * 3"); // 7 // With context Map<String, Object> user = Map.of("name", "John", "age", 30); Map<String, Object> context = Map.of("user", user); Boolean isAdult = (Boolean) jexlang.evaluate("user.age >= 18", context); // true - String comparison: === vs .equals() vs == - Null handling: undefined vs null vs Optional<> - Type coercion: JavaScript's quirky "5" + 3 = "53" vs Java's strict typing - βœ… JavaScript/TypeScript (Browser & Node.js) - βœ… Java (Android & Backend) - 🚧 Swift (iOS) β€” coming soon! - Python support - ⭐ Star the repository β€” It helps with visibility and motivates continued development. - πŸ§ͺ Try it in your projects β€” Put it through real-world scenarios and see how it holds up. - πŸ› Report issues β€” Found a bug? Edge case that doesn't work? Let me know! - πŸ’‘ Suggest features β€” What would make JexLang more useful for your use case? - πŸ“’ Share with your network β€” Know someone who might benefit? Spread the word!