Skazka
Skazka

Reputation: 3

How to update nested array recursively using javascript

I have an array which contains: name, id (id represents the path in array) and children (optional). Children has the same structure array has. Each element may have children, and its children may have their own children etc. Potentially it can be deeply nested array. I want to update ids for all nested children depending on new index. I have a function which accepts an array and a new index. The function calls itself recursively if an element contains children. For example, I pass an array and an index equal to 1. In this case I expect'2.children[0].children[0]' to be replaced with'1.children[0].children[0]'

For some reason nested children are not updated correctly. When I debug the code I see that newValue is generated correctly but in the end I receive an arry with old ids.

  {
    name: 'Cat',
    id: '0',
  },
  {
    name: 'Dog',
    id: '1',
  },
  {
    name: 'Rabbit',
    id: '2',
    children: [
      {
        name: 'Little Rabbit Child',
        id: '2.children[0]',
        children: [
          { name: 'Little Rabbit Child One', id: '2.children[0].children[0]',children: [{ name: "Rat", id: "2.children[0].children[0].children[0]"}] },
          { name: 'Little Rabbit Child Two', id: '2.children[0].children[1]' },
          { name: 'Little Rabbit Child Three', id: '2.children[0].children[2]' },
        ],
      },
      { name: 'Little Rabbit Son', id: '2.children[1]' },
      { name: 'Little Rabbit Daughter', id: '2.children[2]' },
    ],
  },
  {
    name: 'Elephant',
    id: '3',
    children: [{ name: 'Little Elephant', id: '3.children[0]' }],
  },
];


const updateChildrenIds = (childrenData = [], index) => {
  const newData = childrenData.map(value => {
    const { id, children = [] } = value;
    if (children.length) updateChildrenIds(children, index);
    const splitIds = id.split('.');
    splitIds[0] = index;
    const newId = splitIds.join('.');

    const newValue = {...value, id: newId}

    return newValue;
  });

  return newData;
};


const data = updateChildrenIds(testData[2].children, 1);

expected result would be:

[
      {
        name: 'Little Rabbit Child',
        id: '1.children[0]',
        children: [
          { name: 'Little Rabbit Child One', id: '1.children[0].children[0]',children: [{ name: "Rat", id: "1.children[0].children[0].children[0]"}] },
          { name: 'Little Rabbit Child Two', id: '1.children[0].children[1]' },
          { name: 'Little Rabbit Child Three', id: '1.children[0].children[2]' },
        ],
      },
      { name: 'Little Rabbit Son', id: '1.children[1]' },
      { name: 'Little Rabbit Daughter', id: '1.children[2]' },
    ]

I would appreciate if someone could help me with that

Upvotes: 0

Views: 4186

Answers (2)

Scott Sauyet
Scott Sauyet

Reputation: 50797

The problem in your code is that, while you call updateChildren(children, index), you don't do anything with the result.

This code creates a new array, and doesn't modify your old object in place. (I heartily approve of this!) But that means, when you call updateChildren on the children, you really need to do something with the result.

Here is a tweak to your code that will fix that.

const updateChildrenIds = (childrenData = [], index) => {
  const newData = childrenData.map(value => {
    const { id, children} = value; // no default value for children
    // removed the recursive call from here
    const splitIds = id.split('.');
    splitIds[0] = index;
    const newId = splitIds.join('.');

    const newValue = {...value, id: newId}
    if (children) {  // moved here
      newValue.children = updateChildrenIds(children, index)
    }

    return newValue;
  });

  return newData;
};


const testData = [{name: "Cat", id: "0"}, {name: "Dog", id: "1"}, {name: "Rabbit", id: "2", children: [{name: "Little Rabbit Child", id: "2.children[0]", children: [{name: "Little Rabbit Child One", id: "2.children[0].children[0]", children: [{name: "Rat", id: "2.children[0].children[0].children[0]"}]}, {name: "Little Rabbit Child Two", id: "2.children[0].children[1]"}, {name: "Little Rabbit Child Three", id: "2.children[0].children[2]"}]}, {name: "Little Rabbit Son", id: "2.children[1]"}, {name: "Little Rabbit Daughter", id: "2.children[2]"}]}, {name: "Elephant", id: "3", children: [{name: "Little Elephant", id: "3.children[0]"}]}]

console .log (updateChildrenIds (testData [2] .children, 1))
.as-console-wrapper {min-height: 100% !important; top: 0}

But if I were to write this, I would do it differently. So here's another approach that hews more closely to functional programming norms:

const updateIds = (nodes, baseId) => 
  nodes .map (({id, children, ...rest}) => ({
    ...rest,
    id: id .replace (/^\d+/, baseId),
    ... (children && {children: updateIds (children, baseId)})
  }))

const testData = [{name: "Cat", id: "0"}, {name: "Dog", id: "1"}, {name: "Rabbit", id: "2", children: [{name: "Little Rabbit Child", id: "2.children[0]", children: [{name: "Little Rabbit Child One", id: "2.children[0].children[0]", children: [{name: "Rat", id: "2.children[0].children[0].children[0]"}]}, {name: "Little Rabbit Child Two", id: "2.children[0].children[1]"}, {name: "Little Rabbit Child Three", id: "2.children[0].children[2]"}]}, {name: "Little Rabbit Son", id: "2.children[1]"}, {name: "Little Rabbit Daughter", id: "2.children[2]"}]}, {name: "Elephant", id: "3", children: [{name: "Little Elephant", id: "3.children[0]"}]}]

console .log (updateIds (testData [2] .children, 1))
.as-console-wrapper {min-height: 100% !important; top: 0}

As well as a somewhat simpler overall structure, this code also replaces your split/replace/join instructions with a single regex.replace.

Neither approach updates your original data structure. If you need to do that, you can assign this result back to the appropriate path in your structure, or create a function that does the equivalent while sharing as much structure as possible. That's not hard. The function assocPath in a recent answer, inspired by Ramda, would do fine.

Upvotes: 1

andsilver
andsilver

Reputation: 5972

When you return the value,

return Object.assign({}, newData)

or

return {...newData}

Upvotes: 0

Related Questions