跳转到内容

JsonViewer JSON 视图

把一份 JSON 展开为可折叠的树:键名、值与值类型各自成块,对象与数组可以逐层收起。

用法

一份 JSON 展开为可折叠的树:键名与值各自成块,六种类型各自上色,默认只展开根行

name"曦寒视图"
version"1.0.0-alpha.2"
stars128
activetrue
homepagenull

组件结构

加粗的是必需部件。

data-scope="json-viewer"root · tree · item · item-key · item-value · branch · branch-control · branch-trigger · branch-indicator · branch-text · branch-content · preview · text · empty

示例

默认展开层数

defaultExpandedDepth 决定初次展开到第几层:1 只展开根行,3 连孙层一起铺开

server
host"127.0.0.1"
port5173
tls
enabledfalse
certnull
build
target"es2022"
minifytrue

受控展开

传入 expandedValue 后由宿主决定,组件只发 expanded-value-change 不落内部值,写回后才变化

展开了 1 处

大数据

maxItems 把超长数组折为一行占位,maxStringLength 截断过长的字符串,一份大 JSON 不会拖慢页面

total240
cursor"eyJvZmZzZXQiOjAsImxpbWl0…"
items
0"第 1 条"
1"第 2 条"
2"第 3 条"
3"第 4 条"
4"第 5 条"
… 235 more

键排序

sortKeys 使对象键按字典序排列,数组顺序不变;接口返回的字段顺序不稳定时使用它

action"deploy"
meta
at"2026-08-20"
by"ci"
retries2
steps
0"build"
1"test"
2"publish"
zone"cn-east-1"

循环引用

值出现在自己的祖先链上即停止并标为 [Circular],不会无限递归;共享引用不算环,照常展开

name"root"
left
id1
right
id1
parent[Circular]

尺寸

size 三档只改变字号与层级缩进,行的结构与配色都不变

id7
label"曦寒"
nested
oktrue
id7
label"曦寒"
nested
oktrue
id7
label"曦寒"
nested
oktrue

原文视图

view="text" 直接输出缩进后的 JSON 原文:整块可框选可复制,且不受 maxStringLength / maxItems 折减

{
  "orderNo": "SO-2026-0825-0417",
  "amount": 12.5,
  "items": [
    {
      "sku": "A-1001",
      "qty": 2
    },
    {
      "sku": "B-2003",
      "qty": 1
    }
  ],
  "remark": "跨境订单,需人工复核收件地址与税号"
}

空态与形态

一行都无法展开时显示空态格;variant="ghost" 去掉外框与底色

这份接口还没有返回内容
No data

设计指引

何时使用

  • 调试面板、接口返回体、配置文件的只读呈现。
  • 日志详情中的大段结构化字段,直接铺开会淹没正文。

何时不用

  • 数据可编辑时,本组件只读;修改值需要自行接入表单文本字段
  • 数据只是一段带语法高亮的源码时,使用代码视图
  • 层级数据不是 JSON、键名与类型没有语义时,使用;它处理通用层级数据与选中,本组件处理 JSON 的类型语义(键名、值形态、按类型着色、循环引用)。
  • 只有几个字段需要平铺展示时,使用描述列表

特性

  • 行结构由 value 展开,作者不写任何行标记:Vue 与自定义元素两侧铺出同一棵 DOM,根容器内原有内容由组件接管。
  • 自定义渲染器可调用 groupJsonViewerNodesByParent(nodes) 把可见行按父路径分组;返回值保留父路径首次出现顺序、组内输入顺序与节点身份。
  • 未提供 value 时是空视图,不展开任何行;对象内确实存在值为 undefined 的成员时,该行照常展开。
  • 展开集合可受控(expandedValue / defaultExpandedValue),非受控时按 defaultExpandedDepth 计算:数据晚于组件挂载到达(自定义元素常先升级、再由脚本写 .value)也能计算,第一次展开或收起之后固定,不再跟随数据。
  • maxStringLength 截断长字符串,maxItems 折叠超长数组,sortKeys 让对象键按字典序排列。
  • 循环引用展开到即停,标记为 [Circular],不会无限递归。
  • 每一行带 data-value-type,六种值形态各自着色。
  • 尺寸轴与其他组件同源;variant 决定外框形态,默认 outlinesubtle 换成淡底无描边,ghost 去掉外框与底色只保留内容。
  • 没有任何行可展开时由 empty 部件说明,文案使用 translations.empty,作者也可以自行写入内容。
  • 只支持 JSON 能表达的形状,传入活对象时呈现有损:Date / Map / Set 一律按自有可枚举键展开,因此显示为 {}undefined 归入 null 一档、显示为 undefinedbigint 归入 number;函数与 symbol 归入 string,按各自的字符串形式呈现。需要如实展示这些值时先转换为 JSON 能表达的形状。
  • 自定义元素侧:value 属性接受一段 JSON 文本(无法解析时按字符串值展示),对象与数组直接赋 property(el.value = { … });expandedValue / defaultExpandedValue / translations 没有对应属性,只能通过 property 设置,写成 expanded-value='["$"]' 不会生效。

