- 前端
- UI组件
【免费下载链接】zag
Build your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines
导读
@zag-js/json-tree-utils是 zag 仓库中专门用于构建 JSON 树(Json Tree)组件的工具包,它把任意 JavaScript 值(普通对象、数组、Map、Set、Date、RegExp、TypedArray、DOM 元素甚至 React 元素)递归转换为结构化的JsonNode树,并进一步输出为 Hast 兼容的元素树,供 React、Solid、Vue、Svelte 或原生 JS 直接渲染。阅读本文后,你将掌握该包的安装方式、核心 API(jsonToTree、jsonNodeToElement、getAccessibleDescription等)、预览截断策略的调优参数,以及如何参考仓库中五种框架的示例搭建一个可折叠、可访问的 JSON 树查看器。
本文以 packages/utilities/json-tree-utils/README.md 为骨架,结合该包源码(src/json-to-tree.ts、src/data-type.ts、src/node-conversion.ts)与跨框架示例展开。
一、安装
官方 README 提供了两种包管理器安装方式,包名为@zag-js/json-tree-utils:
yarn add @zag-js/json-tree-utils # or npm i @zag-js/json-tree-utils从 package.json 可以看到,该包声明为纯工具库("sideEffects": false),发布产物为dist/**/*,当前版本为1.46.0。它在 zag monorepo 中是一个独立包,通过 pnpm workspace 管理,也可单独安装使用。安装后即可在任意框架项目中导入其导出的全部 API(见 src/index.ts)。
二、核心 API 一览:构建 JSON 树的最小流程
该包围绕「数据 → 节点树 → 可渲染元素 → 无障碍描述」一条流水线设计,核心导出包括:
| API | 作用 | 源码位置 |
|---|---|---|
jsonToTree(data, options?) | 把任意值递归转换为JsonNode树 | json-to-tree.ts |
getRootNode(data, opts?) | 包一层虚拟根节点(keyPath: []),返回可直接展开的根 | node-conversion.ts |
jsonNodeToElement(node, opts?) | 将单个节点转换为 Hast 兼容元素(span/div/a) | data-type.ts |
getAccessibleDescription(node, opts?) | 生成节点的aria-label描述文本 | accessibility.ts |
keyPathToId/keyPathToKey/isRootKeyPath | keyPath 序列化与取键名工具 | node-conversion.ts |
getPreviewOptions(opts?) | 合并用户选项与默认预览配置 | node-conversion.ts |
nodeToValue/jsonPathToValue | 基于 hash 生成稳定节点 ID | node-conversion.ts |
最小使用方式只有一行:
import { jsonToTree } from "@zag-js/json-tree-utils" const tree = jsonToTree({ name: "John Doe", age: 30, tags: ["a", "b"] })三、jsonToTree:递归转换与三项安全机制
jsonToTree的定义位于 json-to-tree.ts,其签名与选项如下:
export interface JsonToTreeOptions { visited?: WeakSet<WeakKey> | undefined // 用于检测循环引用 keyPath?: (string | number)[] | undefined // 当前节点的路径,默认 ["$"] options?: JsonNodePreviewOptions | undefined depth?: number | undefined } export const jsonToTree = (data: unknown, props: JsonToTreeOptions = {}): JsonNode => { ... }实现上有三个值得注意的工程细节:
1. 深度上限(MAX_DEPTH = 20)。递归每深入一层depth + 1,一旦depth > MAX_DEPTH,节点值被替换为字符串"[Max Depth Reached]",节点类型标记为"string"。这防止了极度嵌套的数据拖垮渲染进程。
2. 循环引用检测。对任何对象值,先用WeakSet记录访问过的引用;如果再次遇到同一引用,返回类型为"circular"、值为"[Circular Reference]"的占位节点,从而避免无限递归。这正是使用WeakSet而非Set的原因——不持有对原始对象的强引用,不干扰 GC。
3. 类型驱动的分派。递归主体是查找「第一个 check 通过」的数据类型定义并调用其node()工厂:
const dataType = dataTypes.find((dataType) => dataType.check(data)) || PrimitiveType return dataType.node({ value: data, createNode: (nestedKeyPath, value) => jsonToTree(value, { visited, keyPath: [...keyPath, ...nestedKeyPath], options, depth: depth + 1 }), keyPath, options, })createNode闭包把「继续递归」的能力交给每个类型定义,同时自动拼接keyPath,保证每个节点都知道自己在整棵树中的位置。类型分派顺序决定了优先级,例如NullType、UndefinedType排在很前面,而ObjectType作为兜底排在ClassType之后(类实例会先被ClassType捕获以记录constructorName),StringType与PrimitiveType最后兜底——完整顺序见>export interface JsonNode<T = any> { value: T // 原始值 type: JsonNodeType // 节点类型 keyPath: JsonNodeKeyPath // 路径,如 ["$", "address", "city"] constructorName?: string | undefined // 类实例的构造器名(ClassType 使用) isNonEnumerable?: boolean // 是否为不可枚举属性 propertyDescriptor?: PropertyDescriptor | undefined children?: JsonNode[] // 子节点 }
4.2 类型枚举
JsonNodeType覆盖了 44 种类型,远超 JSON 字面量本身,可归为几组:
- JSON 原生:
object、array、boolean、number、string、null - 容器:
set、map、weakset、weakmap、iterable - 二进制:
arraybuffer、sharedarraybuffer、buffer、dataview及全部 10 种 TypedArray(int8array到biguint64array) - 内置对象:
regex、date、url、urlsearchparams、formdata、headers、error、promise、blob、file - 特殊值:
undefined、symbol、bigint、function、circular - 宿主环境:
element、document、window - 框架对象:
react-element
4.3 每种类型如何渲染?
每种类型由dataType()工厂定义的JsonDataTypeOptions描述,包含四个关键成员(见 types.ts):
export interface JsonDataTypeOptions<T = any> { type: JsonNodeType | ((value: T) => JsonNodeType) check: (value: any) => boolean // 判定该类型 node: JsonNodeCreatorFn<T> // 生成节点树(含子节点) description: string | ((node, opts) => string) // 无障碍描述用 previewText?: (node, opts) => string // 折叠预览文本 previewElement: (node, opts) => JsonNodeElement // 折叠预览元素 }以Map为例(data-type.ts),其node()会生成一个[[Entries]]数组子节点(isNonEnumerable: true)加一个size子节点;previewText则按maxPreviewItems截取前几项,输出"key" => value形式的预览。类似的展开策略贯穿Set、FormData、URLSearchParams、Headers、Iterable等类型。
五、预览选项:控制折叠与截断
5.1 默认值一览
// 来自 node-conversion.ts export const DEFAULT_PREVIEW_OPTIONS: JsonNodePreviewOptions = { maxPreviewItems: 3, // 折叠时最多预览的条目数 collapseStringsAfterLength: 30, // 字符串超过该长度后截断并追加 "…" groupArraysAfterLength: 100, // 数组元素超过该数量后分块分组 showNonenumerable: true, // 是否展示不可枚举属性(如 [[Entries]]、[[Function]]) }getPreviewOptions使用自实现的defu(见 shared.ts)做浅合并:传入的undefined字段不会覆盖默认值。你可以按需覆盖:
import { jsonToTree } from "@zag-js/json-tree-utils" const tree = jsonToTree(data, { options: { maxPreviewItems: 5, collapseStringsAfterLength: 50, groupArraysAfterLength: 200, showNonenumerable: false, }, })5.2 三个截断行为的实现细节
- 字符串截断:
StringType.previewText先把\n、\t、\r转义为\\n、\\t、\\r,再按collapseStringsAfterLength切片,超长时补…。 - 大数组分块:
ArrayType.node中,当实际有值的下标数量超过groupArraysAfterLength时,会按块大小把下标切成[0…99]、[100…199]这样的分组节点(每组仍为type: "array")。这正是 CHANGELOG 中1.18.5版本记录的改进——"Add support forgroupArraysAfterLengthto truncate large arrays into chunks (reducing the number of DOM nodes rendered)",即用更少的 DOM 节点渲染超大数组。 - 稀疏数组处理:
ArrayType通过Object.keys(value)只遍历实际有值的下标并做数值排序,避免稀疏数组(如arr[50] = "x")出现空洞崩溃;同时额外生成一个length子节点展示真实长度。
5.3 其他预览细节
- 函数节点:
FunctionType解析函数签名,超过 50 字符截断为...;根据AsyncFunction/GeneratorFunction等构造器名生成async ƒ、ƒ*前缀;展开时提供name、length、constructor及[[Function]](函数源码字符串)等子节点。 - 错误对象:
ErrorType展开name、message、stack;stack会被jsonNodeToElement特殊处理为多行error-stack元素(每行带\n与+连接符)。 - 二进制预览:
BufferType以十六进制展示前 8 个字节(如'61 62 63 …');Blob/File展示size、type、lastModified等元信息。 - DOM 元素:
ElementType折叠时渲染为<tag#id.class>形态,展开时提供attributes、dataset、style、tagName等 13 个属性子节点;ReactElementType则把<Component prop=...>形态直接呈现出来。
六、渲染管线:从 JsonNode 到 Hast 元素
jsonNodeToElement(node, opts?)(data-type.ts 末尾)把单个JsonNode转成 Hast 兼容结构——JsonNodeElement(type: "element",tagName限定为span/div/a)或JsonNodeText(type: "text"):
export interface JsonNodeElement { type: "element" tagName: "span" | "div" | "a" properties: { root?: boolean nodeType?: JsonNodeType kind?: JsonNodeSyntaxKind [key: string]: any } children: Array<JsonNodeElement | JsonNodeText> }转换时:
- 根节点会被标记
element.properties.root = true; - 每个元素都写入
data-kind="preview"与data-type(节点类型); - 内部语法元素(
constructor、brace、preview-text、function-type、colon、circular、operator、error-stack)通过kind区分,方便 CSS 精确着色。
之所以输出 Hast 兼容结构而非直接输出 DOM,是为了让各框架用自己最自然的方式渲染:React 用<Element>const KeyNode = (props: KeyNodeProps) => { const { node } = props const key = keyPathToKey(node.keyPath, { excludeRoot: true }) return ( <> <span>const ValueNode = (props: ValueNodeProps) => { const { node } = props if (node.type === "text") return <>{node.value}</> const Element = node.tagName return ( <Element>export default function JsonTree() { return ( <main style={{ fontFamily: "monospace" }}> <JsonTreeNode node={jsonToTree(jsonTreeData)} indexPath={[0]} /> </main> ) }
Vue 与 Svelte 版本几乎逐行对应:Vue 用defineComponent+h()完成相同逻辑(basic.vue),Svelte 用{#snippet}与<svelte:element>(+page.svelte)。这印证了该包「框架无关」的设计:业务逻辑全部收敛在@zag-js/json-tree-utils,各框架只负责薄薄一层映射渲染。
八、无障碍:为每个节点生成 aria-label
getAccessibleDescription(node, opts?)(accessibility.ts)为每个节点生成适合朗读的描述,示例组件将其放在aria-label上。其规则包括:
- 可展开节点:
"地址: Object, expandable with 4 properties"(key 与类型描述拼接,并统计子节点数;[[Entries]]特殊处理为"N properties"); - 原始值叶子:字符串加引号、数字直接输出,如
"name: \"John Doe\""; stack属性:取第二行(调用帧)作为描述;[[Function]]:描述为"function implementation";- 不可枚举属性:前缀
non-enumerable,若存在propertyDescriptor还追加read-only、non-configurable等特性说明; circular/null/undefined:分别输出"circular reference"、"null"、"undefined"。
九、其他实用工具与底层细节
- 路径与 ID:
keyPathToId用.连接路径生成字符串 key;nodeToValue与jsonPathToValue基于 shared.ts 的 djb2 风格hash(返回 36 进制字符串)生成紧凑 ID,适合作为虚拟列表的 key。 - 类型判定:
typeOf基于Object.prototype.toString.call精确识别[object URL]、[object Blob]、[object Headers]、[object HTMLDocument]、[object Window]等跨 realm 类型;isObj用于排除数组的纯对象判定。 - TypedArray:
TypedArrayType维护构造器名到类型名的映射表(Int8Array → int8array等),check通过value.constructor.name in typedArrayConstructors判定,折叠预览展示前 5 个元素,展开时[[Values]]子节点最多取前 100 个值以避免性能问题。
十、贡献与许可
按官方 README 说明,欢迎参与贡献,具体规范见仓库根目录的 CONTRIBUTING.md(提交 PR 前请阅读其中的开发流程与编码约定)。该包采用 MIT 许可证,全文见仓库根目录的 LICENSE,可在遵循许可条款的前提下自由使用与分发。
结语
@zag-js/json-tree-utils的价值在于把「类型识别 → 递归建模 → 预览截断 → 无障碍描述 → Hast 渲染」这条完整链路收敛为一个框架无关的工具包。无论你是想实现一个类 DevTools 的 JSON 检视器,还是在调试面板、表单回显或代码沙箱中展示复杂数据结构,都可以直接复用它的jsonToTree+jsonNodeToElement管线,并参考仓库中 React / Vue / Svelte / Solid / Preact 五份示例快速落地。对超大数组的性能问题(groupArraysAfterLength分块)和循环引用、稀疏数组等边界情况的处理,也让它足以应对真实业务中的"脏数据"。
- 前端
- UI组件
【免费下载链接】zag
Build your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines
相关推荐
react-json-tree 完全指南:在 React 中渲染任意 JSON 与可迭代数据的高性能 JSON 查看器
react json tree 完全指南:在 React 中渲染任意 JSON 与可迭代数据的高性能 JSON 查看器 react json tree 是从 r
开发工具前端Warp MCP 工具调用 JSON 树形渲染(APP-2527):从扁平 JSON 到可交互可折叠的通用 Tree 组件
Warp MCP 工具调用 JSON 树形渲染(APP 2527):从扁平 JSON 到可交互可折叠的通用 Tree 组件 导读 本指南围绕 Warp 开源仓库
桌面应用开发者工具人工智能AI 应用AI Agent代码智能体@json-render/svelte 实战指南:用 Svelte 5 与 Runes 将 JSON Spec 渲染为组件树
@json render/svelte 实战指南:用 Svelte 5 与 Runes 将 JSON Spec 渲染为组件树 导读 本文是 json rende
人工智能AI 应用前端MCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考