1

I have a recursive object of the structure:

const obj = {
  name: 'entity-0',
  children: [{
    name: 'entity-0-0',
    children: []
  }, {
    name: 'entity-0-1',
    children: [{
      name: 'entity-1-0',
      children: []
    }]
  }]
}

And I'm currently converting it to a flat array like this:

const arr = [];

function convertObjToArr(obj, level, parent) {
  arr.push({ name: obj.name, parent: parent?.name, level });
  obj.children.forEach(v => this.convertObjToArr(v, level + 1, obj));
}

convertObjToArr(obj, 0);

This results in array that looks like this:

[
  { name: 'entity-0', level: 0 },
  { name: 'entity-0-0', parent: 'entity-0', level: 1 },
  { name: 'entity-0-1', parent: 'entity-0', level: 1 },
  { name: 'entity-1-0', parent: 'entity-0-1', level:  2}
]

What I now need to do is reverse the process. Where I give the generated arr as the input and get obj as the output. Is this possible? How would I do this?

This is as far as I've gotten:

function convertArrToObj(iArr) {
  for(let i = 0; i < iArr.length; i++) {
    if(!iArr[i].parent) newObj = { ...iArr[i], children: [] };
    else {
      // this should somehow find iArr[i].parent and insert itself into its children array
    }
  }
}

But as you can see, it's incomplete and I'm pretty sure it's not gonna be recursive, so if it's more than 1 layer deep it won't work, I don't think.

SeriousLee
  • 1,301
  • 4
  • 20
  • 42
  • `this should somehow find iArr[i].parent` you mean like `if (newObj[iArr[i].parent])`? Are you just asking how to access an object's child based on a key stored in a variable? –  Nov 10 '21 at 09:34
  • 1
    Note that your intermediate format cannot readily distinguish between a tree and a forest of trees, so the answers here -- as well as at least some from the duplicates -- all include an additional array wrapper. If you know yours is a singly-rooted tree, you can simply take the first element from the result. – Scott Sauyet Nov 10 '21 at 14:43

2 Answers2

2

You could take a single loop and build a new structure.

const
    getTree = (data, root) => {
        const t = {};
        data.forEach(({ name, parent }) => 
            ((t[parent] ??= {}).children ??= []).push(Object.assign(t[name] ??= {}, { name }))
        );
        return t[root].children;
    },
    data = [{ name: "entity-0", parent: undefined, level: 0 }, { name: "entity-0-0", parent: "entity-0", level: 1 }, { name: "entity-0-1", parent: "entity-0", level: 1 }, { name: "entity-1-0", parent: "entity-0-1", level: 2 }],
    tree = getTree(data);

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Thanks for taking the time! That's some intimidating javascript (I love it!) so I'll have to do some separate reading to figure out how it works. But it seems that what it does is convert my tree structure to a flat array, is that right? Coz I actually already have that part down (as per my question). What I need is a way to now convert the generated flat array back to the tree structure. – SeriousLee Nov 10 '21 at 09:52
2

This might work.

If there is a single root, take the first element in the result array.

const arr = [
  { name: 'entity-0', level: 0 },
  { name: 'entity-0-0', parent: 'entity-0', level: 1 },
  { name: 'entity-0-1', parent: 'entity-0', level: 1 },
  { name: 'entity-1-0', parent: 'entity-0-1', level:  2}
];

const convertArrToObj = (arr, parent) => {
    return arr.filter(item => item.parent === parent)
        .reduce((acc, item) => {
            acc.push({name: item.name, children: convertArrToObj(arr, item.name)});
            return acc;
        }, []);
};

const result = convertArrToObj(arr);

console.log(result);

// using map is simpler, advised by Nick Parsons
const convertArrToObj2 = (arr, parent) => {
    return arr.filter(item => item.parent === parent)
        .map(item => ({name: item.name, children: convertArrToObj2(arr, item.name)}));
};

console.log(convertArrToObj2(arr));
ProDec
  • 5,390
  • 1
  • 3
  • 12
  • Nice, I think `.map()` would be more appropriate here instead of `.reduce()` though. – Nick Parsons Nov 10 '21 at 09:52
  • 1
    @NickParsons nice comment. it's better. – ProDec Nov 10 '21 at 09:59
  • Note that the output here looks like the original data, *except* that it's wrapped in an additional array. You can easily wrap this up in a call that takes the first element from the result, but this is fairly fundamental behavior since the intermediate format cannot readily distinguish between a tree and a forest of trees. – Scott Sauyet Nov 10 '21 at 14:41