modifying dynamically nested arrays knowing only the indexes to access

Viewed 295

I have the necessity to modify a nested object-array structure which has always the same pattern knowing only the indexes to access that particular object I want to change.

Example JSON array:

[
    {
    "name":"bob",
    "age":5,
    "subRows":[
        {   
            "name":"paul",
            "age":10,
            "subRows":[]
        },
        {   
            "name":"claire",
            "age":20,
            "subRows":[
                {   
                    "name":"carl",
                    "age":40,
                    "subRows":[]
                }
            ]
        }
    }
]

I need to dynamically delete an object which can be different every time, let's say the one with name "carl", therefore to achieve this expected result:

[
    {
    "name":"bob",
    "age":5,
    "subRows":[
        {   
            "name":"paul",
            "age":10,
            "subRows":[]
        },
        {   
            "name":"claire",
            "age":20,
            "subRows":[]
        }
    }
]

Knowing all indexes of the Json structure to access it:

let indexesArray = [0, 1, 0];

I've tried this:

const deleteObject = (jsonData, indexesArray) =>{
    let pathToDelete =  "jsonData[indexesArray[0]]";
    for(let i=1; i<indexesArray.length(); i++){
        pathToDelete += ".subRows["i"]";
    ]
    pathToDelete = []; //empty the array removing "carl" object
    return jsonData;
}

How can I make javascript process my variable pathTodelete created dynamically to delete a specific object? Are there better ways to do it (of course, my code doesn't work)?

4 Answers

Here is one version, slightly more generic, allowing us to supply the name subRow dynamically to generate the function that does this:

const removeIndexPath = (subName) => (xs, [i, ...is]) =>
  i === undefined
    ? xs
  : is.length === 0
    ? [... xs .slice (0, i), ... xs .slice (i + 1)] // skip xs[i]
  : [
      ... xs .slice (0, i), 
      ... (i in xs // replace xs[i] by recurring with the remaining path on subName
             ? [{...xs[i], [subName]: removeIndexPath (subName) (xs [i] [subName] || [], is)}] 
             : [] // handle missing nodes
          ), 
      ... xs .slice (i + 1)
    ]

const removeSubRowPath = removeIndexPath ('subRows') 

const input = [{name: "bob", age: 5, subRows: [{name: "paul", age: 10, subRows: []}, {name: "claire", age: 20, subRows: [{name: "carl", age: 40, subRows: []}]}]}]

console .log ('without carl:', removeSubRowPath (input, [0, 1, 0]))
console .log ('without clair:', removeSubRowPath (input, [0, 1]))
console .log ('without paul:', removeSubRowPath (input, [0, 0]))
console .log ('without bob:', removeSubRowPath (input, [0]))
console .log ('without nonexistent node:', removeSubRowPath (input, [0, 3, 5]))
.as-console-wrapper {max-height: 100% !important; top: 0}

It handles nodes that don't have the subRow (or whatever) property, and it handles trying to delete non-existent indices anywhere along the path, by simply returning a copy of the original.


I do wonder if this is the fundamental goal. If this is a step taken after first finding the path to "carl", you might be better off writing a dedicated function that deletes a nested node that matches a predicate supplied (perhaps ({name}) => name == "carl" or some such.) If that is your real goal, please start another question (and feel free to add a link to it in the comments here.)

You could just use reduce method on the array of indexes and when its a last index and the match is found you can remove that element from the array by index using splice method.

const data = [{"name":"bob","age":5,"subRows":[{"name":"paul","age":10,"subRows":[]},{"name":"claire","age":20,"subRows":[{"name":"carl","age":40,"subRows":[]}]}]}]

let indexesArray = [0, 1, 0];

indexesArray.reduce((r, e, i, a) => {
  const match = r[e];
  
  if (a.length == i + 1 && match) r.splice(e, 1)
  else if (match) return match.subRows;

  return {}
}, data)

console.log(data)

Since you've tagged the question "recursion," here is a simple recursion:

function f(arr, path){
  function g(i, curr){
    if (i == path.length - 1){
      curr.subRows.splice(path[i], 1);
      return arr;
      
    } else {
      return g(i + 1, curr.subRows[path[i]]);
    }
  }
  
  return g(1, arr[path[0]]);
}

var data = [{"name":"bob","age":5,"subRows":[{"name":"paul","age":10,"subRows":[]},{"name":"claire","age":20,"subRows":[{"name":"carl","age":40,"subRows":[]}]}]}];

var path = [0, 1, 0];

console.log(f(data, path));

Here is an iterative solution using object-scan

First we create the path, then we splice the entity found

// const objectScan = require('object-scan');

const myData = [{ name: 'bob', age: 5, subRows: [ { name: 'paul', age: 10, subRows: [] }, { name: 'claire', age: 20, subRows: [{ name: 'carl', age: 40, subRows: [] }] } ] }];

const prune = (data, indices) => objectScan(
  [indices.map((e) => `[${e}]`).join('.subRows')],
  {
    rtn: 'bool',
    abort: true,
    filterFn: ({ property, parent }) => parent.splice(property, 1)
  }
)(data);

console.log(prune(myData, [0, 1, 0])); // returns true iff deleted
// => true

console.log(myData);
// => [ { name: 'bob', age: 5, subRows: [ { name: 'paul', age: 10, subRows: [] }, { name: 'claire', age: 20, subRows: [] } ] } ]
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@13.8.0"></script>

Disclaimer: I'm the author of object-scan

Related