Finding optimal mapping patterns that transform relational data into a linear format I'm trying to upgrade my javascript to be more functional. I have used function composition and curry successful on less complicated code but i'm now running into a point where i'm not sure on how to approach it.

Given the below relational data structures.

// Contains all node types and its property definitions. Including any additional information like readable descriptions etc..

const nodes = [{
  id: '0ec7deff-91af-e911-812a-00155d0a5146',
  description: 'Airplanes',
  properties: [{
    id: '0fc7deff-91af-e911-812a-00155d0a5146',
    description: 'Weight'
  }, {
    id: '06c7deff-91af-e911-812a-00155d0a5146',
    description: 'Color'
  }]
}, {
  id: '278182d0-4ba2-4813-b74e-277a2296e864',
  description: 'Manufacturers',
  properties: [{
    id: '003e14d7-c11a-41e1-ad08-ab8896a3cb55',
    description: 'Boeing'
  }]
}]

// Contains only the to be used nodes and there relations(defined through children nodes) in the eventual output. The above definitions can be used to pull in additional data.

const rootNode = {
  id: '0ec7deff-91af-e911-812a-00155d0a5146',
  properties: [{
   id: '0fc7deff-91af-e911-812a-00155d0a5146',
  }],
  children: [{
    id: '278182d0-4ba2-4813-b74e-277a2296e864',
    properties: [{
      id: '003e14d7-c11a-41e1-ad08-ab8896a3cb55',
    }]
  }],
}


The rootNode is the starting point. It can contain an infinite amount of children including properties that need to be displayed. Each node needs to be checked for child nodes and be added to the eventual result. For example a linear set to be displayed on the x axis where the properties will be shown on a next row also on the x axis.

[{
  id: '0ec7deff-91af-e911-812a-00155d0a5146',
  description: 'Airplanes',
  properties: [{
    id: '0fc7deff-91af-e911-812a-00155d0a5146',
    description: 'Weight'
  }]
 }, {
   id: '278182d0-4ba2-4813-b74e-277a2296e864',
   description: 'Manufacturers',
   properties: [{
     id: '003e14d7-c11a-41e1-ad08-ab8896a3cb55',
     description: 'Boeing'
   }]
 }]


The eventual output will be shown in a 2 dimensional grid or table.

Airplanes     Manufacturers
Weight        Boeing


Or in a more elaborate example if i would provide more data and a larger query.

Airplanes                       Manufacturers
Weight    Color    Type         Boeing    Lockhead


I build a function that works and uses recursion but it just doesn't feel right. I want to pull it apart into smaller units. Note that lodash is used here, hence the shorthands etc.

function initializeNodeMapper(nodes) {
  const result = []

  return function mapNodes(node) {
    const usedPropertyIds = map(node.properties, 'id')
    const currentNode = find(nodes, ['id', node.id])
    const nodeWithReducedProperties = reduce(currentNode, (acc, value) =&gt; ({
        ...acc,
        properties: filter(value, property =&gt; indexOf(usedPropertyIds, property.id) !== -1),
    }), currentNode)

    result.push(nodeWithReducedProperties)

    if (node.children &amp;&amp; node.children.length &gt; 0) {
      forEach(node.children, mapNodes)
    }

    return result
  }
}

const mapData = initializeNodeMapper(nodes)
const mappedData = mapData(initialNode)


Using functional patterns i want to find a more optimal pattern of mapping and testing the code.
Some slight improvements:


You could do without the result array (less state ).
I think using vanilla object destructuring is more clear than the lowdash reduce method which is confusing to me (unless I still don't understand it and my code actually is not equivalent to yours ).


const allNodes = [
];

const rootNode = {
};

function initializeNodeMapper(nodes) {
  return function mapNodes(node) {
    const propertyIds = node.properties.map(({ id }) =&gt; id);
    const currentNode = nodes.find(({ id }) =&gt; id === node.id);
    const nodeWithFilteredProperties = {
      ...currentNode,
      properties: currentNode.properties.filter(({ id }) =&gt;
        propertyIds.includes(id)
      )
    };

    return node.children
      ? [nodeWithFilteredProperties].concat(node.children.map(mapNodes))
      : [nodeWithFilteredProperties];
  };
}

const mappedData = initializeNodeMapper(allNodes)(rootNode);


Update:

Are you looking for something like this?

const nodes = [
];

const rootNode = {
};

const pipe = (...fns) =&gt; x =&gt; fns.reduce((v, f) =&gt; f(v), x);

const getFlattenedNodes = node =&gt;
  node.children
    ? [node].concat(
        node.children.reduce(
          (acc, val) =&gt; acc.concat(getFlattenedNodes(val)),
          []
        )
      )
    : [node];

const getPropertyIds = node =&gt; node.properties.map(({ id }) =&gt; id);

const getNodeWithProperties = nodesWithProperties =&gt; node =&gt;
  nodesWithProperties.find(({ id }) =&gt; id === node.id);

const getNodesWithFilteredProperties = propertyIdsAndNodesToBeFiltered =&gt;
  propertyIdsAndNodesToBeFiltered.map(([propertyIds, nodeWithProperties]) =&gt; ({
    ...nodeWithProperties,
    properties: nodeWithProperties.properties.filter(({ id }) =&gt;
      propertyIds.includes(id)
    )
  }));

const getPropertyIdsAndNodes = nodesWithProperties =&gt; flattenNodes =&gt;
  flattenNodes.map(node =&gt; [
    getPropertyIds(node),
    getNodeWithProperties(nodesWithProperties)(node)
  ]);

const mappedData = pipe(
  getFlattenedNodes,
  getPropertyIdsAndNodes(nodes),
  getNodesWithFilteredProperties
)(rootNode);