Format and validate JSON data with ease (oh... and Jexl). Jexlate uses Jexl to allow for complex tranformations and translations able to be stored in a JSON object.
A prime use-case would be to store transformations in a database without needing to maintain them in source code. This allows for dynamic transformations to be applied to data without needing to redeploy the application code.
npm i jexlate
# or
yarn add jexlateJexlate is a JSON transformation library that uses Jexl to transform data from a source object to a target object format. A very simple example is shown below, simply changing 2 keys to CamelCase, and create a new key with the full name concatenated from the first and last name:
import { Jexlate } from 'jexlate';
// Define a transformation template
const template = {
FirstName: {
from: 'first_name',
},
LastName: {
from: 'last_name',
},
FullName: {
from: 'first_name + " " + last_name',
},
};
// Instantiate a new Jexlate instance
const jexlate = new Jexlate(template, options);
// Transform data
const data = {
first_name: 'John',
last_name: 'Doe',
};
const transformedData = jexlate.transform(data);
/**
* transformedData:
* {
* FirstName: 'John',
* LastName: 'Doe',
* FullName: 'John Doe',
* }
*/The Jexlate constructor accepts an optional options object as the second argument. The following options are available:
const options = {
transforms: {
uppercase: (value) => value.toUpperCase(),
// 'first_name|uppercase' => 'JOHN'
},
functions: {
concat: (...args) => args.join(''),
// 'concat(first_name, " ", last_name)' => 'John Doe'
},
binaryOps: {
add: {
precedence: 1,
fn: (left, right) => left + right,
},
// '10 add 5' => 15
},
};Jexlate also supports transforming streams of data. This is useful for transforming large datasets without needing to load the entire dataset into memory.
import { Jexlate } from 'jexlate';
// Define template and options...
const jexlate = new Jexlate(template, options);
// Create a Jexlate transform stream
const jexlateTransformStream = jexlate.stream();
// Open a readable stream and pipe it through the Jexlate transform stream
const readableStream = someReadableStream();
const writableStream = someWritableStream();
// Pipe the readable stream through the Jexlate transform stream and then to the writable stream
readableStream.pipe(jexlateTransformStream).pipe(writableStream);The stream method accepts an optional options object as the first argument. The following options are available:
const options = {
onError: string<throw | collect>
errorCollector: []
};By default the stream will throw on any validation or processing errors. If you want to continue the stream and collect errors, you can set onError to collect.
An optional errorCollector array can be passed to collect errors when onError is set to collect.
const template = {
FirstName: {
from: 'first_name',
},
LastName: {
from: 'last_name',
},
FullName: {
from: 'first_name + " " + last_name',
},
};Templates can use dot-notation to access nested objects in the source data.
const data = {
company: {
name: 'Acme Inc.',
streetAddress: '123 Main St.',
},
};
const template = {
Company: {
Name: {
from: 'company.name',
},
Address: {
Street: {
from: 'company.streetAddress',
},
},
},
};
/**
* transformedData:
* {
* Company: {
* Name: 'Acme Inc.',
* Address: {
* Street: '123 Main St.',
* },
* },
* }
*/Arrays can be transformed by using the [] syntax in the from value.
const data = {
users: [{ name: 'John Doe' }, { name: 'Jane Doe' }],
};
const template = {
Users: {
from: 'users[]',
values: {
Name: {
from: 'name',
},
},
},
};
/**
* transformedData:
* {
* Users: [
* { Name: 'John Doe' },
* { Name: 'Jane Doe' },
* ],
* }
*/Arrays can be created and acted on from string values by using the splitOn property:
const data = {
tags: 'tag1,tag2,tag3',
};
const template = {
Tags: {
from: 'tags[]',
splitOn: ',',
values: {
TagName: {
from: 'value',
},
},
},
};
/**
* transformedData:
* {
* Tags: [
* { TagName: 'tag1' },
* { TagName: 'tag2' },
* { TagName: 'tag3' },
* ],
* }
*/Jexlate supports conditional transformations using the if key in the template.
const data = {
age: 25,
};
const template = {
IsAdult: {
from: 'age',
if: 'age >= 18',
},
CanRetire: {
from: 'age',
if: 'age >= 65',
},
};
/**
* transformedData:
* {
* IsAdult: true,
* CanRetire: false,
* }
*/Jexlate can be configured to require certain properties to be present in the source data. If a required property is missing, an error will be thrown. This will also throw if a condition or transformation fails or returns false or undefined
const template = {
FirstName: {
from: 'first_name',
required: true,
},
LastName: {
from: 'last_name',
required: true,
},
};
const data = {
first_name: 'John',
};
// Throws an error because 'last_name' is missing
const transformedData = jexlate.transform(data);
// JSON contained in the error object:
// { required: ['last_name'] }Jexlate can be configured to validate the transformed data using a JSON schema. If the transformed data does not match the schema, an error will be thrown. Validation uses Jexl syntax, functions, and binary operators.
const template = {
Age: {
from: 'age',
validate: 'age > 25',
},
};
const data = {
age: 24,
};
// Throws an error because 'age' is less than 25
const transformedData = jexlate.transform(data);
// JSON contained in the error object:
// { invalid: [{ test: 'age > 25', value: 24 }] }Jexl will attempt to evaluate the from value as an expression. If you want to use a static value, you can wrap the value one of the functions below:
const template = {
StringValue: {
from: 'value(string)',
},
NumberValue: {
from: 'number(43)',
},
IsTrue: {
from: 'boolean(true)',
},
IsFalse: {
from: 'boolean(false)',
},
NullValue: {
from: 'null()',
},
};Jexlate will attempt to coerce values to the correct type based on the from value. If you want to force a value to be a specific type, you can use the following functions:
const template = {
AgeAsString: {
from: 'age',
as: 'string', // 'string' | 'number' | 'boolean' | 'json'
},
};
const data = {
age: 25,
};
// transformedData: { AgeAsString: '25' }...
yarnyarn test
# or
yarn test:watchyarn build
yarn build:types