gpt4 book ai didi

javascript - 如何从 NodeList 递归构造 JSON 层次结构?

转载 作者:行者123 更新时间:2023-12-05 01:09:10 26 4
gpt4 key购买 nike

给定以下输入:

<dl>
<dt>
<h3>Title A</h3>
<dl>
<dt>
<h3>Title A- A</h3>
<dl>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
</dl>
</dt>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
<dt>
<h3>Title B- A</h3>
<dl>
<dt><a href="#">Item</a></dt>
<dt><a href="#">Item</a></dt>
</dl>
</dt>
<dt><a href="#">Item</a></dt>
</dl>
</dt>
</dl>

我想根据上面的输入构建一个 JSON 对象:

{
"title": "Title A",
"children": [
{
"title": "Title A- A",
"children": [
{"title": "Item"},
{"title": "Item"}
]
},
{"title": "Item"},
{"title": "Item"},
{"title": "Item"},
{"title": "Item"},
{
"title": "Title B- A",
"children": [
{"title": "Item"},
{"title": "Item"}
]
},
{"title": "Item"}
]
}

这是我迄今为止尝试过的:

function buildTree(node) {
if (!node) return [];
const h3 = node.querySelector('h3') || node.querySelector('a');
let result = {
title: h3.innerText,
children: []
};
const array = [...node.querySelectorAll('dl')];
if (array) {
result.children = array.map(el => buildTree(el.querySelector('dt')));
}
return result;
}

我得到的结果与我的预期不同,这是我得到的结果:

{
"title": "Title A",
"children": [
{
"title": "Title A",
"children": [
{
"title": "Title A- A",
"children": [
{
"title": "Item A- A 1",
"children": []
}
]
},
{
"title": "Item A- A 1",
"children": []
},
{
"title": "Title B- A 1",
"children": []
}
]
},
{
"title": "Title A- A",
"children": [
{
"title": "Item A- A 1",
"children": []
}
]
},
{
"title": "Item A- A 1",
"children": []
},
{
"title": "Title B- A 1",
"children": []
}
]
}

似乎有些数据不存在,知道我可能遗漏了什么吗?

最佳答案

修复 html

首先我要说的是您误用了 dl .来自 MDN docs -

The HTML <dl> element represents a description list. The element encloses a list of groups of terms (specified using the <dt> element) and descriptions (provided by <dd> elements) ...

以下是dl 的正确用法, dt , 和 dd看起来像 -

<dl>
<dt>Title 1</dt>
<dd>
<dl>
<dt>Title 1.1</dt>
<dd><a href="#">Item 1.1.1</a></dd>
<dd><a href="#">Item 1.1.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.2</a></dd>
<dd><a href="#">Item 1.3</a></dd>
<dd><a href="#">Item 1.4</a></dd>
<dd><a href="#">Item 1.5</a></dd>
<dd>
<dl>
<dt>Title 1.6</dt>
<dd><a href="#">Item 1.6.1</a></dd>
<dd><a href="#">Item 1.6.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.7</a></dd>
</dl>

注意它与您输出的预期形状相匹配 -

{
"title": "Title 1",
"children": [
{
"title": "Title 1.1",
"children": [
{"title": "Item 1.1.1"},
{"title": "Item 1.1.2"}
]
},
{"title": "Item 1.2"},
{"title": "Item 1.3"},
{"title": "Item 1.4"},
{"title": "Item 1.5"},
{
"title": "Title 1.6",
"children": [
{"title": "Item 1.6.1"},
{"title": "Item 1.6.2"}
]
},
{"title": "Item 1.7"}
]
}

来自Html

如果您不愿意(或不能)如上所述更改输入 html,请参阅 Scott 的精彩回答。要为提议的 html 编写程序,我会将其分为两部分。首先我们写fromHtml使用简单的递归形式 -

function fromHtml (e)
{ switch (e?.tagName)
{ case "DL":
return Array.from(e.childNodes, fromHtml).flat()
case "DD":
return [ Array.from(e.childNodes, fromHtml).flat() ]
case "DT":
case "A":
return e.textContent
default:
return []
}
}

fromHtml(document.querySelector('dl'))

这为我们提供了这种中间格式 -

[
"Title 1",
[
"Title 1.1",
[ "Item 1.1.1" ],
[ "Item 1.1.2" ]
],
[ "Item 1.2" ],
[ "Item 1.3" ],
[ "Item 1.4" ],
[ "Item 1.5" ],
[
"Title 1.6",
[ "Item 1.6.1" ],
[ "Item 1.6.2" ]
],
[ "Item 1.7" ]
]

应用标签

接下来,我会写一个单独的 applyLabels添加 title 的函数和 children您需要的标签 -

const applyLabels = ([ title, ...children ]) =>
children.length
? { title, children: children.map(applyLabels) }
: { title }

const result =
applyLabels(fromHtml(document.querySelector('dl')))
{
"title": "Title 1",
"children": [
{
"title": "Title 1.1",
"children": [
{"title": "Item 1.1.1"},
{"title": "Item 1.1.2"}
]
},
{"title": "Item 1.2"},
{"title": "Item 1.3"},
{"title": "Item 1.4"},
{"title": "Item 1.5"},
{
"title": "Title 1.6",
"children": [
{"title": "Item 1.6.1"},
{"title": "Item 1.6.2"}
]
},
{"title": "Item 1.7"}
]
}

我可能会建议进行最后一项更改,以保证输出中的所有节点都具有统一的形状,{ title, children } .这是一个值得注意的变化,因为在这种情况下 applyLabels更容易编写并且表现更好 -

const applyLabels = ([ title, ...children ]) =>
({ title, children: children.map(applyLabels) })

是的,这意味着最深的后代将有一个空的 children: []属性,但它使使用数据变得更加容易,因为我们不必对某些属性进行空值检查。


演示

展开下面的代码片段以验证 fromHtml 的结果和 applyLabels在您自己的浏览器中 -

function fromHtml (e)
{ switch (e?.tagName)
{ case "DL":
return Array.from(e.childNodes, fromHtml).flat()
case "DD":
return [ Array.from(e.childNodes, fromHtml).flat() ]
case "DT":
case "A":
return e.textContent
default:
return []
}
}

const applyLabels = ([ title, ...children ]) =>
children.length
? { title, children: children.map(applyLabels) }
: { title }

const result =
applyLabels(fromHtml(document.querySelector('dl')))

console.log(result)
<dl>
<dt>Title 1</dt>
<dd>
<dl>
<dt>Title 1.1</dt>
<dd><a href="#">Item 1.1.1</a></dd>
<dd><a href="#">Item 1.1.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.2</a></dd>
<dd><a href="#">Item 1.3</a></dd>
<dd><a href="#">Item 1.4</a></dd>
<dd><a href="#">Item 1.5</a></dd>
<dd>
<dl>
<dt>Title 1.6</dt>
<dd><a href="#">Item 1.6.1</a></dd>
<dd><a href="#">Item 1.6.2</a></dd>
</dl>
</dd>
<dd><a href="#">Item 1.7</a></dd>
</dl>


备注

我已经写了数百个关于递归和数据转换主题的答案,但这是我认为我使用 .flat第一次以一种必不可少的方式。我以为我在 this Q&A 中有一个用例但斯科特的评论从我这里拿走了!这个答案不同,因为 domNode.childNodes不是一个真正的数组,所以 Array.prototype.flatMap不能使用。感谢您提出有趣的问题。

关于javascript - 如何从 NodeList 递归构造 JSON 层次结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65521584/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com