n8n-nodes-data-mapper 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SilentJMA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,251 @@
1
- # Temporary Holding Version
1
+ # n8n-nodes-data-mapper
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/n8n-nodes-data-mapper.svg)](https://www.npmjs.com/package/n8n-nodes-data-mapper)
4
+ [![CI](https://github.com/SilentJMA/n8n-nodes-data-mapper/actions/workflows/ci.yml/badge.svg)](https://github.com/SilentJMA/n8n-nodes-data-mapper/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ An [n8n](https://n8n.io) community node that **maps data from one structure to another**. Rename and nest fields, map whole arrays with `[*]`, convert types, format dates, translate codes with lookup tables, and convert between **JSON and XML**, all in one node.
8
+
9
+ Use it to turn an API response into your internal format, shape database rows into a request payload, read a supplier's XML into clean JSON, or pull just the useful parts out of a large payload.
10
+
11
+ - [Installation](#installation)
12
+ - [Quick start](#quick-start)
13
+ - [Paths](#paths)
14
+ - [Parameters](#parameters)
15
+ - [Transforms](#transforms)
16
+ - [Reusable JSON definitions](#reusable-json-definitions)
17
+ - [Working with XML](#working-with-xml)
18
+ - [Examples](#examples)
19
+ - [Compatibility](#compatibility)
20
+ - [Development](#development)
21
+ - [License](#license)
22
+
23
+ ## Installation
24
+
25
+ ### From the n8n UI (recommended)
26
+
27
+ 1. In n8n, open **Settings → Community Nodes**.
28
+ 2. Select **Install**, enter `n8n-nodes-data-mapper`, accept the risk notice and install.
29
+ 3. Search for **Data Mapper** in the node panel.
30
+
31
+ ### Manually (self-hosted, Docker, Unraid)
32
+
33
+ The package has no runtime dependencies, so you can drop it into n8n's `custom` folder:
34
+
35
+ ```bash
36
+ # inside your n8n data folder (mapped to /home/node/.n8n in the official Docker image)
37
+ mkdir -p custom && cd custom
38
+ npm pack n8n-nodes-data-mapper
39
+ tar -xzf n8n-nodes-data-mapper-*.tgz && mv package n8n-nodes-data-mapper
40
+ ```
41
+
42
+ Restart n8n afterwards.
43
+
44
+ ## Quick start
45
+
46
+ Given this input item:
47
+
48
+ ```json
49
+ {
50
+ "id": "A1",
51
+ "customer": { "name": " Ann ", "country": "NL" },
52
+ "items": [
53
+ { "sku": "P-1", "qty": "2" },
54
+ { "sku": "P-2", "qty": "1" }
55
+ ]
56
+ }
57
+ ```
58
+
59
+ add these mappings:
60
+
61
+ | Source Path | Target Path | Transform |
62
+ | --- | --- | --- |
63
+ | `id` | `orderId` | |
64
+ | `customer.name` | `buyer.name` | Trim |
65
+ | `customer.country` | `buyer.country` | Lookup Table: `NL=Netherlands` |
66
+ | `items[*].sku` | `lines[*].productCode` | |
67
+ | `items[*].qty` | `lines[*].quantity` | To Integer |
68
+ | *Fixed Value* `EUR` | `lines[*].currency` | |
69
+
70
+ and get:
71
+
72
+ ```json
73
+ {
74
+ "orderId": "A1",
75
+ "buyer": { "name": "Ann", "country": "Netherlands" },
76
+ "lines": [
77
+ { "productCode": "P-1", "quantity": 2, "currency": "EUR" },
78
+ { "productCode": "P-2", "quantity": 1, "currency": "EUR" }
79
+ ]
80
+ }
81
+ ```
82
+
83
+ Import [`examples/data-mapper-examples.json`](examples/data-mapper-examples.json) into n8n (**Workflows → Import from File**) to try this and the XML examples.
84
+
85
+ ## Paths
86
+
87
+ | Syntax | Meaning |
88
+ | --- | --- |
89
+ | `customer.name` | Nested field |
90
+ | `items[0].sku` | One array element |
91
+ | `items[*].sku` | Every array element |
92
+ | `["first name"]` or `data["x-id"]` | Keys with spaces or special characters |
93
+ | *(empty)* | The whole input (source) or the whole output (target, value must be an object) |
94
+
95
+ How `[*]` in the source and target line up:
96
+
97
+ - **Same number of `[*]`**: elements are matched by position. `items[*].sku` → `lines[*].code` writes `lines[0].code`, `lines[1].code`, … Mappings that share a target array build up the same elements, so several mappings can fill in one array of objects.
98
+ - **Fewer `[*]` in the target**: the extra levels are collected into a list. `items[*].sku` → `skus` gives `["P-1", "P-2"]`. `orders[*].items[*].sku` → `orders[*].skus` gives one list per order, and an empty list for an order with no items.
99
+ - **More `[*]` in the target**: not allowed, except for a fixed value, which is written to every existing element (`EUR` → `lines[*].currency`).
100
+ - A single object where `[*]` expects an array is treated as a one-element array. This matters for XML, where one child element is not an array.
101
+
102
+ Mappings run in order, and a later mapping to the same target overwrites an earlier one.
103
+
104
+ ## Parameters
105
+
106
+ | Parameter | Description |
107
+ | --- | --- |
108
+ | **Input** | *Item JSON* maps each incoming item. *Text Field* first parses JSON or XML text stored in a field. *File* first parses a JSON or XML binary file. |
109
+ | **Input Field** / **Input Binary Field** | Which field holds the text or file. |
110
+ | **Input Format** | *Detect Automatically* (XML when the text starts with `<`), *JSON* or *XML*. |
111
+ | **Define Mappings** | *Using Fields Below* or *Using JSON*. See [Reusable JSON definitions](#reusable-json-definitions). |
112
+ | **Mappings** | The list of mappings. Each has a **Source** (*Input Field* path or *Fixed Value*, which can be an expression), a **Target Path**, an optional **Transform**, and **If Source Is Missing**: *Leave Out* (default), *Set to Null*, *Use Default* or *Stop With Error*. |
113
+ | **Unmapped Fields** | *Drop* outputs only mapped fields. *Keep* starts from a copy of the input and applies the mappings on top. |
114
+ | **Output Format** | *JSON* (the item), *JSON File*, *XML* (text in a field) or *XML File*. |
115
+ | **Put Output in Field** | *XML only.* Default `xml`. |
116
+ | **Put Output File in Field** / **File Name** | *File outputs only.* Defaults `data` and `output.xml` / `output.json`. |
117
+ | **Root Element** | *XML outputs only.* Leave empty to use the single top-level field as the root, or `root` when there are several. |
118
+ | **Include XML Declaration** | *XML outputs only.* Adds `<?xml version="1.0" encoding="UTF-8"?>`. |
119
+
120
+ Turn on **Settings → On Error → Continue** to output failed items as `{ "error": "…" }`. Every error names the mapping that caused it, e.g. `Mapping 3 (→ lines[*].quantity): Cannot convert "two" to a number`.
121
+
122
+ ## Transforms
123
+
124
+ | Transform | Options | Example |
125
+ | --- | --- | --- |
126
+ | None | | |
127
+ | To String | | `42` → `"42"`; objects become JSON text |
128
+ | To Number | | `"10.5"` → `10.5` (fails on non-numbers) |
129
+ | To Integer | | `"10.9"` → `10` |
130
+ | Round | Decimal Places | `10.456` → `10.46` |
131
+ | To Boolean | | `yes`/`no`, `true`/`false`, `1`/`0`, `on`/`off`, `y`/`n` |
132
+ | Trim, Uppercase, Lowercase | | Also applied to every string in a list |
133
+ | Format Date | Input Date Format, Output Date Format | `20261017` with `yyyyMMdd` → `17/10/2026` with `dd/MM/yyyy`. Empty input format reads ISO 8601 or Unix milliseconds; empty output format writes ISO 8601. Tokens: `yyyy MM dd HH mm ss SSS`, in UTC. |
134
+ | Split Into List | Separator | `"a, b"` → `["a", "b"]` |
135
+ | Join List | Separator | `["a", "b"]` → `"a,b"`. With `items[*].sku` → `skuList` it joins the collected list. |
136
+ | Lookup Table | Lookup Table, If No Match | Table as `key=value` lines or a JSON object. If no match: *Keep Original Value*, *Use Default*, *Set to Null* or *Stop With Error*. |
137
+
138
+ For anything else, use a **Fixed Value** with an expression, e.g. `{{ $json.first + ' ' + $json.last }}`.
139
+
140
+ ## Reusable JSON definitions
141
+
142
+ Set **Define Mappings** to **Using JSON** to describe the mapping as data. You can paste it in, or load it with an expression so one mapping is shared by many workflows. For example, `{{ $('Get mapping').item.json.mapping }}` can read it from a database row, a file or a static data node.
143
+
144
+ ```json
145
+ [
146
+ { "source": "id", "target": "orderId" },
147
+ { "source": "customer.name", "target": "buyer.name", "transform": "trim" },
148
+ { "source": "items[*].sku", "target": "lines[*].productCode" },
149
+ { "source": "items[*].qty", "target": "lines[*].quantity", "transform": "integer" },
150
+ { "source": "created", "target": "orderDate", "transform": "date", "dateInputFormat": "yyyyMMdd", "dateOutputFormat": "yyyy-MM-dd" },
151
+ { "source": "customer.country", "target": "buyer.country", "transform": "lookup", "lookup": { "NL": "Netherlands" }, "lookupFallback": "keep" },
152
+ { "source": "note", "target": "comment", "ifMissing": "default", "default": "" },
153
+ { "value": "EUR", "target": "lines[*].currency" }
154
+ ]
155
+ ```
156
+
157
+ `{ "mappings": [ … ] }` also works. Available properties per mapping:
158
+
159
+ | Property | Values |
160
+ | --- | --- |
161
+ | `target` | Target path (required) |
162
+ | `source` | Source path |
163
+ | `value` | Fixed value, used instead of `source` |
164
+ | `transform` | `none`, `string`, `number`, `integer`, `round`, `boolean`, `trim`, `uppercase`, `lowercase`, `date`, `split`, `join`, `lookup` |
165
+ | `ifMissing` | `skip` (default), `null`, `default`, `error` |
166
+ | `default` | Value for `ifMissing: "default"` |
167
+ | `separator` | For `split` and `join` (default `,`) |
168
+ | `decimals` | For `round` |
169
+ | `dateInputFormat`, `dateOutputFormat` | For `date` |
170
+ | `lookup` | Object or `key=value` lines, for `lookup` |
171
+ | `lookupFallback` | `keep` (default), `default`, `null`, `error` |
172
+ | `lookupDefault` | Value for `lookupFallback: "default"` |
173
+
174
+ Unknown properties are rejected, so a typo such as `"tranform"` fails loudly instead of being ignored.
175
+
176
+ ## Working with XML
177
+
178
+ XML input is converted with the same conventions as n8n's XML node: attributes go under `$` and an element's text, when it also has attributes or children, goes under `_`.
179
+
180
+ ```xml
181
+ <order ID="7">
182
+ <line><sku>A</sku><qty>3</qty></line>
183
+ <line><sku>B</sku><qty>1</qty></line>
184
+ <total currency="EUR">12.50</total>
185
+ </order>
186
+ ```
187
+
188
+ | Value | Path |
189
+ | --- | --- |
190
+ | `7` | `order.$.ID` |
191
+ | every SKU | `order.line[*].sku` |
192
+ | `12.50` | `order.total._` |
193
+ | `EUR` | `order.total.$.currency` |
194
+
195
+ XML output works the other way round. Write to `$` for attributes, `_` for text, and a list becomes repeated elements:
196
+
197
+ | Source Path | Target Path |
198
+ | --- | --- |
199
+ | `id` | `order.$.id` |
200
+ | `items[*].sku` | `order.line[*].$.sku` |
201
+ | `items[*].qty` | `order.line[*].quantity` |
202
+
203
+ ```xml
204
+ <?xml version="1.0" encoding="UTF-8"?>
205
+ <order id="A1">
206
+ <line sku="P-1">
207
+ <quantity>2</quantity>
208
+ </line>
209
+ <line sku="P-2">
210
+ <quantity>1</quantity>
211
+ </line>
212
+ </order>
213
+ ```
214
+
215
+ Special characters are escaped automatically. Invalid XML input fails with the line and column of the problem.
216
+
217
+ ## Examples
218
+
219
+ ### Distill a large API response
220
+
221
+ Keep three fields from a big payload: **Unmapped Fields** *Drop*, with mappings `data.user.id` → `id`, `data.user.profile.email` → `email` (Lowercase) and `data.user.roles[*].name` → `roles`.
222
+
223
+ ### Convert JSON to an XML file for an SFTP upload
224
+
225
+ **Output Format** *XML File*, **Root Element** `invoice`, **File Name** `{{ $json.number }}.xml`, then connect an FTP node.
226
+
227
+ ### Normalize data from several sources
228
+
229
+ Put one Data Mapper on each source branch, each mapping into the same target shape, then merge the branches.
230
+
231
+ ## Compatibility
232
+
233
+ - n8n 1.x and 2.x (Nodes API version 1)
234
+ - Node.js 20.15 or newer for development
235
+
236
+ ## Development
237
+
238
+ ```bash
239
+ git clone https://github.com/SilentJMA/n8n-nodes-data-mapper.git
240
+ cd n8n-nodes-data-mapper
241
+ npm install --ignore-scripts
242
+ npm run dev # local n8n at http://localhost:5678 with the node loaded
243
+ npm run lint
244
+ npm test
245
+ ```
246
+
247
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the project layout and release process, and [CHANGELOG.md](CHANGELOG.md) for version history.
248
+
249
+ ## License
250
+
251
+ [MIT](LICENSE)
@@ -0,0 +1,5 @@
1
+ import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class DataMapper implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }