Home > Software design >  Javascript: Recursion returning undefined
Javascript: Recursion returning undefined

Time:09-17

Why is my recursion returning undefined? I'm trying to "decode" nested children data from mongo which is returned as IDs like:

{
    "_id": "613fd030f374cb62f8f91557",
    "children": [
        "613fd035f374cb62f8f9155b",
        "613fd136f374cb62f8f91564",
        "613fd1a5f374cb62f8f91571",
        "613fd20bf374cb62f8f9157c"
    ],
    ...more data
}

My goal is to drill down and convert each child ID to the Object the ID represensents and convert their child IDs to objects then keep going until the child === [] (no children). I'm trying to have the initial parent (613fd030f374cb62f8f91557) have access to all multi-level nested children objects.

This is my code:

const get_documents = (documents) => {
    // Loop through each document

    documents.map((document) => {
      if (document.parent === null) {
        //convert children ids (_id) to array of objects
        let dbData = [];

        document.children.map((id) => {
          let dbChildren = documents.find((x) => x._id === id);
          dbData.push(dbChildren);
        });

        let formattedData = [];

        dbData.map((child) => {
          let formattedObject = {
            id: child._id,
            name: child.name,
            depth: 0,
            parent: child.parent,
            closed: true,
            children: child_recursion(child.children), 
          };
            formattedData.push(formattedObject)
        });
      }
    });
  };
const child_recursion = (arr) => {
    let dbData = [];

    arr.map((id) => {
      let dbChildren = documents.find((x) => x._id === id);
      dbData.push(dbChildren);
    });

    let formattedData = [];

    dbData.map((child) => {
      
      let newChild = [];

      if (child.children.length > 1) {
        newChild = child_recursion(child.children);
      }

      let formattedObject = {
        id: child._id,
        name: child.name,
        depth: 0,
        parent: child.parent,
        closed: true,
        children: newChild,
      };
      
      formattedData.push(formattedObject);
     
     if (newChild === []) {
      return formattedData
     }
    });
  };

What am I doing wrong in my recursion? Thank you for the help!

CodePudding user response:

What is getting you here is mixing mutation with recursion which tends to make things a lot more messy.

What this line is telling me:

children: child_recursion(child.children), 

is that you are always expecting child_recursion to return an array of formatted children.

However, in child_recursion you aren't always returning something. Sometimes you are mutating sometimes instead. Personally, I believe that it tends to be easier to wrap my head around not using mutation.

The process, therefore, should go something like this:

  1. given an object
  2. check if that object has children
    1. if it does convert the children using this function
    2. if it does not, stop recursion
  3. return a new object, created from the input object with my children set to the output of the conversion.

In this way we can convert each child into an object with its children converted and so on.

Also it is somewhat strange that you are trying to convert all documents at once. Instead, as you gave in your question, you should focus on the object you are trying to convert and work downwards from there. If it is the case where objects can be both parents and children then you have a graph, not a tree and recursion would have to be handled differently than you are expecting.

We don't really need two functions to do this, just one and in the case where you already have the objects you are searching you can pass that along as well (if you don't just remove documents and get them from the db or some service instead). We can also use what is called an accumulator to set initial values before our recursion and track them as we recur.

const convert_children = (obj, documents) => {
  const convert_children_acc = (obj, documents, parent, depth) => {
    let partial_format = {
      id: obj._id,
      name: obj.name,
      depth: depth,
      parent: parent,
      close: true
    }
    if (obj.children && obj.children.length === 0) {
      return {
        ...partial_format,
        children: []
      }
    } else {
      return {
        ...partial_format,
        children: obj.children.map(child => {
          child = documents.find(x => child === x._id);
          return convert_children_acc(child, documents, obj._id, depth 1)
        })
      }
    }
  }
  return convert_children_acc(obj, documents, null, 0);
};

https://jsfiddle.net/5gaLw1y7/

  • Related