组合

最佳实践

  • 大数据必须提供 maxItemsmaxStringLength:一次展开几万行会让页面停滞。
  • 默认展开层数不宜过大:defaultExpandedDepth 超过 2 会把整份数据铺满屏幕。
  • 值的类型只靠颜色区分不够,字符串的引号、null 的字面量都要保留。
  • 一行被收起时,其内部持有焦点的行会离开 DOM,焦点回到 <body>。应在收起前把焦点交回分支行本身,键盘用户才不会每收一层就丢失位置。

反模式

  • 将它用作日志流:日志是时间序的条目,使用日志
  • 把几 MB 的响应体原样传入,让用户自行查找。

API 参考

产物

自定义元素<xh-json-viewer>
Vue 组件XhJsonViewerRoot
组合式函数useJsonViewer
状态机jsonViewerMachine
皮肤@xihan-ui/styles/json-viewer.css

Props

属性类型必填说明
valueunknown要展示的值,任意形状。未提供时为空视图(不展开任何行)。
viewJsonViewerView展示形态,默认 tree。 text 档直接输出 JSON 原文:整块可框选可复制,且不受 maxStringLength / maxItems 折减: 目的是与后端下发的内容完全一致。展开集合与键盘导航在该档上不生效。
variantControlVariant外框形态:outline 带描边与底色(默认),subtle 淡底无描边,ghost 去掉描边与底色只保留内容。
expandedValuestring[]展开集合(元素是行路径)。提供即受控:cell 直读 prop,写入只发 onExpandedValueChange 不落内部值。
defaultExpandedValuestring[]非受控初值;未提供时按 defaultExpandedDepth 计算。
defaultExpandedDepthnumber初始展开到第几层(层级号不超过它的分支全部展开),默认 1,即只展开根行。
maxStringLengthnumber字符串值超过该字符数即截断并补省略号;未提供时不截断。
maxItemsnumber同一层最多展开该数量的成员,其余收为一行占位;未提供时全部展开。
sortKeysboolean对象键按字典序排列;数组顺序不受影响。
loopboolean上下键到达首尾是否回绕,默认 false。
dirDirection文字方向,只对调左右方向键的展开 / 收起语义;未提供时从 DOM 读取。
sizeSize尺寸:sm / md / lg。
translationsPartial<JsonViewerTranslations>
onExpandedValueChange(details: JsonViewerExpandedValueChangeDetails) => void

事件

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
expanded-value-changeJsonViewerExpandedValueChangeDetails展开集合变化;detail 为 { value: string[] }

插槽

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhJsonViewerRootempty

状态

公开状态写入 data-state

部件取值
branch'open' | 'closed'
branch-control'open' | 'closed'
branch-trigger'open' | 'closed'
branch-indicator'open' | 'closed'
branch-text'open' | 'closed'
branch-content'open' | 'closed'
preview'open' | 'closed'

以下名称仅用于内部状态机。

状态idle

事件EXPANDED.SET · BRANCH.EXPAND · BRANCH.COLLAPSE · BRANCH.TOGGLE · NODE.FOCUS · VIEWER.BLUR · PRESS.START · PRESS.END

