1

For my current project, I'm working with an API that returns data formatted like this:

{
  groups: [
    {
      items: [
        {
          points: [
            { name: "name1", ... },
            { name: "name2", ... },
            { name: "name3", ... },
            ...
          ],
          ...
        },
        ...
      ]
    },
    ...
  ],
  ...
};

I'd like to create a pure function, mapValues, that takes in an object in the above format, as well as an object mapping each name to a value, and returns the same structure, but with each point containing the value that corresponds to its name.

For example, calling mapValues(data, { name1: "value1", name2: "value2", name3: "value3" }) should return this:

{
  groups: [
    {
      items: [
        {
          points: [
            { name: "name1", value: "value1", ... },
            { name: "name2", value: "value2", ... },
            { name: "name3", value: "value3", ... },
            ...
          ],
          ...
        },
        ...
      ]
    },
    ...
  ],
  ...
};

Here's my first pass:

function mapValues(data, values) {
  return _.extend({}, data, {
    groups: _.map(ui.groups, (group) => {
      return _.extend({}, group, {
        items: _.map(group.items, (item) => {
          return _.extend({}, item, {
            points: _.map(item.points, (point) => {
              return _.extend({ value: values[point.name] }, point);
            })
          });
        })
      });
    })
  });
}

That works, but there's quite a bit of nesting a duplicate code. For my second attempt, I reached for recursion.

function mapValues(data, values) {
  return (function recursiveMap(object, attributes) {
    if (attributes.length === 0) { return _.extend({ value: values[object.name] }, object); }
    let key = attributes[0];
    return _.extend({}, object, {
      [key]: _.map(object[key], child => recursiveMap(child, attributes.slice(1)))
    });
  })(ui, ["groups", "items", "points"]);
}

That works too, but it's difficult to read and not very concise.

Is there a cleaner way to recursively map an object using Lodash? Ideally, I'm looking for a functional approach.

LandonSchropp
  • 10,084
  • 22
  • 86
  • 149

2 Answers2

1

Here's a way you can do it using Object.assign and no fancy functions

var data = <your data here>;
var values = <your mapped values>;

data.groups.items.points.map(p=>
  Object.assign(p, {value: values[p.name]})
);

This works because arrays and objects are pass by reference. So any modifications to the values will result in the original being changed.

If you don't want to mutate your original dataset, it requires you to use {} as the first argument (to assign to a new, empty object) and show clear read/write paths for each object.

var newData = Object.assign({}, data,
  {groups:
    {items:
      {points: data.groups.items.points.map(p=>
        Object.assign({}, p, {value: values[p.name]})
      )}
    }
  }
);
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • Thanks for the repsonse! I think the problem here is that `groups` and `items` are also arrays, and your solution is treating them as objects. – LandonSchropp May 10 '16 at 16:17
0

I know you wanted Lodash, but I had same issue some time ago and I've came up with this JSFiddle I am using great tool created by nervgh. It is very simple yet usefull. You can adjust this function to be pure using Object.assign, accept key parameter and tweak it however you want. You get the idea.

function mapValues(data, values) {
  var iterator = new RecursiveIterator(data);
  for(let {node} of iterator) {
    if(node.hasOwnProperty('name')) {
        node.value = values[node.name];
    }
  }
  return data;
}
Kamil Latosinski
  • 756
  • 5
  • 28
  • Thanks for replying! I like the concept of a recursive iterator, but this solution appears to modify the values in place, and I was hoping for a pure, functional solution. By pure, I mean this: https://en.wikipedia.org/wiki/Pure_function – LandonSchropp May 10 '16 at 17:04
  • You can always use `Object.assign` to avoid mutating data object, and create iterator of this "copied" object and then return it. – Kamil Latosinski May 11 '16 at 10:46