It is a Typescript package that provides a simple JSON Programming Language, allowing you to execute a safe logic in Frontend or Backend (NodeJS). Furthermore, it can be stored in the database and rendered to the Frontend-Side to execute/run some business logic.
JsonLang is designed to be extendable. You can define new rules with sync/async handlers.
npm install jsonlang-js
- Typescript. It's a strongly typed npm package
- the JsonLang structure is Simple and Optimized. Its structure and rules have a shortcut to make your JSON in a small size.
- Its structure is always Consistent. i.e.
{"$R": "R1", "$I": ["value1", "value2", {"$R": "R2", "$I": [...] }, ...] }
. - Safe & Secure. Each Rule has a secure handler.
- Extendable. Easy to add new rules.
- Sync/Async. All rules in JsonLang are sync rules, but you can extend it and add async rules.
- DRY. You can pass any rule result in a variable to be used in another rule which makes JsonLang JSON more optimized
execute = async (jsonLang: IJsonLangParams, data?: {}, options?: { sync: false }): Promise<RuleResult>
Execute is used to run the JsonLang and takes two parameters.
- JsonLang: check the Structure
- Data: schemaless data object to read/write to it. To get data use the Rule Data
Execute is the Async
version of JsonLang, use it to run all builtin rules and any extended Sync
or Async
Rules
execute = (jsonLang: IJsonLangParams, data: {} | undefined, options: { sync: true }): RuleResult
Execute is the Sync
version of JsonLang, use it to run all builtin rules and any extended Sync
Rules
registerOne = (ruleDefinition: RuleDefinition, ruleHandler: RuleHandler): void
Extend JsonLang by adding 2 params
- ruleDefinition: Object of
- identifier:
{ name: string, shortcut?: string, group?: string }
,name
(required) is theRule
name,shortcut
(optional) is the shortcut. i.eSum
is thename
, and+
is theshortcut
, andgroup
a name of group to categorize/group set of rule under group. - inputs: JsonSchema to define the rule inputs
- output: JsonSchema to define the rule output
- identifier:
- ruleHandler: Object of two props sync & async, you need to pass the implementation Function
(...inputs: RuleInput[]) => RuleResult)
,inputs
(required) is array of all inputs needs for the handler check Input in Structure, anddata
is the schemaless data check Data in the Execute Section
registerMany(rules: Rules): void
registerMany allows registering a Map()
of rules. The Map key
is RuleDefinition
, and the Map value
is the RuleHandler
This way is the best practice way to extend JSONLang using decorators
@JsonLangExtension('Test') // here pass the groupName
export class DataRules {
@RuleExtension({
identifier: { name: 'RuleOne' },
inputs: { inputOne: {type: 'string', enum: ['TP', 'RF']}, inputTwo: {type: 'number'} },
output: { type: 'boolean', default: false }
})
RuleOne(
inputOne: string,
inputTwo: number
): boolean {
// do the implementation here
}
@RuleExtension({
identifier: { name: 'RuleTwo', shortcut: 'R' },
inputs: { type: 'array', items: { type: 'string' } },
output: { type: 'number' }
})
RuleTwo(...inputs: string[]): number {
// do the implementation here
}
}
JsonLang have three main parameters:
- $R: (
String
) is the rule name itself. i.e.and
,or
,==
,>
. - $I: (
any[]
) is an array of inputs which will be passed to theRule
handler/function, their type depends on theRule
handler, or it can be a nested rule - $O?: (
Symbol [Optional]
), is an optional field, it accept a name of variable which used to save the Rule result in a variable and can be called in any other rule by{ "$R": "Var": "$I": ["variableX"] }
. The output value should be unique. If you define the same value more than once, the last one will override the value of the previous one.
-
Var
- Input[]: Array (Size: 1), for the Variable name of the Output.
- Output: Any (depends on the output value).
- Description: used to get the value of any
Output
from any rules, Check the Output part.
-
Data
- Input[]?: Array (Size: 1) Enum of "External" or "Internal", defaulted with "External".
- Output: any.
- Description: if the Input is
["External"]
it will return the schemaless data object which you pass it to the execute method, else if the input is["Internal"]
, it will return the value passed from the parent rule like filter in array rules.
-
And or &&
- Input[]: Array (Size: Unlimited).
- Output: Boolean (true or false).
- Description: Do the
Anding
operation, if any value inInput[]
has a value of (null, 0, false), it will returnfalse
, else it will returntrue
.
-
Or or ||
- Input[]: Array (Size: Unlimited).
- Output: Boolean (true or false).
- Description: Do the
Oring
operation, if all values inInput[]
has a value of (null, 0, false), it will returnfalse
, else it will returntrue
.
-
Equal or ==
- Input[]: Array (Size: 2).
- Output: Boolean (true or false).
- Description: It takes an array of 2 inputs to compare if element one
Equal
element two or not.
-
NotEqual or =
- Input[]: Array (Size: 2).
- Output: Boolean (true or false).
- Description: It takes an array of 2 inputs to compare if element one
Not Equal
to element two or not.
-
Not or !
- Input[]: Array (Size: 1).
- Output: Boolean (true or false).
- Description: It takes an array of 1 input inverts its value. If it
true
it will returnfalse
and vice versa.
-
GreaterThan or >
- Input[]: Array (Size: 2).
- Output: Boolean (true or false).
- Description: It takes an array of 2 inputs to compare if element one
Greater Than
element two or not.
-
LessThan or <
- Input[]: Array (Size: 2).
- Output: Boolean (true or false).
- Description: It takes an array of 2 inputs to compare if element one
Less Than
element two or not.
-
GreaterThanOrEqual or >=
- Input[]: Array (Size: 2).
- Output: Boolean (true or false).
- Description: It takes an array of 2 inputs to compare if element one
Greater Than or Equal
element two or not.
-
LessThanOrEqual or <=
- Input[]: Array (Size: 2).
- Output: Boolean (true or false).
- Description: It takes an array of 2 inputs to compare if element one
Less Than or Equal
element two or not.
-
IsNumber
- Input[]: Array (Size: 1).
- Output: Boolean (true or false).
- Description: Check if the value dataType is a number or not.
-
Sum or +
- Input[]: Array (Size: unlimited).
- Output: number.
- Description: Used to Sum all values. i.e.
Input1 + Input2 + .... + InputN
.
-
Subtract or -
- Input[]: Array (Size: unlimited).
- Output: number.
- Description: Used to Subtract all values. i.e.
Input1 - Input2 - .... - InputN
.
-
Multiply or *
- Input[]: Array (Size: unlimited).
- Output: number.
- Description: Used to Multiply all values. i.e.
Input1 * Input2 * .... * InputN
.
-
Divide or /
- Input[]: Array (Size: unlimited).
- Output: number.
- Description: Used to Divide all values. i.e.
Input1 / Input2 / .... / InputN
.
-
Get [In Progress]
- Input[]: Array (Size: 3) {path: string, defaultValue?: any, data:{}}.
- Output: Any.
- Description: It accepts two inputs, the 1st one (required) is a path to get the Data, and the 2nd one (optional) is a default value of the path is not found. the
path
must follow the dotted stylevar1.var2
for nested fields and brackets with number for arraysvar1.var2[3].var3
-
Set [In Progress]
- Input[]: Array (Size: 3) {path: string, value: any, data:{}}.
- Output: Any.
- Description: It accepts two inputs. The 1st one (required) is a path to update/mutate the Data, and the 2nd one is the value to set. the
path
must follow the dotted stylevar1.var2
for nested fields and brackets with number for arraysvar1.var2[3].var3
. If thepath
does not exist, theSet
Rule will create it.
-
Update [In Progress]
- Input[]: Array (Size: 3) {path: string, value: any, data:{}}.
- Output: Any.
- Description: It accepts two inputs. The 1st one (required) is a path to update/mutate the Data, and the 2nd one is the value to update. the
path
must follow the dotted stylevar1.var2
for nested fields and brackets with number for arraysvar1.var2[3].var3
. If thepath
does not exist, theUpdate
rule won't do anything.
-
Delete [In Progress]
- Input[]: Array (Size: 2) {path: string, data:{}}.
- Output: Any.
- Description: It accepts two inputs, a path to mutate the Data by deleting a field in the request path. the
path
must follow the dotted stylevar1.var2
for nested fields and brackets with number for arraysvar1.var2[3].var3
. If thepath
does not exist, theDelete
rule won't do anything.
-
All
- Input[]: Array (Size: Unlimited).
- Output: Array (Size: Unlimited).
- Description: It takes an array of inputs and returns them again. It is used to run a list of
nested Rules
.
-
Filter
- Input[]: Array (Size: 2) {elements: any[], rule: IJsonLangParams}.
- Output: Any[].
- Description: It accepts array of elements with any type to filter them using nested/inner rules, the filter rule will pass each elements as a Data with scope
Internal
, to access it by the inner rules, you will need to use Data Rule with scope local, check this example.
-
Map
- Input[]: Array (Size: 2) {elements: any[], rule: IJsonLangParams}.
- Output: Any[].
- Description: It accepts array of elements with any type to map them using nested/inner rules, the filter rule will pass each elements as a Data with scope
Internal
, to access it by the inner rules, you will need to use Data Rule with scope local.
-
Foreach
- Input[]: Array (Size: 2) {elements: any[], rule: IJsonLangParams}.
- Output: true.
- Description: It accepts array of elements with any type to iterate over them using nested/inner rules, the filter rule will pass each elements as a Data with scope
Internal
, to access it by the inner rules, you will need to use Data Rule with scope local.
-
Flatten
- Input[]: Array (Size: 2) {elements: any[], level?: number}.
- Output: true.
- Description: It accepts array of elements with any type to flatten this array with any level.
import { JsonLang } from 'jsonlang-js';
const jsonLang = new JsonLang();
jsonLang.execute( { "$R": "LessThan" , "$I": [10, 20] }, undefined, { sync: true } ); // true
// or for short
jsonLang.execute( { "$R": "<" , "$I": [10, 20] }, undefined, { sync: true } ); // true
// or use the async function
jsonLang.execute( { "$R": "<" , "$I": [10, 20] } )
.then(result => {
console.log(result); // true
});
import { JsonLang } from 'jsonlang-js';
const jsonLang = new JsonLang();
const result = jsonLang.execute({
$R: '+',
$I: [
{
$R: '+',
$I: [
1,
{ $R: '*', $I: [2, 3] },
5
]
},
{
$R: '+',
$I: [
1,
{ $R: '*', $I: [3, 3], $O: 'x' },
5
]
},
{ $R: 'Var', $I: ['x'] },
{ $R: 'Get', $I: ['user.age', null, { $R: 'Data', $I: ['External'] }] }
]
}, { user: { name: 'test', age: 100 } }, { sync: true });
console.log(result);
// 136
import { JsonLang } from 'jsonlang-js';
const jsonLang = new JsonLang();
const result = jsonLang.execute({ $R: 'All', $I: [
{
$R: 'Filter',
$I: [[1, 3, 5], { $R: '>', $I: [{ $R: 'Data', $I: ['Internal'] }, 2] }]
},
{
$R: 'Filter',
$I: [
{ $R: 'Get', $I: ['data.test', null, { $R: 'Data', $I: ['External'] }] },
{ $R: '<', $I: [{ $R: 'Data', $I: ['Internal'] }, 500] }
]
}
] }, { data: { id: 'test', test: [100, 300, 700] } }, { sync: true });
console.log(result);
// [ [ 3, 5 ], [ 100, 300 ] ]
import { JsonLang } from 'jsonlang-js';
const jsonLang = new JsonLang();
jsonLang.registerOne({ name: 'Test', shortcut: 't' }, {
sync: (input: any) => { return `${input} Test` },
async: async (input: any) => { return `${input} Test` }
});
const result = await jsonLang.execute({
$R: 'Test',
$I: [
{ $R: 'Get', $I: ['user.age', null, { $R: 'Data' }] }
]
}, { user: { name: 'test', age: 100 } }, { sync: true });
console.log(result);
// 100 Test
You can extend JsonLang and add any logic you want from well-known sync/async packages like lodash, moment, ajv, axios, mysql, mongoose, ...etc.
Just use the register functions and follow its structure to add whatever you want.
JsonLang can be extended with any function, and you can override the existing rules, but make sure that any method you will add won't:
- Have any security issue
- Async method without timeout or with unhandled errors
- Block the event loop in backend nodejs https://nodejs.org/en/docs/guides/dont-block-the-event-loop/
- abuse the CPU or the memory
This library uses Array.map
and Array.reduce
, so it's not exactly Internet Explorer 8 friendly.
- Adding more math, logic, object, array, date, and casting methods.
- Allow importing packages to extend JsonLang easily.
- Provide plugins to wrap well-known packages like MathJs, Jsonata, Axios, Lodash, MomentJs, ...etc.
- Make a UI Editor generate the JSON of JsonLang.
- Allow Writing Rules as expression. i.e.
And(true, Or(1, Get('var1.var2', 0)))
. - Public website has good documentation, for example, playground to try JsonLang, use-cases session has many ideas for using JsonLang.
JsonLang is MIT licensed