connect API

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
visibleNodesreadonly JsonViewerNode[]当前可见行序列(收起分支的子行不在其中)。 方向键、Home/End 都在它上面移动,适配器也按它铺设 DOM。
expandedValuestring[]
focusedValuestring | nullroving tabindex 的锚点行:焦点在树内时即当前行,焦点离开后仍保留(Tab 回来时落回它); 它已随分支收起而不再可见时为 null。
isFocusWithinboolean焦点当前是否在树内。行的高亮标记随它变化,锚点不随之变化。
isExpanded(value: string) => boolean
previewText(node: JsonViewerNode) => string分支收起摘要的显示文字(如 {…} 3),只用于视觉;叶子行返回空串。
valueText(node: JsonViewerNode) => string值的显示文字;截断占位行返回其余 N 项的文案。
setExpandedValue(next: string[]) => void
expand(value: string) => void
collapse(value: string) => void
toggle(value: string) => void
viewJsonViewerView当前生效的展示形态。
isEmptyboolean无法展开任何一行:value 未提供或为 undefined。空态部件随它显隐。
emptyTextstring空态的兜底文案,作者未向空态部件写入内容时铺设它。
textstring缩进后的 JSON 原文;键序与环路记号与树档一致。text 档之外也可获取,便于作者实现复制原文。
getRootProps() => T['element']
getTreeProps() => T['element']
getTextProps() => T['element']
getItemProps(props: JsonViewerNodeProps) => T['element']
getItemKeyProps(props: JsonViewerNodeProps) => T['element']
getItemValueProps(props: JsonViewerNodeProps) => T['element']
getBranchProps(props: JsonViewerNodeProps) => T['element']
getBranchControlProps(props: JsonViewerNodeProps) => T['element']
getBranchTriggerProps(props: JsonViewerNodeProps) => T['element']
getBranchIndicatorProps(props: JsonViewerNodeProps) => T['element']
getBranchTextProps(props: JsonViewerNodeProps) => T['element']
getBranchContentProps(props: JsonViewerNodeProps) => T['element']
getPreviewProps(props: JsonViewerNodeProps) => T['element']
getEmptyProps() => T['element']

无障碍

键盘

规格出处:W3C APG

按键生效条件行为
Tab / Shift+Tabfocus outside the tree整棵树只占一个 Tab 位:第一次进来落首行,之后回到上次停留的那一行
ArrowDownfocus in tree焦点移到下一个可见行(loop 默认关,末行不回绕)
ArrowUpfocus in tree焦点移到上一个可见行(loop 默认关,首行不回绕)
Homefocus in tree焦点移到首个可见行
Endfocus in tree焦点移到末个可见行(展开着的子层也算行)
ArrowRightfocus on branch(dir=rtl 时改由 ArrowLeft 承担)收起的对象/数组就地展开;已展开则把焦点移到首个子行;标量行什么都不做且不吞键
ArrowLeftfocus in tree(dir=rtl 时改由 ArrowRight 承担)展开的对象/数组就地收起;收起的分支与标量行则把焦点移到父行;根行什么都不做
Enter / Spacefocus on branch切换该分支的展开态;焦点在标量行上时不吞这两个键
Enter / Spaceheld on branch按住期间该分支行(branch-control)投影 data-pressed,与指针 :active 同一副按压面(行只换面不缩放);抬起或失焦撤下。展开态的切换照旧由这一次按键承担
*focus in tree展开与焦点行同一父级的全部分支(已展开的不动);同级没有可展开的分支时不吞这个键

ARIA

以下属性由 connect 生成。

部件属性
treearia-labellabel.tree
treerole'tree'
itemaria-levelnode?.level
itemaria-posinsetnode?.posInSet
itemaria-setsizenode?.setSize
itemrole'treeitem'
brancharia-expanded'true' | 'false'
brancharia-labelbranchLabel(node) | undefined
brancharia-levelnode?.level
brancharia-posinsetnode?.posInSet
brancharia-setsizenode?.setSize
branchrole'treeitem'
branch-triggeraria-hidden'true'
branch-indicatoraria-hidden'true'
branch-contentrole'group'
previewaria-hidden'true'
textaria-labellabel.text
textrole'region'
  • 树是 role=tree,每一行是 role=treeitem,层级三项(aria-level / aria-posinset / aria-setsize)取自展开结果。
  • 整棵树只占一个 Tab 位:首次进入落在首行,之后 Tab 离开再返回时落回上次停留的行;组内靠上下键移动。
  • 展开箭头对读屏隐藏,它重复的是分支自身已报出的 aria-expanded 与左右方向键。
  • 分支的名称显式提供(aria-label):它包裹整棵子层,从内容计算名称会把所有子孙的文字一并读出。
  • 收起摘要({…} 3)是排版记号,对读屏隐藏;其中的成员数并入分支的可访问名称(默认读为 tags, 3 items,整句可用 translations.collapsedBranchLabel 替换)。

样式参考

皮肤

