Mazen Alsenih
Home
Skills
Blog
Projects
Products
Contact

Mazen Alsenih

Senior Full Stack Developer crafting exceptional digital experiences with modern technologies and best practices.

Remote · Germany
Let's Work Together

Quick Links

  • Home
  • Skills
  • Blog
  • Contact

Connect

LinkedIn
Connect professionally
GitHub
View my code
Xing
Professional network
X (Twitter)
Follow my thoughts
© 2026 Mazen Alsenih•Made with and lots of ☕
Site actively maintained
Built withNext.js•TypeScript•Tailwind CSS•MDX
Elwood: The Scripting Language That Makes JSON Transformation Feel Natural

Elwood: The Scripting Language That Makes JSON Transformation Feel Natural

June 3, 2026

There's a specific kind of pain that every backend developer knows.

You've got a JSON payload coming in from some upstream service — deeply nested, inconsistently shaped, full of fields you don't care about and missing the ones you do. And now you need to map it into the shape your downstream system expects. So you write a wall of TypeScript or C# — nested maps, filter calls, manual object literals, null checks scattered everywhere — and it works, but nobody's happy about it. You know it. The next person who reads it knows it. It's the kind of code you write and immediately feel bad about.

My manager and dear friend Max (Massimiliano Favilli) had already built several extended JSON parsers. For a while, they did the job well enough, but over time they became increasingly hard to maintain. As the extended JSON syntax grew more complex, adding new features started to feel painful, and that familiar “this works, but nobody feels great about it” feeling kept coming back.

So he built Elwood.


What Elwood Actually Is

Elwood is a functional, expression-oriented DSL (domain-specific language) purpose-built for JSON transformation. It combines JSONPath-style navigation with a pipe-based operator model, named lambdas, pattern matching, let bindings, and a standard library of 70+ built-in methods. It ships as a .NET NuGet package, a TypeScript/npm package, and a CLI binary — with both implementations validated against a shared conformance test suite so you get identical behavior regardless of which stack you're running on.

Here's the canonical example from the readme that made me stop and think:

Read that out loud and it maps almost 1:1 to what you'd describe in English: "Take confirmed orders over 100, reshape them, sort by total descending, give me the top 10." That's the goal. That's the whole design philosophy — transformations should read like intent, not like implementation.


The Problem With the Status Quo

Before getting into the features, it's worth being honest about what the alternatives actually look like in practice.

jq is powerful, but its syntax is famously hostile. It has a steep learning curve, error messages that give you nothing, and a functional model that requires you to think inside-out once you go beyond trivial queries. It's a great tool for one-liners in a terminal; it's a miserable tool for anything you need to maintain.

Handwritten imperative code works, but it scales poorly. The bigger the transformation, the harder it is to follow, and refactoring it is risky because the logic is tangled up with the iteration and null-handling boilerplate.

JSONata is the closest comparison. It's a solid, mature DSL that's been around a while. But it's JavaScript-centric, the functional composition model is different, and it doesn't have first-class .NET support with behavioral parity.

Elwood's answer to all of this is pipes — left-to-right data flow with named lambdas that let you see exactly what's happening at each step. Once you read it a few times, the mental model just clicks.


Language Features That Actually Matter in Production

Pipes and Named Lambdas

The pipe model is the foundation. Data flows left to right through a sequence of operators, each one taking the previous output as input:

The named lambda (x => ...) is key. You give the current item a name and then work with it. No implicit @, no this, no magic dollar signs mid-expression. It's legible and it stays legible as the pipeline grows.

Let Bindings for Multi-Step Logic

One of my favourite features for complex transformations. You can break logic into named intermediate values and compose them at the end:

This is the difference between a transformation script that reads like a runbook and one that reads like a puzzle. When you come back to it three months later, you want the former.

Pattern Matching

Clean, exhaustive, readable:

That wildcard _ as a fallback arm is particularly good for the "everything else" case that you'd otherwise handle with a ternary inside a ternary.

Memoized Functions

This one is a genuine production win. When you're doing lookups against reference data during a transformation — say, resolving a category ID to a category name across thousands of rows — you don't want to re-evaluate that expression for every item:

memo wraps the lambda with memoization automatically. Results are cached by argument. For lookup-heavy pipelines this is the kind of thing that can meaningfully change your throughput numbers.

Lazy Evaluation

The .NET engine evaluates lazily — pipe operators stream elements without materialising intermediate arrays. If you take(10) at the end of a pipeline that starts with 100,000 items, it processes roughly 15 items, not 100,000. The repo's own benchmark puts a where | select | take(10) on a 100K-item dataset at 0.02ms average. A full pipeline across 200K items (~73MB) runs at about 41K rows per second.

That's not "fast for a DSL." That's just fast.


Using It in Practice

TypeScript / Node.js

That's it. No setup, no schema definition, no config. You hand it a string expression and a JS object, you get a result back.

.NET

The .NET and TypeScript implementations run against the same 68-case conformance test suite. So if your expression works in the playground or in a Node.js test, it works identically in the C# service. That cross-platform behavioral parity is genuinely rare and genuinely useful when your backend is polyglot.

CLI for One-Offs and Debugging

The REPL in particular is great for developing a transformation — you can load a sample payload and iterate on the expression live before embedding it into code.

Docker API for Language-Agnostic Integration

This is the path if you need to call Elwood from a service that's neither .NET nor Node — Python, Go, Java, whatever your stack is. Wrap the API in a thin client and you get the full language from anywhere.


Where This Fits in a Real System

The use cases that keep coming up in practice:

ETL pipelines. You're pulling data from one system and pushing it into another. The shapes don't match. Elwood scripts can live in config or in your database and be evaluated at runtime, which means you can update transformation logic without deploying code.

API response shaping. Your backend aggregates from multiple upstream services and needs to stitch together a coherent response for the client. Instead of building a bespoke DTO mapping layer in code, an Elwood expression describes the transformation declaratively.

Webhook processing. Incoming events from third-party services are rarely shaped the way you want. An Elwood script at the ingestion point normalises the payload before it hits your core logic.

Data validation and enrichment. The where + pattern matching combination makes it natural to both filter and tag data in the same pass, with the conditional logic visible and readable rather than buried in imperative code.


The Honest Assessment

Elwood is not trying to be a general-purpose language. It can't make HTTP calls, it doesn't have async/await, it doesn't manage state. It does one thing: transform JSON. And it does that thing with a level of readability and composability that I haven't found elsewhere.

The feature I keep coming back to is how well the language telegraphs intent. When you write an Elwood script and come back to it later, you can read it like prose. That's not an accident — it's the design goal. Functional, expression-oriented, left-to-right. It's the same reason people like SQL: when the language matches the shape of the problem, the code stops feeling like translation and starts feeling like description.

The 68-case conformance test suite with explanations also doubles as excellent documentation. Each test case comes with an explanation file that walks through what's happening and why. If you're learning the language, that's the fastest path in.

Max has been iterating on this steadily — v0.7.16 dropped on the same day this post is going out, and the repo has 149 commits and 23 releases at this point. The roadmap and changelog are both in the docs folder if you want to see where it's headed.


If you're doing any non-trivial JSON transformation work — in .NET, in Node, or anywhere you can hit an HTTP endpoint — it's worth 30 minutes in the playground. The expression model clicks fast and you'll know immediately whether it fits your workflow.

For me, it already has.


Questions or thoughts? Reach out via the contact form.

Mazen Alsenih

Written by

Mazen Alsenih

Back to blog posts
#Elwood#JSON#Data Transformation#DSL#.NET#TypeScript#Production