0

I am desperately trying to get the selected nodes from angular tree in JSON nested format. So far I managed to get the selected array of flat nodes with this.checklistSelection.selected. But what I need, I need to get the selected nodes in JSON format, with all nested JSON objects by their level.

[{item: "Risk Analysis", level: 0, expandable: true}
,{item: "Standard", level: 1, expandable: true}
,{item: "Active", level: 2, expandable: true}
,{item: "Volatility", level: 3, expandable: true}
,{item: "Contribution", level: 4, expandable: true}
,{item: "Total", level: 5, expandable: false}
,{item: "Systematic", level: 5, expandable: false}
,{item: "Specific", level: 5, expandable: false}
,{item: "VaR (95%, 2 weeks, Chebyshev)", level: 3, expandable: true}
,{item: "Contribution", level: 4, expandable: true}
,{item: "Total", level: 5, expandable: false}
,{item: "Systematic", level: 5, expandable: false}
,{item: "Specific", level: 5, expandable: false}
,{item: "Benchmark", level: 2, expandable: true}
,{item: "Volatility", level: 3, expandable: true}
,{item: "Contribution", level: 4, expandable: true}
,{item: "Total", level: 5, expandable: false}
,{item: "Systematic", level: 5, expandable: false}
,{item: "Specific", level: 5, expandable: false}
,{item: "VaR (95%, 2 weeks, Chebyshev)", level: 3, expandable: true}
,{item: "Contribution", level: 4, expandable: true}
,{item: "Total", level: 5, expandable: false}
,{item: "Systematic", level: 5, expandable: false}
,{item: "Specific", level: 5, expandable: false}
,{item: "Portfolio", level: 2, expandable: true}
,{item: "Volatility", level: 3, expandable: true}
,{item: "Contribution", level: 4, expandable: true}
,{item: "Total", level: 5, expandable: false}
,{item: "Systematic", level: 5, expandable: false}
,{item: "Specific", level: 5, expandable: false}
,{item: "VaR (95%, 2 weeks, Chebyshev)", level: 3, expandable: true}
,{item: "Contribution", level: 4, expandable: true}
,{item: "Total", level: 5, expandable: false}
,{item: "Systematic", level: 5, expandable: false}
,{item: "Specific", level: 5, expandable: false}]

Expected:

"Risk Analysis": {
      "Standard": {
        "Active": {
          "Volatility": {
            "Contribution": ["Total", "Systematic", "Specific"]
          },
          "VaR (95%, 2 weeks, Chebyshev)": {
            "Contribution": ["Total", "Systematic", "Specific"]
          }
        },
        "Portfolio": {
          "Volatility": {
            "Contribution": ["Total", "Systematic", "Specific"]
          },
          "VaR (95%, 2 weeks, Chebyshev)": {
            "Contribution": ["Total", "Systematic", "Specific"]
          }
        },
        "Benchmark": {
          "Volatility": {
            "Contribution": ["Total", "Systematic", "Specific"]
          },
          "VaR (95%, 2 weeks, Chebyshev)": {
            "Contribution": ["Total", "Systematic", "Specific"]
          }
        }
      }
    }
  }

Can someone point me out if there a method that Mat tree offers, or any kind of function that could do this magic?

Thanks in advance :)

1
  • Where is "Volatility" in the original object? Commented May 28, 2021 at 12:10

2 Answers 2

1

In order to build a tree, you need to pre-process your data by assigning IDs to each of your items. You can use a stack to keep track of the relationships as you assign them.

You can accomplish this in phases:

  1. Assign id and parentId keys for each item (applyRelationships)
  2. Convert the flat array into a tree (listToTree)
  3. Convert the tree into an object (treeToObject)

In the original example, I brute-forced the nesting of each object by setting max-depth. I did not utilize the expandable property. In this modified example, I ditched the maxDepth paramater.

const main = () => {
  useCases.forEach(({ data, expected }) => {
    const actual = buildTreeObject(data);
    console.log(JSON.stringify(actual) === JSON.stringify(expected));
    console.log(actual);
  });
};

