@firebrandtech/jexlate v1.4.2
Jexlate
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.
Installation
npm i jexlate
# or
yarn add jexlateUsage
Jexlate 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',
* }
*/Options
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
},
};Streams
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);Stream Options
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.
Operations and Configation
Basic Transformations
const template = {
FirstName: {
from: 'first_name',
},
LastName: {
from: 'last_name',
},
FullName: {
from: 'first_name + " " + last_name',
},
};Nested Onjects
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
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' },
* ],
* }
*/Splitting Arrays from String Values
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' },
* ],
* }
*/Conditional Transformations
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,
* }
*/Required Properties
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'] }Validation
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 }] }Static Values
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()',
},
};Type Coercion
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' }...
Development
Install Dependencies
yarnRun Tests
yarn test
# or
yarn test:watchBuild
yarn build
yarn build:types