Home > other >  Updating array of objects with recursive function (Mapping replies to comments dynamically)
Updating array of objects with recursive function (Mapping replies to comments dynamically)

Time:12-08

I am receiving a list of comments from a graphql backend in the following format:

[
        {
            "__typename": "Comment",
            "id": "1",
            "userId": "1",
            "postId": "1",
            "parentCommentId": null,
            "content": "test 1"
        },
        {
            "__typename": "Comment",
            "id": "2",
            "userId": "1",
            "postId": "1",
            "parentCommentId": null,
            "content": "this is a comment"
        },
        {
            "__typename": "Comment",
            "id": "34",
            "userId": "1",
            "postId": "1",
            "parentCommentId": "1",
            "content": "reply to test1"
        },
        {
            "__typename": "Comment",
            "id": "35",
            "userId": "1",
            "postId": "1",
            "parentCommentId": "34",
            "content": "nested reply to \"reply to test1\"\n\n"
        },
        {
            "__typename": "Comment",
            "id": "36",
            "userId": "1",
            "postId": "1",
            "parentCommentId": "34",
            "content": "test?"
        }
    ]
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

The comments with parentCommentId === null are the highest level comments, while comments where parentCommentId !== null are replies to a comment where id === parentCommentId

I would like to transform this data structure to something like:

[{
    "__typename": "Comment",
    "id": "1",
    "userId": "1",
    "postId": "1",
    "parentCommentId": null,
    "content": "test1",
    "replies": [{
      "__typename": "Comment",
      "id": "34",
      "userId": "1",
      "postId": "1",
      "parentCommentId": "1",
      "content": "reply to test1",
      "replies": [{
        "__typename": "Comment",
        "id": "35",
        "userId": "1",
        "postId": "1",
        "parentCommentId": "34",
        "content": "reply to test1"
      }]
    }]
  },
  {
    "__typename": "Comment",
    "id": "2",
    "userId": "1",
    "postId": "1",
    "parentCommentId": null,
    "content": "this is a comment",
    "replies": []
  }
]
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

I have the following function to do the data transformation:

function formatData(comments: Array < IComment > ) {
  let commentList = Array < IComment > ();

  // add comments without `parentCommentId` to the list.
  // these are top level comments.
  for (let i = 0; i < comments.length; i  ) {
    if (!comments[i].parentCommentId) {
      commentList.push({ ...comments[i],
        replies: []
      });
    }
  }

  for (let i = 0; i < comments.length; i  ) {
    if (comments[i].parentCommentId) {
      const reply = comments[i];
      mapReplyToComment(commentList, reply);
    }
  }


  return commentList;

  function mapReplyToComment(
    commentList: Array < IComment > ,
    reply: IComment
  ): any {
    return commentList.map((comment) => {
      if (!comment.replies) {
        comment = { ...comment,
          replies: []
        };
      }
      if (comment.id === reply.parentCommentId) {
        comment.replies.push(reply);

        return comment;
      } else {
        return mapReplyToComment(comment.replies, reply);
      }
    });
  }
}
<iframe name="sif3" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

However this only works for one level deep into the object tree. so I am getting the replies of a main comment, but replies to replies are not added to the object.

this is what I am getting now:

[{
    "__typename": "Comment",
    "id": "1",
    "userId": "1",
    "postId": "1",
    "parentCommentId": null,
    "content": "test1",
    "replies": [{
      "__typename": "Comment",
      "id": "34",
      "userId": "1",
      "postId": "1",
      "parentCommentId": "1",
      "content": "reply to test1"
      // -- I should have here another node of "replies"
    }]
  },
  {
    "__typename": "Comment",
    "id": "2",
    "userId": "1",
    "postId": "1",
    "parentCommentId": null,
    "content": "this is a comment",
    "replies": []
  }
]
<iframe name="sif4" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

Could you please point out what am I doing wrong and provide some explanation? Thanks in advance

CodePudding user response:

You could take a single iteration with the help of an object which keeps the references of parent to children and children to parent.

const
    getTree = (data, root) => {
        const t = {};
        data.forEach(o =>
            ((t[o.parentCommentId] ??= {}).replies ??= []).push(
                Object.assign(t[o.id] ??= {}, o)
            )
        );
        return t[root].replies;
    },
    data = [{ __typename: "Comment", id: "1", userId: "1", postId: "1", parentCommentId: null, content: "test 1" }, { __typename: "Comment", id: "2", userId: "1", postId: "1", parentCommentId: null, content: "this is a comment" }, { __typename: "Comment", id: "34", userId: "1", postId: "1", parentCommentId: "1", content: "reply to test1" }, { __typename: "Comment", id: "35", userId: "1", postId: "1", parentCommentId: "34", content: "nested reply to \"reply to test1\"\n\n" }, { __typename: "Comment", id: "36", userId: "1", postId: "1", parentCommentId: "34", content: "test?" }],
    tree = getTree(data, null);

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<iframe name="sif5" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

Since the other answer was hard to understand for me, I will post mine as well which splits the steps into standalone functions: (Note that I added the replies array in your sample data)

let data = [{    "__typename": "Comment",    "id": "1",    "userId": "1",    "postId": "1",    "parentCommentId": null,    "content": "test 1",    "replies": []  },
  {    "__typename": "Comment",    "id": "2",    "userId": "1",    "postId": "1",    "parentCommentId": null,    "content": "this is a comment",    "replies": []  },
  {    "__typename": "Comment",    "id": "34",    "userId": "1",    "postId": "1",    "parentCommentId": "1",    "content": "reply to test1",    "replies": []  },
  {    "__typename": "Comment",    "id": "35",    "userId": "1",    "postId": "1",    "parentCommentId": "34",    "content": "nested reply to \"reply to test1\"\n\n",    "replies": []  },
  {    "__typename": "Comment",    "id": "36",    "userId": "1",    "postId": "1",    "parentCommentId": "34",    "content": "test?",    "replies": []  }
]

function findLowestComment(dataArray) {
  for (let i = 0; i < dataArray.length; i  ) {
    let comment = dataArray[i]
    isLowest = true
    if (comment.parentCommentId == null) {
      continue
    }
    for (let j = 0; j < dataArray.length; j  ) {
      if (dataArray[j].id != comment.id &&
        dataArray[j].parentCommentId == comment.id &&
        dataArray[j].parentCommentId != null) {
        isLowest = false;
        break
      }
    }
    if (isLowest) {
      return i
    }
  }
}

function insertIntoParent(dataArray, commentIndex) {
  for (let j = 0; j < dataArray.length; j  ) {
    if (dataArray[j].id == dataArray[commentIndex].parentCommentId) {
      dataArray[j].replies.push(dataArray[commentIndex])
      dataArray.splice(commentIndex, 1)
      break
    }
  }
}

function mapComments(dataArray) {
  for (let j = 0; j < dataArray.length; j  ) {
    let lowestIndex = findLowestComment(dataArray)
    insertIntoParent(dataArray, lowestIndex)
  }
}

mapComments(data)
console.log(JSON.stringify(data, undefined, 2))
<iframe name="sif6" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

  • Related