Skip to content

NFun as data language (JSON/TOML alternative) #100

@tmteam

Description

@tmteam

Idea

NFun expressions already look like data literals. Structs, arrays, numbers, strings — all are valid NFun expressions. Essentially, NFun is already a superset of JSON in expressiveness.

The proposal is to formalize and promote NFun as a data language — an alternative to JSON/TOML/YAML for configs, data, and templates.

Three usage modes

Mode Capabilities Analogue
Constant Only literals, structs, arrays. No computations JSON, TOML
Computable + expressions, variables, functions, if/else Jsonnet, Dhall
With external variables + host provides context (user, order, ...) Current main NFun use case

All three modes are the same NFun — the host decides what's available.

Example: NFun document

# ===== Server configuration =====

appName = 'MyService'
version = '2.1.0'
debug   = true

# ----- Network -----

host = 'localhost'
port = if (debug) 8080 else 443
baseUrl = 'http://{host}:{port}/api/v1'

# ----- Database -----

db = {
  host     = 'db.internal'
  port     = 5432
  name     = 'myservice'
  pool     = if (debug) 5 else 50
  connStr  = 'Host={db.host};Port={db.port};Database={db.name};Pooling=true;MaxPool={db.pool}'
}

# ----- Limits -----

maxUploadMb    = 50
maxUploadSize  = maxUploadMb * 1024 * 1024
requestTimeout = 30
retryDelays    = [1, 2, 5, 10, 30]

# ----- Logging -----

logging = {
  level   = if (debug) 'trace' else 'warning'
  console = debug
  file    = '/var/log/{appName}.log'
  maxSize = 100 * 1024 * 1024
  rotate  = 5
}

# ----- External services -----

services = [
  { name = 'auth',    url = 'http://auth.internal:3001',    timeout = 5  }
  { name = 'storage', url = 'http://storage.internal:3002', timeout = 30 }
  { name = 'notify',  url = 'http://notify.internal:3003',  timeout = 10 }
]

# ----- CORS -----

cors = {
  origins = if (debug)
    ['http://localhost:3000', 'http://localhost:5173']
  else
    ['https://myservice.com', 'https://app.myservice.com']

  methods = ['GET', 'POST', 'PUT', 'DELETE']
  maxAge  = 24 * 60 * 60
}

Key advantage: embeddability

Unlike Jsonnet/Dhall/CUE — NFun is not an external tool, but a library:

// Constant / computable — result is a value
var config = Funny.Calc<ServerConfig>(configText);

// With external variables — host provides context
var rule = Funny.ForCalc<Context, Result>(ruleText);

The host application controls the sandbox. No filesystem, no network — only what was explicitly provided.

What needs to be done

Comparison with JSON

The same config in JSON:

{
  "appName": "MyService",
  "version": "2.1.0",
  "debug": true,
  "host": "localhost",
  "port": 8080,
  "baseUrl": "http://localhost:8080/api/v1",
  "db": {
    "host": "db.internal",
    "port": 5432,
    "name": "myservice",
    "pool": 5,
    "connStr": "Host=db.internal;Port=5432;Database=myservice;Pooling=true;MaxPool=5"
  },
  "maxUploadMb": 50,
  "maxUploadSize": 52428800,
  "requestTimeout": 30,
  "retryDelays": [1, 2, 5, 10, 30],
  "logging": {
    "level": "trace",
    "console": true,
    "file": "/var/log/MyService.log",
    "maxSize": 104857600,
    "rotate": 5
  },
  "services": [
    { "name": "auth",    "url": "http://auth.internal:3001",    "timeout": 5  },
    { "name": "storage", "url": "http://storage.internal:3002", "timeout": 30 },
    { "name": "notify",  "url": "http://notify.internal:3003",  "timeout": 10 }
  ],
  "cors": {
    "origins": ["http://localhost:3000", "http://localhost:5173"],
    "methods": ["GET", "POST", "PUT", "DELETE"],
    "maxAge": 86400
  }
}

Key differences:

  • No comments — impossible to explain why port is 8080 or what maxSize means
  • No computations — 52428800 instead of 50 * 1024 * 1024, 86400 instead of 24 * 60 * 60
  • No variables — "MyService" repeated in file, baseUrl must be hardcoded with duplicated host/port
  • No conditionals — need separate config.dev.json / config.prod.json files
  • Mandatory double quotes on keys — visual noise
  • connStr must be manually assembled with all values hardcoded and duplicated

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions