JCL vs Other Configuration Languages
This guide compares JCL with popular configuration languages: JSON, YAML, TOML, and HCL. We’ll examine the same configuration in each format and highlight JCL’s advantages.
Table of Contents
Overview
JCL (Jack-of-All Configuration Language) combines the best features from existing configuration languages while addressing their common pain points:
- Simple & readable like JSON
- Powerful like HCL
- Feature-rich with 70+ built-in functions
- Type-safe with optional type annotations
- Developer-friendly with LSP support and tooling
JCL vs JSON
The Problem with JSON
JSON is widely used but has significant limitations for configuration:
- No comments - Can’t document your configuration
- Strict syntax - Trailing commas cause errors
- Limited types - No date/time types, no way to express durations
- No variables - Repetition everywhere
- No functions - Can’t compute values
- Verbose - Excessive quotes and brackets
Side-by-Side Example
JSON:
{
"name": "web-server",
"version": "1.0.0",
"environment": "production",
"server": {
"host": "0.0.0.0",
"port": 80,
"workers": 4,
"timeout": 30
},
"database": {
"host": "db.example.com",
"port": 5432,
"name": "web-server_production",
"ssl": true
},
"features": {
"auth": true,
"api": true,
"debug": false
},
"cors_origins": [
"https://example.com",
"https://www.example.com"
]
}
JCL:
# Application Configuration
name = "web-server"
version = "1.0.0"
environment = "production"
# Server configuration
server = (
host = "0.0.0.0",
port = if environment == "production" then 80 else 8080,
workers = 4,
timeout = 30
)
# Database configuration
database = (
host = "db.example.com",
port = 5432,
name = "${name}_${environment}", # Computed from variables
ssl = environment == "production" # Conditional value
)
# Feature flags with computed values
features = (
auth = true,
api = true,
debug = environment != "production" # Automatically false in prod
)
cors_origins = [
"https://example.com",
"https://www.example.com"
]
JCL Advantages over JSON
✅ Comments - Document your configuration inline
✅ String interpolation - "${name}_${environment}" instead of repetition
✅ Conditionals - if environment == "production" then 80 else 8080
✅ No trailing comma issues - Commas are flexible
✅ Less verbose - No quotes on keys, cleaner syntax
✅ Computed values - environment != "production"
✅ 70+ built-in functions - Hash, encode, transform data
Migration from JSON
# Convert JSON to JCL
jcl-migrate config.json -o config.jcf
# Or to stdout
jcl-migrate config.json
JCL vs YAML
The Problem with YAML
YAML is popular but notorious for its issues:
- Indentation hell - Whitespace is syntax, hard to debug
- Implicit types -
nobecomesfalse,1.0might be a string - Complex spec - Anchors, aliases, merge keys are confusing
- Slow parsing - YAML parsers are complex and slow
- Security issues - YAML can execute code if not careful
- Tab vs space issues - Mixing tabs/spaces breaks everything
Side-by-Side Example
YAML:
name: web-server
version: 1.0.0
environment: production
server:
host: 0.0.0.0
port: 80 # Must manually change for dev
workers: 4
timeout: 30
database:
host: db.example.com
port: 5432
name: web-server_production # Manually duplicated
ssl: true # Must manually change for dev
features:
auth: true
api: true
debug: false # Must manually change for dev
cors_origins:
- https://example.com
- https://www.example.com
# With anchors (complex and hard to read)
defaults: &defaults
timeout: 30
retry: 3
service_a:
<<: *defaults
name: service-a
service_b:
<<: *defaults
name: service-b
JCL:
name = "web-server"
version = "1.0.0"
environment = "production"
server = (
host = "0.0.0.0",
port = if environment == "production" then 80 else 8080, # Automatic
workers = 4,
timeout = 30
)
database = (
host = "db.example.com",
port = 5432,
name = "${name}_${environment}", # No duplication
ssl = environment == "production" # Automatic
)
features = (
auth = true,
api = true,
debug = environment != "production" # Automatic
)
cors_origins = [
"https://example.com",
"https://www.example.com"
]
# Reusable values with functions (clear and simple)
fn default_service(name) = (
name = name,
timeout = 30,
retry = 3
)
service_a = default_service("service-a")
service_b = default_service("service-b")
JCL Advantages over YAML
✅ No indentation issues - Uses explicit delimiters () and []
✅ Explicit types - No surprising type coercions
✅ Simpler spec - Easy to learn, no anchors/aliases needed
✅ Fast parsing - Rust-based parser is 5-10x faster
✅ Safe by default - No code execution vulnerabilities
✅ Functions instead of anchors - More powerful and readable
✅ Tab/space agnostic - Whitespace is not syntax
Migration from YAML
# Convert YAML to JCL
jcl-migrate config.yaml -o config.jcf
# Or to stdout
jcl-migrate config.yaml
JCL vs TOML
The Problem with TOML
TOML is designed for simplicity but has limitations:
- Nested structures are verbose - Deep nesting gets ugly fast
- No functions - Can’t compute values
- No conditionals - Can’t adapt based on environment
- Limited expressions - No string interpolation
- Inconsistent syntax - Tables, inline tables, arrays of tables
Side-by-Side Example
TOML:
name = "web-server"
version = "1.0.0"
environment = "production"
[server]
host = "0.0.0.0"
port = 80 # Must manually change for dev
workers = 4
timeout = 30
[database]
host = "db.example.com"
port = 5432
name = "web-server_production" # Manually duplicated
ssl = true # Must manually change for dev
[features]
auth = true
api = true
debug = false # Must manually change for dev
cors_origins = [
"https://example.com",
"https://www.example.com"
]
# Deeply nested structures get verbose
[app.services.api.config.auth]
enabled = true
provider = "oauth2"
[app.services.api.config.auth.oauth2]
client_id = "abc123"
client_secret = "secret"
JCL:
name = "web-server"
version = "1.0.0"
environment = "production"
server = (
host = "0.0.0.0",
port = if environment == "production" then 80 else 8080,
workers = 4,
timeout = 30
)
database = (
host = "db.example.com",
port = 5432,
name = "${name}_${environment}",
ssl = environment == "production"
)
features = (
auth = true,
api = true,
debug = environment != "production"
)
cors_origins = [
"https://example.com",
"https://www.example.com"
]
# Deeply nested structures stay clean
app = (
services = (
api = (
config = (
auth = (
enabled = true,
provider = "oauth2",
oauth2 = (
client_id = "abc123",
client_secret = "secret"
)
)
)
)
)
)
JCL Advantages over TOML
✅ Cleaner nested structures - No [table.subtable.deep.nested] syntax
✅ String interpolation - "${name}_${environment}"
✅ Conditionals - if environment == "production" then 80 else 8080
✅ Functions - Transform and compute values
✅ Consistent syntax - One way to define objects
✅ More flexible - Lists of objects are natural
Migration from TOML
# Convert TOML to JCL
jcl-migrate config.toml -o config.jcf
# Or to stdout
jcl-migrate config.toml
JCL vs HCL
The Relationship with HCL
HCL (HashiCorp Configuration Language) is one of the most powerful configuration languages, used by Terraform. JCL shares some philosophical similarities with HCL but differs in key ways.
Side-by-Side Example
HCL (Terraform):
variable "environment" {
default = "production"
}
resource "aws_instance" "web" {
ami = "ami-123456"
instance_type = var.environment == "production" ? "t3.large" : "t3.micro"
tags = {
Name = "web-server"
Environment = var.environment
}
}
locals {
db_name = "web-server_${var.environment}"
port = var.environment == "production" ? 80 : 8080
}
output "database_name" {
value = local.db_name
}
JCL:
environment = "production"
aws_instance_web = (
ami = "ami-123456",
instance_type = if environment == "production" then "t3.large" else "t3.micro",
tags = (
Name = "web-server",
Environment = environment
)
)
# Locals are just regular variables
db_name = "web-server_${environment}"
port = if environment == "production" then 80 else 8080
# Outputs are just values
database_name = db_name
Differences from HCL
| Feature | HCL | JCL |
|---|---|---|
| Purpose | Infrastructure-as-Code | General-purpose configuration |
| Blocks | resource "type" "name" { } |
Maps with keys: resource = ( ... ) |
| Variables | var.name |
Direct: name |
| Locals | local.name |
Direct: name |
| Functions | ~100 built-in, Terraform-specific | 70+ built-in, general-purpose |
| Learning curve | Steeper (blocks, modules, state) | Gentler (simpler concepts) |
| Use cases | Terraform, infrastructure | Any application configuration |
JCL Advantages over HCL
✅ Simpler syntax - No resource/data/variable blocks
✅ General purpose - Not tied to infrastructure tools
✅ Consistent - Everything is a variable or expression
✅ No special variable syntax - Use name instead of var.name
✅ Easier to learn - Fewer concepts to master
✅ More flexible - Not opinionated about structure
When to Use HCL vs JCL
Use HCL when:
- You’re using Terraform or other HashiCorp tools
- You need infrastructure-specific features (state, providers)
- You’re managing cloud resources
Use JCL when:
- You need general application configuration
- You want simpler syntax for config files
- You’re not using Terraform
- You want faster parsing and evaluation
- You need better tooling (LSP, formatters, validators)
Feature Comparison Matrix
| Feature | JCL | JSON | YAML | TOML | HCL |
|---|---|---|---|---|---|
| Comments | ✅ | ❌ | ✅ | ✅ | ✅ |
| String interpolation | ✅ | ❌ | ❌ | ❌ | ✅ |
| Conditionals | ✅ | ❌ | ❌ | ❌ | ✅ |
| Functions | ✅ (70+) | ❌ | ❌ | ❌ | ✅ (100+) |
| For loops | ✅ | ❌ | ❌ | ❌ | ✅ |
| Type annotations | ✅ | ❌ | ❌ | ❌ | ❌ |
| Schema validation | ✅ | ✅ | ✅ | ❌ | ❌ |
| LSP support | ✅ | ✅ | ✅ | ✅ | ✅ |
| Fast parsing | ✅ | ✅ | ❌ | ✅ | ✅ |
| No indentation | ✅ | ✅ | ❌ | ✅ | ✅ |
| Learning curve | Low | Very Low | Low | Low | Medium |
| Code formatter | ✅ | ✅ | ✅ | ✅ | ✅ |
| Migration tools | ✅ | N/A | N/A | N/A | ❌ |
Migration Guide
JCL provides tools to migrate from other formats seamlessly.
From JSON
# Single file
jcl-migrate config.json -o config.jcf
# Multiple files
for file in *.json; do
jcl-migrate "$file" -o "${file%.json}.jcf"
done
# Format the results
jcl-fmt *.jcf
From YAML
# Single file
jcl-migrate config.yaml -o config.jcf
# Multiple files
for file in *.yaml *.yml; do
jcl-migrate "$file" -o "${file%.*}.jcf"
done
# Format the results
jcl-fmt *.jcf
From TOML
# Single file
jcl-migrate config.toml -o config.jcf
# Multiple files
for file in *.toml; do
jcl-migrate "$file" -o "${file%.toml}.jcf"
done
# Format the results
jcl-fmt *.jcf
Manual Enhancement After Migration
After migration, you can enhance your configs with JCL features:
Before (migrated from JSON):
environment = "production"
port = 80
debug = false
After (enhanced with JCL features):
environment = "production"
port = if environment == "production" then 80 else 8080
debug = environment != "production"
Validation After Migration
# Validate against a schema
jcl-validate config.jcf --schema schema.yaml
# Check formatting
jcl-fmt --check config.jcf
# Test evaluation
jcl eval config.jcf
Real-World Examples
Example 1: Application Configuration
YAML (old):
app:
name: myapp
env: production
database:
host: db.prod.example.com
port: 5432
name: myapp_production
redis:
host: redis.prod.example.com
port: 6379
JCL (new):
env = "production"
app_name = "myapp"
app = (
name = app_name,
env = env
)
database = (
host = "db.${env}.example.com",
port = 5432,
name = "${app_name}_${env}"
)
redis = (
host = "redis.${env}.example.com",
port = 6379
)
Example 2: Feature Flags
JSON (old):
{
"features": {
"new_ui": true,
"beta_api": false,
"analytics": true,
"debug_mode": false,
"rate_limiting": true
}
}
JCL (new):
env = "production"
beta_users = ["user123", "user456"]
current_user = "user789"
features = (
new_ui = true,
beta_api = contains(beta_users, current_user),
analytics = true,
debug_mode = env != "production",
rate_limiting = env == "production"
)
Example 3: Multi-Environment Config
TOML (old - needs 3 separate files):
# config.production.toml
[server]
port = 80
workers = 8
[database]
host = "db.prod.example.com"
pool_size = 50
JCL (new - one file for all environments):
environment = "production" # Change this one line
server = (
port = if environment == "production" then 80 else 8080,
workers = if environment == "production" then 8 else 2
)
database = (
host = "db.${environment}.example.com",
pool_size = if environment == "production" then 50 else 10
)
Summary
JCL combines the best features of existing configuration languages:
- Simpler than YAML - No indentation issues
- More powerful than JSON - Comments, functions, conditionals
- More flexible than TOML - Better nested structures
- More accessible than HCL - General-purpose, easier to learn
Why Choose JCL?
- Productivity - Write less, express more
- Safety - Type annotations and schema validation
- Tooling - LSP, formatters, validators, benchmarks
- Migration - Easy to migrate from existing formats
- Performance - Fast Rust-based implementation
- Flexibility - Use it for any configuration need
Getting Started
# Install JCL
cargo install jcl
# Migrate your configs
jcl-migrate old-config.json -o config.jcf
# Format them
jcl-fmt config.jcf
# Start using JCL!
jcl eval config.jcf