Hello n8n community!
I have searched the documentation but did not find a successful answer to my problem.
The use case
I’m processing a list of customer records. Each customer has a contacts array containing items of different types (e.g. phone, email). My goal is to:
- Split the
contacts array into individual items
- Apply a different mapping depending on the contact type (e.g. phone numbers need to be parsed and reformatted, emails pass through directly)
- Re-aggregate all mapped contacts back into a single array
- Merge that array back into the original customer item to build the final payload
The workflow
The Merge (append) node works inside a loop when the Aggregate is the last node. However, adding a Merge (combine by position) after the Aggregate — which is necessary to reassemble the contacts back into the customer item — breaks the loop entirely.
Test data:
{
"data": [{
"firstName": "John",
"lastName": "Doe",
"contacts": [
{
"type": "phone",
"value": "+33652252525"
}
]
},
{
"firstName": "Mary",
"lastName": "Doe",
"contacts": [
{
"type": "email",
"value": "marydoe@example.com"
}
]
},
{
"firstName": "John",
"lastName": "Smith",
"contacts": [
{
"type": "email",
"value": "johnsmith@example.com"
},
{
"type": "phone",
"value": "+33652252526"
}
]
}]
}
Workflow
Information on your n8n setup
- n8n version: 2.20.9
- Database (default: SQLite): PostgrSQL
- Running n8n via (Docker, npm, n8n cloud, desktop app): Docker
Bienvenue @thomaslc dans notre communauté ! Je suis Jay et je suis un créateur vérifié n8n.
Le nœud Merge (combiner par position) à l’intérieur d’une boucle SplitInBatches ne fonctionne pas parce qu’il s’attend à ce que deux entrées arrivent simultanément - mais à l’intérieur d’une boucle, une seule branche est active par itération. Ce n’est pas conçu pour ce type de motif.
La solution la plus simple ici est de sauter complètement la boucle interne + Merge et de gérer la transformation complète des contacts dans un seul nœud Code par client. Quelque chose comme ceci :
const contacts = $json.contacts;
const mapped = contacts.map(c => {
if (c.type === 'phone') {
return { type: 'phone', value: c.value.replace(/\s/g, '') };
}
return c;
});
return [{ json: { ...$json, contacts: mapped } }];
Cela s’exécute pour chaque élément de votre boucle externe, transforme le tableau de contacts en place et alimente directement votre charge utile finale - aucune boucle interne ou Merge nécessaire.
Limitation intéressante. Vous pouviez simplifier le flux de travail en utilisant un nœud Code (comme répondu précédemment ci-dessus) ou même un nœud Edit Fields avec du JS. Une autre option consiste à transférer la fonctionnalité de boucle dans un sous-flux de travail et fusionner la sortie du sous-flux de travail.
La façon la plus propre de gérer cela est un nœud Code unique qui exécute les quatre étapes en une seule fois, en contournant entièrement la boucle :
return items.map(item => {
const mapped = item.json.contacts.map(c => {
if (c.type === 'phone') return { ...c, value: c.value.replace(/\D/g, '') };
return c; // email passes through
});
return { json: { ...item.json, contacts: mapped } };
});
Ceci évite le conflit boucle-fusion car vous transformez le tableau en ligne. Le fait que Merge (combine by position) casse la boucle est attendu - les boucles de n8n ne gèrent pas bien l’agrégation mi-boucle. Si vous devez conserver l’approche basée sur les nœuds, transmettez chaque client traité à un sous-workflow via Execute Workflow et agrégez les résultats une fois que toutes les exécutions sont terminées.
Bonjour @nguyenthieutoan, @njogued
Merci pour vos commentaires ! La raison pour laquelle j’utilise une boucle est que j’utilise des nœuds personnalisés comme un nœud de vérification de téléphone. Un nœud de code ne pourrait pas le remplacer.
J’ai donc créé un sous-workflow qui sera appelé en utilisant l’option « Exécuter une fois pour chaque élément ».
Salut @thomaslc , bienvenue !
J’ai regardé la documentation concernant ces éléments, et à mon avis, cela a à voir avec la façon dont n8n maintient le contexte de l’élément. Lorsque vous utilisez Split Out, Loop Over Items, Merge et Aggregate, il doit préserver ou reconstruire le lien entre chaque contact agrégé et le client original. Le Merge combine by position peut casser cette logique si la quantité ou l’ordre des éléments n’est pas exactement le même.
Voici la documentation qui va t’aider :