const useCases = [{
  data: [
    { item: "Risk Analysis", level: 0, expandable: true },
    { item: "Volatility", level: 1, expandable: true },
    { item: "Total", level: 2, expandable: false },
    { item: "Systematic", level: 2, expandable: false },
    { item: "Specific", level: 2, expandable: false },
    { item: "TaR (68%, 1 year)", level: 1, expandable: true },
    { item: "Total", level: 2, expandable: false },
    { item: "Systematic", level: 2, expandable: false },
    { item: "Specific", level: 2, expandable: false },
    { item: "VaR (95%, 2 weeks, Chebyshev)", level: 1, expandable: true },
    { item: "Total", level: 2, expandable: false },
    { item: "Systematic", level: 2, expandable: false },
    { item: "Specific", level: 2, expandable: false }
  ],
  expected: {
    "Risk Analysis": {
      "Volatility": ["Total", "Systematic", "Specific"],
      "TaR (68%, 1 year)": ["Total", "Systematic", "Specific"],
      "VaR (95%, 2 weeks, Chebyshev)": ["Total", "Systematic", "Specific"]
    }
  }
}, {
  data: [
    { item: "Risk Analysis", level: 0, expandable: true },
    { item: "Standard", level: 1, expandable: true },
    { item: "Active", level: 2, expandable: true },
    { item: "Volatility", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false }
  ],
  expected: {
    "Risk Analysis": {
      "Standard": {
        "Active": {
          "Volatility": {
            "Contribution": [ "Total", "Systematic", "Specific" ]
          }
        }
      }
    }
  }
}, {
  data: [
    { item: "Risk Analysis", level: 0, expandable: true },
    { item: "Standard", level: 1, expandable: true },
    { item: "Active", level: 2, expandable: true },
    { item: "Volatility", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false },
    { item: "VaR (95%, 2 weeks, Chebyshev)", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false },
    { item: "Benchmark", level: 2, expandable: true },
    { item: "Volatility", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false },
    { item: "VaR (95%, 2 weeks, Chebyshev)", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false },
    { item: "Portfolio", level: 2, expandable: true },
    { item: "Volatility", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false },
    { item: "VaR (95%, 2 weeks, Chebyshev)", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false }
  ],
  expected: {
    "Risk Analysis": {
      "Standard": {
        "Active": {
          "Volatility": {
            "Contribution": ["Total", "Systematic", "Specific"]
          },
          "VaR (95%, 2 weeks, Chebyshev)": {
            "Contribution": ["Total", "Systematic", "Specific"]
          }
        },
        "Benchmark": {
          "Volatility": {
            "Contribution": ["Total", "Systematic", "Specific"]
          },
          "VaR (95%, 2 weeks, Chebyshev)": {
            "Contribution": ["Total", "Systematic", "Specific"]
          }
        },
        "Portfolio": {
          "Volatility": {
            "Contribution": ["Total", "Systematic", "Specific"]
          },
          "VaR (95%, 2 weeks, Chebyshev)": {
            "Contribution": ["Total", "Systematic", "Specific"]
          }
        },
      }
    }
  }
}];

const applyRelationships = (data) => {
  let levelStack = [], lastNode = null;
  return data.map((curr, index) => {
    const node = { ...curr, id: index + 1 };
    if (levelStack.length === 0) {
      levelStack.push({ level: node.level, parent: 0 });
    } else {
      const last = levelStack[levelStack.length - 1];
      if (node.level > last.level) {
        levelStack.push({ level: node.level, parent: lastNode.id });
      } else if (node.level < last.level) {
        const
          levelDiff = last.level - node.level - 1,
          lastIndex = levelStack.length - 1;
        levelStack.splice(lastIndex - levelDiff, lastIndex);
      }
    }
    node.parentId = levelStack[levelStack.length - 1].parent;
    lastNode = node;
    return node;
  });
};

const listToTree = (arr = []) => {
   let indexMap = new Map();
   arr.forEach((node, index) => {
      indexMap.set(node.id, index)
      node.children = [];
   });
   return arr.reduce((res, node, index, all) => {
      if (node.parentId === 0) return [...res, node];
      all[indexMap.get(node.parentId)].children.push(node);
      return res;
   }, []);
};

const treeToObject = (tree = [], result = {}) => {
  tree.forEach(child => {
    if (!child.expandable) {
      result.push(child.item);
    } else {
      const childrenAllEmpty = child.children
        .every(({ children }) => children.length === 0);
      result[child.item] = childrenAllEmpty ? [] : {};
      treeToObject(child.children, result[child.item]);
    }
  });
  return result;
};

const buildTreeObject = (arr = []) =>
  treeToObject(listToTree(applyRelationships(arr)));
  
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }


Original response

const main = () => {
  useCases.forEach(({ data, params: { maxDepth }, expected }) => {
    const actual = buildTreeObject(data, maxDepth);
    console.log(JSON.stringify(actual) === JSON.stringify(expected));
    console.log(actual);
  });
};

