news 2026/10/14 18:29:46

@zag-js/json-tree-utils 完全指南:把任意 JavaScript 值渲染成可访问的 JSON 树组件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
@zag-js/json-tree-utils 完全指南:把任意 JavaScript 值渲染成可访问的 JSON 树组件
  • 前端
  • UI组件

【免费下载链接】zag

Build your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines

项目地址:https://gitcode.com/gh_mirrors/za/zag
点击查看免费下载

导读

@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/isRootKeyPathkeyPath 序列化与取键名工具node-conversion.ts
getPreviewOptions(opts?)合并用户选项与默认预览配置node-conversion.ts
nodeToValue/jsonPathToValue基于 hash 生成稳定节点 IDnode-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

项目地址:https://gitcode.com/gh_mirrors/za/zag
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 18:29:42

2026年AI聚合API服务实力横评:调用稳定性、自研链路与中小团队落地适配

这轮横评的方法论下了重本&#xff1a;七十二小时连续压测、十二类典型调用场景模拟、三十七位一线开发者访谈&#xff0c;对两款代表性大模型聚合服务做八大指标对比。评测聚焦三个中小企业最关心的问题——调用稳定性、自研链路技术与高频落地适配性。先把背景铺开&#xff0…

作者头像 李华
网站建设 2026/10/14 18:28:27

SeaTunnel 数据集成配置实战:从参数调优到脏数据容错

简介&#xff1a;本资源是面向数据工程师与开发人员的Apache SeaTunnel配置案例源码包&#xff0c;聚焦MySQL到HDFS、Hive到MySQL两类典型数据迁移场景&#xff0c;适合正在学习数据集成、需要快速跑通迁移流程的中级开发者参考。压缩包共3个文件&#xff0c;以inscode工程配置…

作者头像 李华
网站建设 2026/10/14 18:27:32

OpenSSL 3.0 Provider 架构解析与迁移实战

1. OpenSSL 3.0 的 Provider 架构到底改了什么OpenSSL 3.0 最核心的变化&#xff0c;不是加了几个新算法&#xff0c;而是把整个加密算法的加载方式推倒重来。如果你之前用过 1.1.1 系列&#xff0c;习惯了直接调EVP_EncryptInit_ex然后传个EVP_aes_256_gcm()就完事&#xff0c…

作者头像 李华
网站建设 2026/10/14 18:27:28

考研数学高数:一元函数微分学几何应用核心考点与解题技巧

1. 为什么“几何应用”是微分学里最容易拉开差距的章节如果你正在准备考研数学&#xff0c;或者正在带学生复习高数&#xff0c;大概率已经刷到过“张宇1000题”这个系列。而第五章“一元函数微分学的应用&#xff08;一&#xff09;——几何应用”&#xff0c;在整个高等数学的…

作者头像 李华
网站建设 2026/10/14 18:27:09

华为MetaERP Oracle EBS R12 GL​ 与 Oracle Fusion Cloud GL​ 的总账(General Ledger)按你要求的三层——业务对象 / 逻辑对象 /

Oracle EBS R12 GL​ 与 Oracle Fusion Cloud GL​ 的总账&#xff08;General Ledger&#xff09;按你要求的三层——业务对象 / 逻辑对象 / 物理实体——分别拆开&#xff0c;并给出详细清单 映射关系 架构差异分析。这是财务架构师做迁移、对接、报表取数、SLA/CLM 对账时…

作者头像 李华