【问题标题】:Updating array of objects with recursive function (Mapping replies to comments dynamically)使用递归函数更新对象数组(动态映射对评论的回复)
【发布时间】:2022-01-13 06:56:49
【问题描述】:

我正在从 graphql 后端接收以下格式的 cmets 列表:

[
        {
            "__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?"
        }
    ]

带有parentCommentId === null 的cmets 是最高级别的cmets,而带有parentCommentId !== null 的cmets 是对id === parentCommentId 的评论的回复

我想将此数据结构转换为:

[{
    "__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": []
  }
]

我有以下函数来做数据转换:

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);
      }
    });
  }
}

但是,这只适用于对象树深处的一层。所以我收到了主要评论的回复,但对回复的回复并未添加到对象中。

这就是我现在得到的:

[{
    "__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": []
  }
]

您能否指出我做错了什么并提供一些解释? 提前致谢

编辑:

根据@Nina Scholz 的评论,我想出了这个解决方案:

function formatData(data: Array < IComment > , root: string) {
  const temp: any = {};

  data.forEach((comment: IComment) => {
    const parentCommentId = comment.parentCommentId ? ? root;

    if (temp[parentCommentId] == null) {
      temp[parentCommentId] = {};
    }

    if (temp[parentCommentId].replies == null) {
      temp[parentCommentId].replies = [];
    }

    if (temp[comment.id] == null) {
      temp[parentCommentId].replies.push(
        Object.assign((temp[comment.id] = {}), comment)
      );
    } else {
      temp[parentCommentId].replies.push(
        Object.assign(temp[comment.id], comment)
      );
    }
  });
  return temp[root].replies;
}

标签: javascript typescript data-transform


【解决方案1】:

您可以在一个对象的帮助下进行一次迭代,该对象保持父级对子级和子级对父级的引用。

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; }

【讨论】:

  • 我很好奇,我们被告知将算法拆分为简单的逐步函数,以便我们可以对每一步进行单元测试。你的例子是一个更现实的现实世界的例子,还是它通常像我的回答一样分成几个步骤?或者您将如何对所有情况下的算法进行单元测试?
  • 您可以扩展每个包裹的部分并使用临时任务。
  • 感谢您的输入,它看起来很优雅!只是想把我的头绕在它周围并将其翻译成打字稿。我目前使用您的代码遇到此错误:You may need an additional loader to handle the result of these loaders.,我怀疑原因是 ??= 操作。您能否提示我如何以不同的方式重写该部分?
  • 模式x ??= y (几乎)有这个等价物:x = x || y。这意味着如果x 不是nullundefined 则分配y,如果x 是假的,则后者分配y
【解决方案2】:

由于其他答案对我来说很难理解,所以我也会发布我的答案,它将步骤分成独立的功能: (请注意,我在您的示例数据中添加了回复数组)

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))

【讨论】:

    猜你喜欢
    • 2017-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-13
    • 2017-10-11
    • 2019-07-06
    • 2011-07-24
    相关资源
    最近更新 更多