4
\$\begingroup\$

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) => ({
        ...acc,
        properties: filter(value, property => indexOf(usedPropertyIds, property.id) !== -1),
    }), currentNode)

    result.push(nodeWithReducedProperties)

    if (node.children && node.children.length > 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.

\$\endgroup\$
  • 1
    \$\begingroup\$ @200_success Thanks! I've tried describing it a bit better. \$\endgroup\$ – ngr Jul 31 '19 at 23:56
2
\$\begingroup\$

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 }) => id);
    const currentNode = nodes.find(({ id }) => id === node.id);
    const nodeWithFilteredProperties = {
      ...currentNode,
      properties: currentNode.properties.filter(({ id }) =>
        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) => x => fns.reduce((v, f) => f(v), x);

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

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

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

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

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

const mappedData = pipe(
  getFlattenedNodes,
  getPropertyIdsAndNodes(nodes),
  getNodesWithFilteredProperties
)(rootNode);
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Far more clearer with vanilla! Why did i not think of it before :) I have been thinking of how you could compose a mapper like this through compose or a pipe but i'm not sure if it is even possible. \$\endgroup\$ – ngr Aug 1 '19 at 18:22
  • \$\begingroup\$ That's exactly what i had in mind but could not lay my finger on. Thanks! Your opening eyes. :) So in functional it's perfectly fine to compose recursive functions together? And i see you first call getPropertyIdsAndNodes which returns a function. I had been thinking about that approach but wondered if it would be a proper way of composing. \$\endgroup\$ – ngr Aug 1 '19 at 23:40
  • \$\begingroup\$ > So in functional it's perfectly fine to compose recursive functions together? You mean getPropertyIdsAndNodes? I don't think it's good code, it's a bit hard to read imo. But it's functional 🤷 Feel free to accept the answer if it helped! \$\endgroup\$ – Mohrn Aug 5 '19 at 14:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.