diff --git a/readme.md b/readme.md index b065132..1b8ba06 100644 --- a/readme.md +++ b/readme.md @@ -404,6 +404,61 @@ JsonTemplateEngine.create(`let a = {{$.a.b.c}};`, { We can use compile time expressions to generate a template and then recompile it as expression. Refer these examples [simple compilation](test/scenarios/compile_time_expressions/template.jt) and [complex compilation](test/scenarios/compile_time_expressions/two_level_path_processing.jt) for more details. +### Mappings +If you are familiar with [JSON Paths](https://goessner.net/articles/JsonPath/index.html#), you can easily begin working with JSON templates by leveraging your existing knowledge through the mappings feature. + +**Example:** +* Let's say we want to tranform the following data. +* Input: +```json +{ + "a": { + "foo": 1, + "bar": 2 + }, + "b": [ + { + "firstName": "foo", + "lastName": "bar" + }, + { + "firstName": "fizz", + "lastName": "buzz" + } + ] +} +``` +* Output: +```json +{ + "foo": 1, + "bar": 2, + "items":[ + { + "name": "foo bar" + }, + { + "name": "fizz buzz" + } + ] +} +``` +* Mappings: +```json +[ + { + "description": "Copies properties of a to root level in the output", + "input": "$.a", + "output": "$", + }, + { + "description": "Combines first and last name in the output", + "input": "$.b[*].(@.firstName + ' ' + @.lastName)", + "output": "$.items[*].name" + } +] +``` +For more examples, refer [Mappings](test/scenarios/mappings/) ### Comments diff --git a/src/types.ts b/src/types.ts index 0e12c3b..81de864 100644 --- a/src/types.ts +++ b/src/types.ts @@ -276,6 +276,7 @@ export interface ThrowExpression extends Expression { } export type FlatMappingPaths = { + description?: string; input: string; output: string; }; diff --git a/src/utils/converter.ts b/src/utils/converter.ts index e4f26a4..b6d740a 100644 --- a/src/utils/converter.ts +++ b/src/utils/converter.ts @@ -205,6 +205,16 @@ function processFlatMappingPart( } function processFlatMapping(flatMapping, currentOutputPropsAST) { + if (flatMapping.outputExpr.parts.length === 0) { + currentOutputPropsAST.push({ + type: SyntaxType.OBJECT_PROP_EXPR, + value: { + type: SyntaxType.SPREAD_EXPR, + value: flatMapping.inputExpr, + }, + } as ObjectPropExpression); + return; + } for (let i = 0; i < flatMapping.outputExpr.parts.length; i++) { currentOutputPropsAST = processFlatMappingPart(flatMapping, i, currentOutputPropsAST); } @@ -216,15 +226,7 @@ export function convertToObjectMapping( flatMappingASTs: FlatMappingAST[], ): ObjectExpression | PathExpression { const outputAST: ObjectExpression = createObjectExpression(); - - for (let i = 0; i < flatMappingASTs.length; i++) { - const flatMapping = flatMappingASTs[i]; - if (flatMapping.outputExpr.parts.length === 0) { - if (outputAST.props.length === 0 && i === flatMappingASTs.length - 1) { - return flatMapping.inputExpr; - } - throw new Error(`Invalid output mapping: ${flatMapping.output}`); - } + for (const flatMapping of flatMappingASTs) { processFlatMapping(flatMapping, outputAST.props); } diff --git a/test/scenarios/mappings/data.ts b/test/scenarios/mappings/data.ts index b760ec6..5fc6967 100644 --- a/test/scenarios/mappings/data.ts +++ b/test/scenarios/mappings/data.ts @@ -1,4 +1,3 @@ -import { PathType } from '../../../src'; import type { Scenario } from '../../types'; const input = { @@ -6,6 +5,10 @@ const input = { discount: 10, coupon: 'DISCOUNT', events: ['purchase', 'custom'], + details: { + name: 'Purchase', + timestamp: 1630000000, + }, context: { traits: { email: 'dummy@example.com', @@ -153,16 +156,6 @@ export const data: Scenario[] = [ mappingsPath: 'invalid_object_mappings.json', error: 'Invalid object mapping', }, - { - description: 'Root mapping is used after other mappings', - mappingsPath: 'invalid_root_mapping1.json', - error: 'Invalid output mapping', - }, - { - description: 'Root mapping is used before other mappings', - mappingsPath: 'invalid_root_mapping2.json', - error: 'Invalid output mapping', - }, { mappingsPath: 'mappings_with_root_fields.json', input, @@ -271,9 +264,19 @@ export const data: Scenario[] = [ }, }, { - mappingsPath: 'only_root_mapping.json', - input: { a: 1 }, - output: { a: 1 }, + mappingsPath: 'root_mappings.json', + input, + output: { + traits: { + email: 'dummy@example.com', + first_name: 'John', + last_name: 'Doe', + phone: '1234567890', + }, + event_names: ['purchase', 'custom'], + name: 'Purchase', + timestamp: 1630000000, + }, }, { mappingsPath: 'transformations.json', diff --git a/test/scenarios/mappings/invalid_root_mapping1.json b/test/scenarios/mappings/invalid_root_mapping1.json deleted file mode 100644 index 7e5e0cb..0000000 --- a/test/scenarios/mappings/invalid_root_mapping1.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "input": "$.a", - "output": "$.b" - }, - { - "input": "$", - "output": "$" - } -] diff --git a/test/scenarios/mappings/invalid_root_mapping2.json b/test/scenarios/mappings/invalid_root_mapping2.json deleted file mode 100644 index 90d5e5e..0000000 --- a/test/scenarios/mappings/invalid_root_mapping2.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "input": "$", - "output": "$" - }, - { - "input": "$.a", - "output": "$.b" - } -] diff --git a/test/scenarios/mappings/only_root_mapping.json b/test/scenarios/mappings/only_root_mapping.json deleted file mode 100644 index cdebbda..0000000 --- a/test/scenarios/mappings/only_root_mapping.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "input": "$", - "output": "$" - } -] diff --git a/test/scenarios/mappings/root_mappings.json b/test/scenarios/mappings/root_mappings.json new file mode 100644 index 0000000..7a136d0 --- /dev/null +++ b/test/scenarios/mappings/root_mappings.json @@ -0,0 +1,14 @@ +[ + { + "input": "$.context", + "output": "$" + }, + { + "input": "$.events", + "output": "$.event_names" + }, + { + "input": "$.details", + "output": "$" + } +]