const useCases = [{
  data: [
    { item: "Risk Analysis", level: 0, expandable: true },
    { item: "Volatility", level: 1, expandable: true },
    { item: "Total", level: 2, expandable: false },
    { item: "Systematic", level: 2, expandable: false },
    { item: "Specific", level: 2, expandable: false },
    { item: "TaR (68%, 1 year)", level: 1, expandable: true },
    { item: "Total", level: 2, expandable: false },
    { item: "Systematic", level: 2, expandable: false },
    { item: "Specific", level: 2, expandable: false },
    { item: "VaR (95%, 2 weeks, Chebyshev)", level: 1, expandable: true },
    { item: "Total", level: 2, expandable: false },
    { item: "Systematic", level: 2, expandable: false },
    { item: "Specific", level: 2, expandable: false }
  ],
  params : { maxDepth: 1 },
  expected: {
    "Risk Analysis": {
      "Volatility": ["Total", "Systematic", "Specific"],
      "TaR (68%, 1 year)": ["Total", "Systematic", "Specific"],
      "VaR (95%, 2 weeks, Chebyshev)": ["Total", "Systematic", "Specific"]
    }
  }
}, {
  data: [
    { item: "Risk Analysis", level: 0, expandable: true },
    { item: "Standard", level: 1, expandable: true },
    { item: "Active", level: 2, expandable: true },
    { item: "Volatility", level: 3, expandable: true },
    { item: "Contribution", level: 4, expandable: true },
    { item: "Total", level: 5, expandable: false },
    { item: "Systematic", level: 5, expandable: false },
    { item: "Specific", level: 5, expandable: false }
  ],
  params: { maxDepth: 4 },
  expected: {
    "Risk Analysis": {
      "Standard": {
        "Active": {
          "Volatility": {
            "Contribution": [ "Total", "Systematic", "Specific" ]
          }
        }
      }
    }
  }
}];

const applyRelationships = (data) => {
  let levelStack = [], lastNode = null;
  return data.map((curr, index) => {
    const node = { ...curr, id: index + 1 };
    if (levelStack.length === 0) {
      levelStack.push({ level: node.level, parent: 0 });
    } else {
      const last = levelStack[levelStack.length - 1];
      if (node.level > last.level) {
        levelStack.push({ level: node.level, parent: lastNode.id });
      } else if (node.level < last.level) {
        levelStack.pop();
      }
    }
    node.parentId = levelStack[levelStack.length - 1].parent;
    lastNode = node;
    return node;
  });
};

const listToTree = (arr = []) => {
   let indexMap = new Map();
   arr.forEach((node, index) => {
      indexMap.set(node.id, index)
      node.children = [];
   });
   return arr.reduce((res, node, index, all) => {
      if (node.parentId === 0) return [...res, node];
      all[indexMap.get(node.parentId)].children.push(node);
      return res;
   }, []);
};

const treeToObject = (tree, maxDepth = 1, result = {}) => {
  tree.forEach(child => {
    result[child.item] = {};
    if (child.level >= maxDepth) {
      result[child.item] = child.children.map(({ item }) => item);
    } else {
      treeToObject(child.children, maxDepth, result[child.item]);
    }
  });
  return result;
};

const buildTreeObject = (arr = [], maxDepth = 1) =>
  treeToObject(listToTree(applyRelationships(arr)), maxDepth);
  
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }

Sign up to request clarification or add additional context in comments.

6 Comments

and one more thing will it be possible to extend these methods to support nesting on more levels. Like transforming this array into multilevel JSON object based on the 'level' property [{item: "Risk Analysis", level: 0, expandable: true}, {item: "Standard", level: 1, expandable: true}, {item: "Active", level: 2, expandable: true}, {item: "Volatility", level: 3, expandable: true}, {item: "Contribution", level: 4, expandable: true}, {item: "Total", level: 5, expandable: false}, {item: "Systematic", level: 5, expandable: false}, {item: "Specific", level: 5, expandable: false}]
@NatasaMarinkovic I am gonna tweak the maxDepth param. Not sure why it it not going further.
I think the applyRelationships () is not making correct parentIDs when nesting goes deeper than 3 levels. Let me know if u succeed to tweak it. Thank you for you help, I really appreciate it!
@NatasaMarinkovic I fixed it. I stored a reference to the previous node to access its ID. I added the example call at the very end of the code. I set the maxDepth to 4.
Oh, I cant still get it to work with more complex arrays. It would be good not to depend on a max depth argument cause I cannot know in advance the max depth of a JSON object. Try with this one please in the updated question.
|
0

Angular material tree to JSON Object converter.

export class TodoItemNode {
  children: TodoItemNode[];
  item: string;
}

treeToObject(result: any, nodes: TodoItemNode[]): any {
 for (const node of nodes) {
  if (node.children && node.children.length > 0) {
    let isArray = true;
    for (const child of node.children) {
      if (child.children && child.children.length > 0) {
        isArray = false;
        break;
      }
    }
    if (isArray) {
      result[node.item] = [];
      for (const child of node.children) {
        result[node.item].push(child.item);
      }
    } else {
      result[node.item] = {};
      this.treeToObject(result[node.item], node.children);
    }
  } else {
    result[node.item] = null;
  }
 }
 return result;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.