- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
给定以下输入:
<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/
我是一名优秀的程序员,十分优秀!