JavaScript Build tree array from flat array in Typescript / JavaScript build tree array from flat array in javascript,build tree

I have a complex json file that I have to handle with TypeScript / Javascript to make it hierarchical, in order to later build a questionnaire. Every entry of the json has a Id (unique), ParentId (0 If root), Text, Description.

My Typescript Interface

export interface Question {
    Id: number;
    Text: string;
    Desc: string;
    ParentId: number;
    ChildAnswers?: Answer[];
}

export interface Answer {
    Id: number;
    Text: string;
    Desc: string;
    ParentId: number;
    ChildQuestion?: Question;
}

I can guarantee that when the object is an answer it will only have one child which we can assume to be a question.

Flat Data Example :

[
{
    Id: 1,
    Text: 'What kind of apple is it?',
    Desc: '',
    ParentId: 0
},
{
    Id: 2,
    Text: 'Green Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 3,
    Text: 'Red Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 4,
    Text: 'Purple GMO Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 5,
    Text: 'What is the issue with the apple?',
    Desc: '',
    ParentId: 2
},
{
    Id: 6,
    Text: 'Spoiled.',
    Desc: '',
    ParentId: 5
},
{
    Id: 7,
    Text: 'Taste Bad.',
    Desc: '',
    ParentId: 5
},
{
    Id: 8,
    Text: 'Too Ripe.',
    Desc: '',
    ParentId: 5
},
{
    Id: 9,
    Text: 'Is not an apple.',
    Desc: '',
    ParentId: 5
},
{
    Id: 10,
    Text: 'The apple was not green.',
    Desc: '',
    ParentId: 5
},
... So on ...
]

My Goal

{
    Id: 1,
    Text: 'What kind of apple is it?',
    Desc: '',
    ParentId: 0,
    ChildAnswers: [
        {
            Id: 2,
            Text: 'Green Apple',
            Desc: '',
            ParentId: 1,
            ChildQuestion: {
                Id: 5,
                Text: 'What is the issue with the apple?',
                Desc: '',
                ParentId: 2,
                ChildAnswers: [
                    {
                        Id: 6,
                        Text: 'Spoiled.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 7,
                        Text: 'Taste Bad.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 8,
                        Text: 'Too Ripe.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 9,
                        Text: 'Is not an apple.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    {
                        Id: 10,
                        Text: 'The apple was not green.',
                        Desc: '',
                        ParentId: 5,
                        ... So on ...
                    },
                    ... So on ...
                ]
            }
        },
        {
            Id: 3,
            Text: 'Red Apple',
            Desc: '',
            ParentId: 1,
            ... So on ...
        },
        {
            Id: 4,
            Text: 'Red Apple',
            Desc: '',
            ParentId: 1,
            ... So on ...
        }
        ... So on ...
    ]
}

I'm currently using this list_to_tree function I found here on stackoverflow, I just don't know if how to tell a question and answer apart. Should I just check to see if the length is one for a question or at odd intervals mark it?:

function list_to_tree(list) {
    var map = {}, node, roots = [], i;
    for (i = 0; i < list.length; i += 1) {
        map[list[i].Id] = i; // initialize the map
        list[i].Children = []; // initialize the children
    }
    for (i = 0; i < list.length; i += 1) {
        node = list[i];
        if (node.ParentId !== 0) {
            // if you have dangling branches check that map[node.ParentId] exists
            list[map[node.ParentId]].Children.push(node);
        } else {
            roots.push(node);
        }
    }
    return roots;
}
Answer:1

Here is a brute force solution to the problem:

var flat = [
{
    Id: 1,
    Text: 'What kind of apple is it?',
    Desc: '',
    ParentId: 0
},
{
    Id: 2,
    Text: 'Green Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 3,
    Text: 'Red Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 4,
    Text: 'Purple GMO Apple',
    Desc: '',
    ParentId: 1
},
{
    Id: 5,
    Text: 'What is the issue with the apple?',
    Desc: '',
    ParentId: 2
},
{
    Id: 6,
    Text: 'Spoiled.',
    Desc: '',
    ParentId: 5
},
{
    Id: 7,
    Text: 'Taste Bad.',
    Desc: '',
    ParentId: 5
},
{
    Id: 8,
    Text: 'Too Ripe.',
    Desc: '',
    ParentId: 5
},
{
    Id: 9,
    Text: 'Is not an apple.',
    Desc: '',
    ParentId: 5
},
{
    Id: 10,
    Text: 'The apple was not green.',
    Desc: '',
    ParentId: 5
},
]