@xihan-ui/styles/json-viewer.css 使用 [data-scope="json-viewer"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides

forced-colors: active 下另有一套规则:颜色交给系统,边框与状态标记改用系统色关键字。

数据属性

connect 生成;条件不成立时不输出无值属性。

部件属性
rootdata-sizeprops.size
rootdata-variantprops.variant
rootdata-viewprops.view
itemdata-circular''(条件成立时才出现)
itemdata-highlighted''(条件成立时才出现)
itemdata-truncated''(条件成立时才出现)
itemdata-value-typenode?.type
item-keydata-circular''(条件成立时才出现)
item-keydata-highlighted''(条件成立时才出现)
item-keydata-truncated''(条件成立时才出现)
item-keydata-value-typenode?.type
item-valuedata-circular''(条件成立时才出现)
item-valuedata-highlighted''(条件成立时才出现)
item-valuedata-truncated''(条件成立时才出现)
item-valuedata-value-typenode?.type
branchdata-circular''(条件成立时才出现)
branchdata-highlighted''(条件成立时才出现)
branchdata-state'open' | 'closed'
branchdata-truncated''(条件成立时才出现)
branchdata-value-typenode?.type
branch-controldata-circular''(条件成立时才出现)
branch-controldata-highlighted''(条件成立时才出现)
branch-controldata-pressed''(条件成立时才出现)
branch-controldata-state'open' | 'closed'
branch-controldata-truncated''(条件成立时才出现)
branch-controldata-value-typenode?.type
branch-triggerdata-circular''(条件成立时才出现)
branch-triggerdata-highlighted''(条件成立时才出现)
branch-triggerdata-state'open' | 'closed'
branch-triggerdata-truncated''(条件成立时才出现)
branch-triggerdata-value-typenode?.type
branch-indicatordata-circular''(条件成立时才出现)
branch-indicatordata-highlighted''(条件成立时才出现)
branch-indicatordata-state'open' | 'closed'
branch-indicatordata-truncated''(条件成立时才出现)
branch-indicatordata-value-typenode?.type
branch-textdata-circular''(条件成立时才出现)
branch-textdata-highlighted''(条件成立时才出现)
branch-textdata-state'open' | 'closed'
branch-textdata-truncated''(条件成立时才出现)
branch-textdata-value-typenode?.type
branch-contentdata-circular''(条件成立时才出现)
branch-contentdata-highlighted''(条件成立时才出现)
branch-contentdata-state'open' | 'closed'
branch-contentdata-truncated''(条件成立时才出现)
branch-contentdata-value-typenode?.type
previewdata-circular''(条件成立时才出现)
previewdata-highlighted''(条件成立时才出现)
previewdata-state'open' | 'closed'
previewdata-truncated''(条件成立时才出现)
previewdata-value-typenode?.type

CSS 变量

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-json-viewer-bgempty
root
text
tree
backgrounddefault
is([data-scope='json-viewer'][data-part='tree'], [data-scope='json-viewer'][data-part='text'], [data-scope='json-viewer'][data-part='empty'])
variant=subtle
--xh-bg-subtle
--xh-bg-surface
json-viewer 的 empty、root、text、tree 部件 background 覆盖槽。
--xh-json-viewer-boolean-fgitem-valuecolorvalue-type=boolean--xh-syntax-keywordjson-viewer 的 item-value 部件 color 覆盖槽。
--xh-json-viewer-borderempty
text
tree
borderdefault--xh-border-defaultjson-viewer 的 empty、text、tree 部件 border 覆盖槽。
--xh-json-viewer-empty-fgemptycolordefault--xh-fg-mutedjson-viewer 的 empty 部件 color 覆盖槽。
--xh-json-viewer-empty-gapemptygapdefault--xh-space-2json-viewer 的 empty 部件 gap 覆盖槽。
--xh-json-viewer-empty-pxemptypadding-inlinedefault--xh-space-4json-viewer 的 empty 部件 padding-inline 覆盖槽。
--xh-json-viewer-empty-pyemptypadding-blockdefault--xh-space-6json-viewer 的 empty 部件 padding-block 覆盖槽。
--xh-json-viewer-fgrootcolordefault--xh-fg-defaultjson-viewer 的 root 部件 color 覆盖槽。
--xh-json-viewer-fontroot
text
font-familydefault--xh-font-family-monojson-viewer 的 root、text 部件 font-family 覆盖槽。
--xh-json-viewer-font-sizerootfont-sizedefault--xh-_json-viewer-font-sizejson-viewer 的 root 部件 font-size 覆盖槽。
--xh-json-viewer-icon-sizeroot--xh-icon-sizedefault
size=lg
size=sm
--xh-glyph-size-lg
--xh-glyph-size-md
--xh-glyph-size-sm
json-viewer 的 root 部件 --xh-icon-size 覆盖槽。
--xh-json-viewer-indentbranch-contentpadding-inline-startdefault--xh-_json-viewer-indentjson-viewer 的 branch-content 部件 padding-inline-start 覆盖槽。
--xh-json-viewer-indicator-fgbranch-triggercolordefault--xh-fg-subtlejson-viewer 的 branch-trigger 部件 color 覆盖槽。
--xh-json-viewer-indicator-sizebranch-trigger--xh-icon-size
inline-size
default--xh-control-indicator-sizejson-viewer 的 branch-trigger 部件 --xh-icon-size、inline-size 覆盖槽。
--xh-json-viewer-key-fgbranch-text
item-key
colordefault--xh-fg-brand-strongjson-viewer 的 branch-text、item-key 部件 color 覆盖槽。
--xh-json-viewer-key-font-weightbranch-text
item-key
font-weightdefault--xh-font-weight-mediumjson-viewer 的 branch-text、item-key 部件 font-weight 覆盖槽。
--xh-json-viewer-max-htext
tree
max-block-sizedefault--xh-viewport-max-hjson-viewer 的 text、tree 部件 max-block-size 覆盖槽。
--xh-json-viewer-null-fgitem-valuecolorvalue-type=null--xh-fg-subtlejson-viewer 的 item-value 部件 color 覆盖槽。
--xh-json-viewer-number-fgitem-valuecolorvalue-type=number--xh-syntax-numberjson-viewer 的 item-value 部件 color 覆盖槽。
--xh-json-viewer-preview-fgpreviewcolordefault--xh-fg-mutedjson-viewer 的 preview 部件 color 覆盖槽。
--xh-json-viewer-preview-font-sizepreviewfont-sizedefault--xh-text-caption-sizejson-viewer 的 preview 部件 font-size 覆盖槽。
--xh-json-viewer-punctuation-fgbranch-text
item-key
item-value
colordefault
value-type=array
value-type=object
--xh-fg-subtlejson-viewer 的 branch-text、item-key、item-value 部件 color 覆盖槽。
--xh-json-viewer-pxtext
tree
padding-inlinedefault--xh-space-2json-viewer 的 text、tree 部件 padding-inline 覆盖槽。
--xh-json-viewer-pytext
tree
padding-blockdefault--xh-space-2json-viewer 的 text、tree 部件 padding-block 覆盖槽。
--xh-json-viewer-radiusempty
text
tree
border-radiusdefault--xh-shape-surfacejson-viewer 的 empty、text、tree 部件 border-radius 覆盖槽。
--xh-json-viewer-row-bg-activebranch-controlbackgrounddisabled
is(:active, [data-pressed])
not([data-disabled])
pressed
--xh-bg-subtle-hoverjson-viewer 的 branch-control 部件 background 覆盖槽。
--xh-json-viewer-row-bg-hoverbranch-control
item
backgroundhighlighted
is(:hover, [data-highlighted])
--xh-bg-subtlejson-viewer 的 branch-control、item 部件 background 覆盖槽。
--xh-json-viewer-row-gapbranch-control
item
gapdefault--xh-space-1json-viewer 的 branch-control、item 部件 gap 覆盖槽。
--xh-json-viewer-row-pxbranch-control
item
padding-inlinedefault--xh-space-1json-viewer 的 branch-control、item 部件 padding-inline 覆盖槽。
--xh-json-viewer-row-pybranch-control
item
padding-blockdefault--xh-_json-viewer-row-pyjson-viewer 的 branch-control、item 部件 padding-block 覆盖槽。
--xh-json-viewer-row-radiusbranch-control
item
border-radiusdefault--xh-shape-insetjson-viewer 的 branch-control、item 部件 border-radius 覆盖槽。
--xh-json-viewer-shadowempty
text
tree
box-shadowdefaultnonejson-viewer 的 empty、text、tree 部件 box-shadow 覆盖槽。
--xh-json-viewer-string-fgitem-valuecolorvalue-type=string--xh-syntax-stringjson-viewer 的 item-value 部件 color 覆盖槽。
--xh-json-viewer-text-fgtextcolordefault--xh-fg-defaultjson-viewer 的 text 部件 color 覆盖槽。

动效

background · rotatetransition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

RTL

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像;另有按 dir 分支的规则。

  • 左右方向键的展开 / 收起语义跟随书写方向:未传 dir 时从 DOM 读取,整页 dir="rtl" 也能识别。

Released under The MIT License