A functional, expression-oriented JSON transformation language combining JSONPath navigation, pipe operators, lambda expressions, and a rich standard library. Why functional? →
$.orders[*]
| where o => o.status == "confirmed" && o.total > 100
| select o => {
id: o.id,
customer: o.customerName.toUpper(),
date: o.createdAt.dateFormat("yyyy-MM-dd"),
priority: o.priority | match "high" => "URGENT", _ => "normal"
}
| orderBy o => o.total desc
| take 10
Available as a .NET library (NuGet) and a TypeScript/JavaScript package (npm). Both implementations are behaviorally identical, validated by a shared conformance test suite.
npm install @elwood-lang/coreimport { evaluate } from '@elwood-lang/core';
const data = {
users: [
{ name: 'Alice', age: 30, active: true },
{ name: 'Bob', age: 17, active: true },
{ name: 'Charlie', age: 25, active: false },
]
};
const result = evaluate('$.users[*] | where u => u.active && u.age >= 18 | select u => u.name', data);
console.log(result.value); // ["Alice"]dotnet add package Elwood.Core
dotnet add package Elwood.Jsonusing Elwood.Core;
using Elwood.Json;
var engine = new ElwoodEngine(JsonNodeValueFactory.Instance);
var input = JsonNodeValueFactory.Instance.Parse(json);
var result = engine.Evaluate("$.users[*] | where u => u.active | select u => u.name", input);
// result.Value contains the transformed JSONDownload a single binary (no runtime needed):
| Platform | Download |
|---|---|
| Windows | elwood-win-x64.exe |
| macOS | elwood-macos-x64 |
| Linux | elwood-linux-x64 |
Or install as a .NET tool: dotnet tool install --global Elwood.Cli
# Evaluate an expression
elwood eval "$.users[*] | where u => u.active" --input data.json
# Run a script
elwood run transform.elwood --input data.json
# Interactive REPL
elwood
# Pipe from stdin
echo '{"x": 42}' | elwood eval "$.x * 2"docker run -p 8080:8080 ghcr.io/max-favilli/elwood-apicurl -X POST http://localhost:8080/api/evaluate \
-H "Content-Type: application/json" \
-d '{"script": "$.users[*] | where u => u.active | select u => u.name", "input": {"users": [{"name": "Alice", "active": true}]}}'
# → {"success":true,"value":["Alice"],"diagnostics":[]}Integrates with any iPaaS, workflow tool, or language via HTTP.
Data flows left-to-right through pipe operators:
$.items[*] | where x => x.price > 10 | select x => x.name | distinct | take 5
| Operator | Description |
|---|---|
where |
Filter items by predicate |
select |
Transform each item |
selectMany |
Flatten nested arrays |
orderBy |
Sort (multi-key, asc/desc) |
groupBy |
Group by key → { key, items } |
distinct |
Remove duplicates |
take / skip |
Slice the array |
takeWhile |
Take items while predicate is true |
batch |
Chunk into groups of N |
join |
SQL-style join (inner/left/right/full) |
concat |
Join array into string |
reduce |
General-purpose fold |
count / sum / min / max |
Aggregation |
first / last |
Single element (optional predicate) |
any / all |
Boolean quantifiers |
match |
Pattern matching |
index |
Replace items with 0-based indices |
x => x.name.toLower()
(acc, item) => acc + item.price
let adults = $.users[*] | where u => u.age >= 18
let names = adults | select u => u.name
return { names: names, count: adults | count }
$.status | match
"active" => "#00FF00",
"retired" => "#FF0000",
_ => "#999999"
let lookup = memo id => $.categories[*] | first c => c.id == id
$.items[*] | select i => { name: i.name, category: lookup(i.catId).name }
iterate(1, x => x * 2) | take 5 // [1, 2, 4, 8, 16]
iterate({a:0, b:1}, s => {a:s.b, b:s.a+s.b}) // Fibonacci
| take 8 | select s => s.a // [0, 1, 1, 2, 3, 5, 8, 13]
{ ...original, newProp: "added", [dynamicKey]: computedValue }
String, numeric, datetime, crypto, null-checks, object manipulation, regex, URL encoding, and more. See the syntax reference.
The .NET engine uses lazy evaluation — pipe operators stream elements without materializing intermediate arrays. take(10) on a 100K-element pipeline processes ~15 items, not 100K.
where|select|take(10) on 100K items: 0.02ms avg
Full pipeline on 200K items (~73MB): ~2s, 41K rows/sec
Elwood/
├── spec/test-cases/ # 68 shared conformance test cases
├── dotnet/ # .NET implementation (C#)
│ ├── src/Elwood.Core/ # Parser, evaluator, built-in functions
│ ├── src/Elwood.Json/ # System.Text.Json adapter
│ └── src/Elwood.Cli/ # CLI tool
├── ts/ # TypeScript implementation
│ └── src/ # Lexer, parser, evaluator, built-ins
└── docs/ # Syntax reference, changelog, roadmap
Both implementations share the same conformance test suite (spec/test-cases/). Every test case includes an explanation file that serves as a tutorial.
- Playground — Try Elwood in your browser
- Syntax Reference — Complete language reference
- Why Functional? — Design philosophy and functional programming concepts
- Changelog — Version history
- Roadmap — Future plans
- Test Cases — 68 examples with explanations (also serve as tutorials)
dotnet build dotnet/Elwood.slnx
dotnet test dotnet/tests/Elwood.Core.Tests/cd ts
npm install
npm test