// first get the roots
const tree = flat.filter((question) => question.ParentId === 0);

// Next we are going to call alternating methods recursively.
function populateQuestionChildren(node) {
  const { Id } = node;
  flat.forEach((answer) => {
    if (answer.ParentId === Id) {
      if (!node.ChildAnswers) {
        node.ChildAnswers = [];
      }
      node.ChildAnswers.push(answer);
      populateAnswerChildren(answer);
    }
  });
}

function populateAnswerChildren(node) {
  const { Id } = node;
  flat.forEach((question) => {
    if (question.ParentId === Id) {
      if (!node.ChildQuestions) {
        node.ChildQuestions = [];
      }
      node.ChildQuestions.push(question);
      populateQuestionChildren(question);
    }
  });
}

// Kick off the build for each question tree. 
tree.forEach((question) => {
  populateQuestionChildren(question);
});

It is likely that there are more elegant solutions - but given that this will be only few dozen or a few hundred question/answers - this should get you what you need.

[EDIT]

I used your interfaces and discovered a problem with my code. There is only one "ChildQuestion" on an Answer object. So here is my change to TypeScript to make it work properly. I hope it helps:


interface Question {
  Id: number;
  Text: string;
  Desc: string;
  ParentId: number;
  ChildAnswers ? : Answer[];
}

interface Answer {
  Id: number;
  Text: string;
  Desc: string;
  ParentId: number;
  ChildQuestion ? : Question;
}

// first get the roots
const tree = flat.filter((question) => question.ParentId === 0);

function populateQuestionChildren(node: Question) {
  const { Id } = node;
  flat.forEach((answer) => {
    if (answer.ParentId === Id) {
      if (!node.ChildAnswers) {
        node.ChildAnswers = [];
      }
      node.ChildAnswers.push(answer);
      populateAnswerChild(answer);
    }
  });
}

function populateAnswerChild(answer: Answer) {
  const { Id } = answer;
  // switch to every so we can break early once a question is found.
  flat.every((node) => {
    if (node.ParentId === Id) {
      answer.ChildQuestion = node;
      populateQuestionChildren(node);
      return false;
    }
    return true;
  });
}

tree.forEach((question) => {
  populateQuestionChildren(question);
});
Answer:2

I would like to store result from mysql query in an array ( docAutocomplete) and after all the query completed I wanted to see the final array. I am using async series for this purpose. The issue is ...

I would like to store result from mysql query in an array ( docAutocomplete) and after all the query completed I wanted to see the final array. I am using async series for this purpose. The issue is ...

  1. async series with promises
  2. async series with arguments
  3. async series with await
  4. async.series in node js
  5. async series for loop
  6. async.series in javascript
  7. async series in angular

I am using a off canvas menu similar to the one on W3 Schools by having two functions called to toggle the menu on/off using onclick. I am wanting to add in a function to be able to close the menu by ...

I am using a off canvas menu similar to the one on W3 Schools by having two functions called to toggle the menu on/off using onclick. I am wanting to add in a function to be able to close the menu by ...

I have AWS Cognito installed for my Angular application, and I am trying to hook in the s3.putObject some how into the ng-file-upload Upload service. I want to use ng-file-upload's Upload.upload() ...

I have AWS Cognito installed for my Angular application, and I am trying to hook in the s3.putObject some how into the ng-file-upload Upload service. I want to use ng-file-upload's Upload.upload() ...

  1. upload file using ajax
  2. upload file using retrofit android example
  3. upload file using javascript
  4. upload file using postman
  5. upload file using jquery
  6. upload file using ajax in php example
  7. upload file using php
  8. upload file using curl
  9. upload file using ajax php
  10. upload file using json
  11. upload file using python script
  12. upload file using ssh
  13. upload file using selenium
  14. upload file using ajax jquery
  15. upload file using putty
  16. upload file using curl command line
  17. upload file using httpwebrequest c#
  18. upload file using volley android
  19. upload file using web api c#
  20. upload file using javascript ajax

I want to know if it is more correct to use innerHTML: function createModal(){ modal = document.createElement('div'); modal.id = "modal"; modal.className = "modal"; modal.innerHTML = "<...

I want to know if it is more correct to use innerHTML: function createModal(){ modal = document.createElement('div'); modal.id = "modal"; modal.className = "modal"; modal.innerHTML = "<...

  1. append getelementbyid innerhtml
  2. element append innerhtml
  3. jquery append element innerhtml