| import { Tree, Typography } from 'antd' |
| import type { DataNode } from 'antd/es/tree' |
| import { useMemo } from 'react' |
|
|
| export type JsonViewerProps = { |
| value: unknown |
| height?: number |
| defaultExpandAll?: boolean |
| } |
|
|
| function isRecord(value: unknown): value is Record<string, unknown> { |
| return !!value && typeof value === 'object' && !Array.isArray(value) |
| } |
|
|
| function preview(value: unknown) { |
| if (value === null) return 'null' |
| if (value === undefined) return 'undefined' |
| if (typeof value === 'string') return JSON.stringify(value) |
| if (typeof value === 'number' || typeof value === 'boolean') return String(value) |
| if (Array.isArray(value)) return `[${value.length}]` |
| if (isRecord(value)) return `{${Object.keys(value).length}}` |
| return String(value) |
| } |
|
|
| function toTreeData(value: unknown, path: string): DataNode[] { |
| if (Array.isArray(value)) { |
| return value.map((item, idx) => { |
| const nextPath = `${path}[${idx}]` |
| const hasChildren = Array.isArray(item) || isRecord(item) |
| return { |
| key: nextPath, |
| title: ( |
| <span> |
| <Typography.Text code>{idx}</Typography.Text> |
| <Typography.Text type="secondary" style={{ marginLeft: 8 }}> |
| {preview(item)} |
| </Typography.Text> |
| </span> |
| ), |
| children: hasChildren ? toTreeData(item, nextPath) : undefined, |
| } |
| }) |
| } |
|
|
| if (isRecord(value)) { |
| return Object.entries(value).map(([k, v]) => { |
| const nextPath = path ? `${path}.${k}` : k |
| const hasChildren = Array.isArray(v) || isRecord(v) |
| return { |
| key: nextPath, |
| title: ( |
| <span> |
| <Typography.Text code>{k}</Typography.Text> |
| <Typography.Text type="secondary" style={{ marginLeft: 8 }}> |
| {preview(v)} |
| </Typography.Text> |
| </span> |
| ), |
| children: hasChildren ? toTreeData(v, nextPath) : undefined, |
| } |
| }) |
| } |
|
|
| return [ |
| { |
| key: path || 'value', |
| title: <Typography.Text type="secondary">{preview(value)}</Typography.Text>, |
| }, |
| ] |
| } |
|
|
| export default function JsonViewer({ |
| value, |
| height = 360, |
| defaultExpandAll = true, |
| }: JsonViewerProps) { |
| const treeData = useMemo<DataNode[]>(() => toTreeData(value, ''), [value]) |
|
|
| if (value === null || value === undefined) { |
| return <Typography.Text type="secondary">空</Typography.Text> |
| } |
|
|
| return ( |
| <Tree |
| blockNode |
| showLine |
| height={height} |
| defaultExpandAll={defaultExpandAll} |
| treeData={treeData} |
| /> |
| ) |
| } |
|
|
|
|