n8n-nodes-data-mapper 0.0.0-stage → 0.2.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,361 @@
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
+ Load a **target profile** (a profile component exported as XML/JSON/Flat File, JSON Schema, sample JSON/XML, XSD or a CSV header) — paste it or upload it — to pick target fields from a dropdown, or let **auto-map** match source fields to the target for you.
10
+
11
+ 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.
12
+
13
+ - [Screenshots](#screenshots)
14
+ - [Installation](#installation)
15
+ - [Quick start](#quick-start)
16
+ - [Paths](#paths)
17
+ - [Parameters](#parameters)
18
+ - [Transforms](#transforms)
19
+ - [Target profiles and auto-map](#target-profiles-and-auto-map)
20
+ - [Reusable JSON definitions](#reusable-json-definitions)
21
+ - [Working with XML](#working-with-xml)
22
+ - [Examples](#examples)
23
+ - [Compatibility](#compatibility)
24
+ - [Development](#development)
25
+ - [License](#license)
26
+
27
+ ## Screenshots
28
+
29
+ **Mapping fields to a JSON output** — rename and nest fields, convert types, and reshape arrays:
30
+
31
+ ![Data Mapper producing JSON output](docs/images/json-mapping.webp)
32
+
33
+ **Building XML with transforms and added fields** — attributes, nested elements, repeated items, a country lookup, rounding and a generated timestamp:
34
+
35
+ ![Data Mapper producing XML output](docs/images/xml-output.webp)
36
+
37
+ ## Installation
38
+
39
+ ### From the n8n UI (recommended)
40
+
41
+ 1. In n8n, open **Settings → Community Nodes**.
42
+ 2. Select **Install**, enter `n8n-nodes-data-mapper`, accept the risk notice and install.
43
+ 3. Search for **Data Mapper** in the node panel.
44
+
45
+ ### Manually (self-hosted, Docker, Unraid)
46
+
47
+ The package has no runtime dependencies, so you can drop it into n8n's `custom` folder:
48
+
49
+ ```bash
50
+ # inside your n8n data folder (mapped to /home/node/.n8n in the official Docker image)
51
+ mkdir -p custom && cd custom
52
+ npm pack n8n-nodes-data-mapper
53
+ tar -xzf n8n-nodes-data-mapper-*.tgz && mv package n8n-nodes-data-mapper
54
+ ```
55
+
56
+ Restart n8n afterwards.
57
+
58
+ ## Quick start
59
+
60
+ Given this input item:
61
+
62
+ ```json
63
+ {
64
+ "id": "A1",
65
+ "customer": { "name": " Ann ", "country": "NL" },
66
+ "items": [
67
+ { "sku": "P-1", "qty": "2" },
68
+ { "sku": "P-2", "qty": "1" }
69
+ ]
70
+ }
71
+ ```
72
+
73
+ add these mappings:
74
+
75
+ | Source Path | Target Path | Transform |
76
+ | --- | --- | --- |
77
+ | `id` | `orderId` | |
78
+ | `customer.name` | `buyer.name` | Trim |
79
+ | `customer.country` | `buyer.country` | Lookup Table: `NL=Netherlands` |
80
+ | `items[*].sku` | `lines[*].productCode` | |
81
+ | `items[*].qty` | `lines[*].quantity` | To Integer |
82
+ | *Fixed Value* `EUR` | `lines[*].currency` | |
83
+
84
+ and get:
85
+
86
+ ```json
87
+ {
88
+ "orderId": "A1",
89
+ "buyer": { "name": "Ann", "country": "Netherlands" },
90
+ "lines": [
91
+ { "productCode": "P-1", "quantity": 2, "currency": "EUR" },
92
+ { "productCode": "P-2", "quantity": 1, "currency": "EUR" }
93
+ ]
94
+ }
95
+ ```
96
+
97
+ Import [`examples/data-mapper-examples.json`](examples/data-mapper-examples.json) into n8n (**Workflows → Import from File**) to try this and the XML examples.
98
+
99
+ ## Paths
100
+
101
+ | Syntax | Meaning |
102
+ | --- | --- |
103
+ | `customer.name` | Nested field |
104
+ | `items[0].sku` | One array element |
105
+ | `items[*].sku` | Every array element |
106
+ | `["first name"]` or `data["x-id"]` | Keys with spaces or special characters |
107
+ | *(empty)* | The whole input (source) or the whole output (target, value must be an object) |
108
+
109
+ How `[*]` in the source and target line up:
110
+
111
+ - **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.
112
+ - **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.
113
+ - **More `[*]` in the target**: not allowed, except for a fixed value, which is written to every existing element (`EUR` → `lines[*].currency`).
114
+ - 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.
115
+
116
+ Mappings run in order, and a later mapping to the same target overwrites an earlier one.
117
+
118
+ ## Parameters
119
+
120
+ | Parameter | Description |
121
+ | --- | --- |
122
+ | **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. |
123
+ | **Input Field** / **Input Binary Field** | Which field holds the text or file. |
124
+ | **Input Format** | *Detect Automatically* (XML when the text starts with `<`), *JSON* or *XML*. |
125
+ | **Define Mappings** | *Using Fields Below*, *Auto-Map From Profile*, or *Using JSON*. See [Target profiles and auto-map](#target-profiles-and-auto-map) and [Reusable JSON definitions](#reusable-json-definitions). |
126
+ | **Target Profile** / **Target Profile Format** | Optional. The structure to map into. See [Target profiles and auto-map](#target-profiles-and-auto-map). |
127
+ | **Mappings** | The list of mappings. Each has a **Source** (*Input Field* path or *Fixed Value*, which can be an expression), a **Target** (*Custom Path* you type, or *From Target Profile* picked from a dropdown), an optional **Transform**, and **If Source Is Missing**: *Leave Out* (default), *Set to Null*, *Use Default* or *Stop With Error*. |
128
+ | **Auto-Map Remaining Target Fields** | *Fields mode.* Fills any profile fields you did not map by hand by matching them to source fields by name. |
129
+ | **Unmapped Fields** | *Drop* outputs only mapped fields. *Keep* starts from a copy of the input and applies the mappings on top. |
130
+ | **Output Format** | *JSON* (the item), *JSON File*, *XML* (text in a field) or *XML File*. |
131
+ | **Put Output in Field** | *XML only.* Default `xml`. |
132
+ | **Put Output File in Field** / **File Name** | *File outputs only.* Defaults `data` and `output.xml` / `output.json`. |
133
+ | **Root Element** | *XML outputs only.* Leave empty to use the single top-level field as the root, or `root` when there are several. |
134
+ | **Include XML Declaration** | *XML outputs only.* Adds `<?xml version="1.0" encoding="UTF-8"?>`. |
135
+
136
+ 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`.
137
+
138
+ ## Transforms
139
+
140
+ | Transform | Options | Example |
141
+ | --- | --- | --- |
142
+ | None | | |
143
+ | To String | | `42` → `"42"`; objects become JSON text |
144
+ | To Number | | `"10.5"` → `10.5` (fails on non-numbers) |
145
+ | To Integer | | `"10.9"` → `10` |
146
+ | Round | Decimal Places | `10.456` → `10.46` |
147
+ | To Boolean | | `yes`/`no`, `true`/`false`, `1`/`0`, `on`/`off`, `y`/`n` |
148
+ | Trim, Uppercase, Lowercase | | Also applied to every string in a list |
149
+ | 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. |
150
+ | Split Into List | Separator | `"a, b"` → `["a", "b"]` |
151
+ | Join List | Separator | `["a", "b"]` → `"a,b"`. With `items[*].sku` → `skuList` it joins the collected list. |
152
+ | 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*. |
153
+
154
+ For anything else, use a **Fixed Value** with an expression, e.g. `{{ $json.first + ' ' + $json.last }}`.
155
+
156
+ ## Target profiles and auto-map
157
+
158
+ A **target profile** describes the structure you want to map into. Paste it into the **Target Profile** field (or reference an uploaded file with an expression — see below). The node reads it and gives you two things: a **dropdown of target fields** in manual mode, and **auto-map**, which matches your source fields to the profile for you.
159
+
160
+ ### Supported formats
161
+
162
+ **Target Profile Format** is *Detect Automatically* by default. Supported:
163
+
164
+ | Format | Notes |
165
+ | --- | --- |
166
+ | Profile Component (XML/JSON/Flat File) | Integration profile components exported as XML, with `type="profile.xml"`, `"profile.json"` or `"profile.flatfile"`. Attributes become `.$.name`, repeating elements become `[*]`, and the JSON profile's `Root`/`Object` wrappers are skipped. |
167
+ | JSON Schema | `properties`, `required`, `items`, `type` and `format` (date/date-time → datetime). |
168
+ | Sample JSON | A representative JSON document; array object fields are merged into one shape. |
169
+ | Sample XML | A representative XML document; attributes become `.$.name`. |
170
+ | XSD | `xs:element` / `xs:complexType` / `xs:attribute`, with `maxOccurs` for repeating elements. |
171
+ | CSV Header | The first line, split on comma, semicolon, tab or pipe. |
172
+
173
+ ### Paste or upload
174
+
175
+ **Target Profile** has two sources:
176
+
177
+ - **Paste It** — paste the profile text into the box. This is the only source that fills the target-field dropdown.
178
+ - **Upload a File** — read the profile from an uploaded file. Put a node that produces a binary file before Data Mapper (for example **Read/Write Files From Disk**, a **Form Trigger** with a file-upload field, or an **HTTP Request**), then name that binary field. Auto-map reads the uploaded file directly; to pick fields from the dropdown, paste the profile instead.
179
+
180
+ ### Picking targets (manual mode)
181
+
182
+ With **Define Mappings** = *Using Fields Below*, paste a profile, then set a mapping's **Target** to *From Target Profile* and choose the field from the dropdown. The dropdown shows each field's path, whether it is a list, and its type. Switch **Target** to *Custom Path* to type a path the profile does not contain.
183
+
184
+ ### Auto-map
185
+
186
+ With **Define Mappings** = *Auto-Map From Profile*, the node matches each target field to the best source field by name and builds the mapping automatically. The target's type becomes the transform (a `number` field gets *To Number*, a `datetime` field gets *Format Date*, and so on).
187
+
188
+ **Auto-Map Options:**
189
+
190
+ | Option | Default | Description |
191
+ | --- | --- | --- |
192
+ | Minimum Confidence | 0.6 | How close names must be, from 0 (anything) to 1 (identical). Names are compared ignoring case and punctuation. |
193
+ | Match | Fuzzy | *Fuzzy* allows similar names; *Exact Names Only* requires the same name. |
194
+ | Case Sensitive | off | Whether names must match in the same case. |
195
+ | Reuse a Source for Several Targets | off | Whether one source may fill more than one target. |
196
+ | Unmatched Target Fields | Leave Out | *Leave Out*, or *Set to Null* to include them as null. |
197
+ | Add Auto-Map Report | off | Adds an `_autoMap` field listing matched and unmatched fields (JSON output). |
198
+
199
+ Auto-map respects arrays: `items[*].Qty` → `lines[*].qty` works, and a non-repeating source is never mapped onto a repeating target. To tweak a few fields by hand, use *Using Fields Below* with **Auto-Map Remaining Target Fields** on — your manual mappings win, and the rest are filled in automatically.
200
+
201
+ For an XML target profile, the root element name comes from the profile, so XML output needs no **Root Element**.
202
+
203
+ ## Reusable JSON definitions
204
+
205
+ 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.
206
+
207
+ ```json
208
+ [
209
+ { "source": "id", "target": "orderId" },
210
+ { "source": "customer.name", "target": "buyer.name", "transform": "trim" },
211
+ { "source": "items[*].sku", "target": "lines[*].productCode" },
212
+ { "source": "items[*].qty", "target": "lines[*].quantity", "transform": "integer" },
213
+ { "source": "created", "target": "orderDate", "transform": "date", "dateInputFormat": "yyyyMMdd", "dateOutputFormat": "yyyy-MM-dd" },
214
+ { "source": "customer.country", "target": "buyer.country", "transform": "lookup", "lookup": { "NL": "Netherlands" }, "lookupFallback": "keep" },
215
+ { "source": "note", "target": "comment", "ifMissing": "default", "default": "" },
216
+ { "value": "EUR", "target": "lines[*].currency" }
217
+ ]
218
+ ```
219
+
220
+ `{ "mappings": [ … ] }` also works. Available properties per mapping:
221
+
222
+ | Property | Values |
223
+ | --- | --- |
224
+ | `target` | Target path (required) |
225
+ | `source` | Source path |
226
+ | `value` | Fixed value, used instead of `source` |
227
+ | `transform` | `none`, `string`, `number`, `integer`, `round`, `boolean`, `trim`, `uppercase`, `lowercase`, `date`, `split`, `join`, `lookup` |
228
+ | `ifMissing` | `skip` (default), `null`, `default`, `error` |
229
+ | `default` | Value for `ifMissing: "default"` |
230
+ | `separator` | For `split` and `join` (default `,`) |
231
+ | `decimals` | For `round` |
232
+ | `dateInputFormat`, `dateOutputFormat` | For `date` |
233
+ | `lookup` | Object or `key=value` lines, for `lookup` |
234
+ | `lookupFallback` | `keep` (default), `default`, `null`, `error` |
235
+ | `lookupDefault` | Value for `lookupFallback: "default"` |
236
+
237
+ Unknown properties are rejected, so a typo such as `"tranform"` fails loudly instead of being ignored.
238
+
239
+ ## Working with XML
240
+
241
+ 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 `_`.
242
+
243
+ ```xml
244
+ <order ID="7">
245
+ <line><sku>A</sku><qty>3</qty></line>
246
+ <line><sku>B</sku><qty>1</qty></line>
247
+ <total currency="EUR">12.50</total>
248
+ </order>
249
+ ```
250
+
251
+ | Value | Path |
252
+ | --- | --- |
253
+ | `7` | `order.$.ID` |
254
+ | every SKU | `order.line[*].sku` |
255
+ | `12.50` | `order.total._` |
256
+ | `EUR` | `order.total.$.currency` |
257
+
258
+ XML output works the other way round. Write to `$` for attributes, `_` for text, and a list becomes repeated elements:
259
+
260
+ | Source Path | Target Path |
261
+ | --- | --- |
262
+ | `id` | `order.$.id` |
263
+ | `items[*].sku` | `order.line[*].$.sku` |
264
+ | `items[*].qty` | `order.line[*].quantity` |
265
+
266
+ ```xml
267
+ <?xml version="1.0" encoding="UTF-8"?>
268
+ <order id="A1">
269
+ <line sku="P-1">
270
+ <quantity>2</quantity>
271
+ </line>
272
+ <line sku="P-2">
273
+ <quantity>1</quantity>
274
+ </line>
275
+ </order>
276
+ ```
277
+
278
+ Special characters are escaped automatically. Invalid XML input fails with the line and column of the problem.
279
+
280
+ ## Examples
281
+
282
+ The [`examples/`](examples) folder has importable workflows (**Workflows → Import from File**). The easiest way to build a flow, though, is to add **Data Mapper** from the node panel directly.
283
+
284
+ | File | Shows |
285
+ | --- | --- |
286
+ | [`examples/data-mapper-examples.json`](examples/data-mapper-examples.json) | JSON, XML and CSV output side by side |
287
+ | [`examples/auto-map-with-profile.json`](examples/auto-map-with-profile.json) | Auto-map and the profile-field dropdown |
288
+ | [`examples/fixed-workflow.json`](examples/fixed-workflow.json) | Field mapping to JSON (the first screenshot above) |
289
+ | [`examples/xml-output-workflow.json`](examples/xml-output-workflow.json) | Building XML with transforms and added fields (the second screenshot above) |
290
+
291
+ > **Importing a workflow and the node shows as `?`** — the node type in a workflow file depends on how the package was installed. Installed through **Settings → Community Nodes**, the type is `n8n-nodes-data-mapper.dataMapper` (what the files in `examples/` use). Installed by copying into n8n's **custom** folder, n8n registers it as `CUSTOM.dataMapper` instead. If an imported workflow shows `?`, either add the node from the panel, or edit the file and swap the type prefix to match your install. [`examples/fixed-workflow.json`](examples/fixed-workflow.json) and [`examples/xml-output-workflow.json`](examples/xml-output-workflow.json) use the `CUSTOM.` prefix.
292
+
293
+ ### Build an XML invoice with transforms
294
+
295
+ This is the second screenshot above ([`examples/xml-output-workflow.json`](examples/xml-output-workflow.json)). From an order like `{ "OrderID": "A1", "CustomerName": " Ann de Vries ", "Country": "NL", "Currency": "eur", "Amount": "129.456", "OrderDate": "2026-10-17", "Items": [...] }`, these mappings (Output Format **XML**):
296
+
297
+ | Source | Target | Transform |
298
+ | --- | --- | --- |
299
+ | `OrderID` | `invoice.$.id` | |
300
+ | *Fixed* `n8n` | `invoice.$.source` | |
301
+ | `CustomerName` | `invoice.customer.name` | Trim |
302
+ | `Country` | `invoice.customer.country` | Lookup `NL=Netherlands` |
303
+ | `Amount` | `invoice.amount._` | Round (2) |
304
+ | `Currency` | `invoice.amount.$.currency` | Uppercase |
305
+ | `OrderDate` | `invoice.issuedDate` | Format Date `dd/MM/yyyy` |
306
+ | *Fixed* `{{ $now.toISO() }}` | `invoice.generatedAt` | |
307
+ | `Items[*].SKU` | `invoice.lines.line[*].$.sku` | |
308
+ | `Items[*].Qty` | `invoice.lines.line[*].qty` | To Integer |
309
+
310
+ produce:
311
+
312
+ ```xml
313
+ <?xml version="1.0" encoding="UTF-8"?>
314
+ <invoice id="A1" source="n8n">
315
+ <customer>
316
+ <name>Ann de Vries</name>
317
+ <country>Netherlands</country>
318
+ </customer>
319
+ <amount currency="EUR">129.46</amount>
320
+ <issuedDate>17/10/2026</issuedDate>
321
+ <generatedAt>2026-10-17T09:00:00.000Z</generatedAt>
322
+ <lines>
323
+ <line sku="P-1"><qty>2</qty></line>
324
+ <line sku="P-2"><qty>1</qty></line>
325
+ </lines>
326
+ </invoice>
327
+ ```
328
+
329
+ ### Distill a large API response
330
+
331
+ 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`.
332
+
333
+ ### Convert JSON to an XML file for an SFTP upload
334
+
335
+ **Output Format** *XML File*, **Root Element** `invoice`, **File Name** `{{ $json.number }}.xml`, then connect an FTP node.
336
+
337
+ ### Normalize data from several sources
338
+
339
+ Put one Data Mapper on each source branch, each mapping into the same target shape, then merge the branches.
340
+
341
+ ## Compatibility
342
+
343
+ - n8n 1.x and 2.x (Nodes API version 1)
344
+ - Node.js 20.15 or newer for development
345
+
346
+ ## Development
347
+
348
+ ```bash
349
+ git clone https://github.com/SilentJMA/n8n-nodes-data-mapper.git
350
+ cd n8n-nodes-data-mapper
351
+ npm install --ignore-scripts
352
+ npm run dev # local n8n at http://localhost:5678 with the node loaded
353
+ npm run lint
354
+ npm test
355
+ ```
356
+
357
+ See the [wiki](docs/Home.md) for full documentation, [CONTRIBUTING.md](CONTRIBUTING.md) for the project layout and release process, and [CHANGELOG.md](CHANGELOG.md) for version history.
358
+
359
+ ## License
360
+
361
+ [MIT](LICENSE)
@@ -0,0 +1,10 @@
1
+ import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodePropertyOptions, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class DataMapper implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getTargetFields(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
+ };
8
+ };
9
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
10
+ }