GrJsonViewer

  • core
  • data
  • API · 11 props
  • ⌨ keys

Walks an unknown value as a tree: collapsible nodes, search by key or value, copy a node with its path.

import { GrJsonViewer } from '@feugene/granularity/components/GrJsonViewer'

Overview

When to take it

  • the answer of someone else’s service is picked apart field by field — a webhook, the answer of a model, the body of an error: a key has to be found, not the whole document read;
  • the structure is not known in advance — the shape arrives from the backend and changes, and there is nothing to lay it out into columns by;
  • a value has to be taken out pointwise — a node is copied together with its path, not the whole sheet;
  • there is a lot of data — collapsing, search and virtualisation hold an answer of thousands of nodes.

When to take something else

NeedTake
Read a text or a JSON in full and copy it at onceGrCodeBlock (the @feugene/granularity-code package)
A tree of your own data with selection and checkboxesGrTree
The data is laid out in columns and known in advanceGrDataTable
A “property → value” pairGrDescriptionList

Playground controls come from the API, not from hand

Stage

Loading…

0 changed· state is in the URL

Examples 3 each with a live demo and its code

  • Webhook payloads with readable values

    default-expand-depth opens as much as fits in one glance, and copyable hands a subtree over as JSON text. The viewer does not know what a value means — the app does: the leaf slot draws cents as euros, ISO dates as dates and URLs as links, while the copied value stays the raw one.

    Webhook deliveries · https://api.northwind.example/hooks/billing
    payload:{4}
    id:"evt_3Q8x"
    type:"invoice.paid"
    created:
    data:{1}
    invoice:{6}
    id:"INV-0418"
    amount_paid:€1,284.50
    currency:"eur"
  • A model request log within limits

    A base64 image and a 1,536-number embedding make a tree unreadable and slow. max-string-length cuts the image to a stub — copying still gives the whole value — max-array-items stops the embedding after twenty numbers, and virtual keeps only the rows near the viewport in the DOM.

    Request log · req_7f2cA 22 KB image and 1,536 numbers, shown as much as is useful
    log:{5}
    id:"req_7f2c"
    model:"vision-large-2026-08"
    latency_ms:2140
    request:{2}
    messages:[2]
    0:{2}
    role:"system"
    content:"You read receipts and return the merchant, the date and the total."
    1:{2}
    role:"user"
    content:[2]
    0:{2}
    type:"text"
    text:"Taxi receipt from the Lisbon trip."
    1:{2}
    type:"image"
    source:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42m"…
    temperature:0
    response:{3}
    output:{4}
    merchant:"Taxi Lisboa"
    date:"2026-10-16"
    total:24.6
    currency:"EUR"
    embedding:[1536]
    0:0
    1:0.205445
    2:0.374602
    3:0.477593
    4:0.496227
    5:0.427213
    6:0.282741
    7:0.088327
    8:-0.121687
    9:-0.310208
    10:-0.443936
    11:-0.499253
    12:-0.466387
    13:-0.351143
    14:-0.173877
    15:0.034101
    16:0.236056
    17:0.396316
    18:0.486575
    19:0.490891
    …:1516more
    usage:{2}
    input_tokens:1288
    output_tokens:31

API generated from the package · cannot drift

Props

PropTypedefaultDescription
size"xs" | "sm" | "md" | "lg" | undefinedundefinedThe type size of the tree and the height of the search field and the panel buttons. Unset — from `GrConfigProvider`, otherwise `md`.
ariaLabelstring | undefinedundefinedThe name of the area for a screen reader. A nameless tree is announced simply as "tree".
virtualboolean | undefinedfalseKeep in the DOM only the window around the viewport. It requires `maxHeight`.
maxHeightstring | number | undefinedundefinedThe height of the viewing area with its own scroll — even without `virtual`. A number is pixels, a string is taken as is.
rootLabelstring | undefinedundefinedThe label of the root. `$` by default — that is how a JSONPath and dev tools address a path.
defaultExpandDepthnumber | undefinedundefinedHow many levels are expanded to begin with.
maxStringLengthnumber | undefinedundefinedThe length of a string value beyond which the display is truncated. Not cosmetics: a request to a model with a picture in base64 is one leaf of hundreds of thousands of characters, and neither collapsing nor virtualisation by row takes it. The copying meanwhile gives away the value in full.
maxArrayItemsnumber | undefinedundefinedHow many elements of an array to parse before the "N more" placeholder.
searchableboolean | undefinedundefinedA panel above the tree: a search field and "Expand all / Collapse all". **On** by default (the default is changed through `GrConfigProvider`). `false` removes the panel entirely: search stays with `filter()`, expanding with `expandAll()`/`collapseAll()` from `defineExpose`.
copyableboolean | undefinedundefinedA button that copies the node, in the row.
valuerequiredunknown—The value being shown. `unknown`, because it comes from a database or from someone else’s service.

Slots

SlotTypeDescription
leaf{ node: GrJsonNode; }The value of a leaf: a link, a date, a sum of money — the styling is known by the application.

Events

EventSignatureDescription
copy(event: "copy", payload: { path: string; value: unknown; }): void—

Methods

MethodsSignatureDescription
filter(value: string) => voidFilter the tree by key or value — with the same query as in the search field. An empty string removes the filter.
expandAll() => voidExpand all branches.
collapseAll() => voidCollapse all branches, including the root.

Types

Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.

Import
import type { GrJsonViewerConfigurableProps, GrJsonNodeKind, GrJsonNode } from '@feugene/granularity/components/GrJsonViewer'

Filedist/types/components/GrJsonViewer/config.d.ts

grJsonViewerConfigconst
const grJsonViewerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrJsonViewer">;

Filedist/types/components/GrJsonViewer/defaults.d.ts

GrJsonViewerConfigurablePropsinterface
interface GrJsonViewerConfigurableProps {
    size: GrComponentSize;
    defaultExpandDepth: number;
    maxStringLength: number;
    maxArrayItems: number;
    searchable: boolean;
    copyable: boolean;
}

Filedist/types/components/GrJsonViewer/jsonToNodes.d.ts

GrJsonNodeKindtype
type GrJsonNodeKind = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null'
GrJsonNodetype
type GrJsonNode = {
    path: string;
    label: string;
    kind: GrJsonNodeKind;
    preview: string;
    value: unknown;
    truncated: boolean;
    isLeaf: boolean;
    children?: GrJsonNode[];
};

Filedist/types/components/GrJsonViewer/GrJsonViewer.vue.d.ts

GrJsonViewerPropsinterface
interface GrJsonViewerProps {
    value: unknown;
    rootLabel?: string;
    defaultExpandDepth?: number;
    maxStringLength?: number;
    maxArrayItems?: number;
    searchable?: boolean;
    copyable?: boolean;
    virtual?: boolean;
    maxHeight?: string | number;
    ariaLabel?: string;
    size?: GrComponentSize;
}
GrJsonViewerEmitsinterface
interface GrJsonViewerEmits {
    (e: 'copy', payload: {
        path: string;
        value: unknown;
    }): void;
}

Filedist/types/components/GrJsonViewer/safelist.d.ts

grJsonViewerSafelistconst
const grJsonViewerSafelist: string[];

Filedist/types/components/GrJsonViewer/index.d.ts

GrJsonViewerInstancetype
type GrJsonViewerInstance = ComponentExposed<typeof GrJsonViewerComponent>;

Accessibility

APG pattern

tree (через GrTree)

Usage from the library prose

The display is truncated, the copying is not

A long string value is truncated in the row (maxStringLength), and a long array is cut off with a “N more” placeholder (maxArrayItems). That is not cosmetics: a request to a model with a picture in base64 is one leaf of hundreds of thousands of characters, and neither collapsing by node nor virtualisation by row takes it, because there is one node there and one row.

The full value of the node goes into the clipboard together with the path: the truncation belongs to the display, and pasting it back is not possible.

A repeated reference is not a cycle

The [Circular] marker goes only to a real ancestor along the chain, and an object honestly put into the data twice is drawn twice.

This is the case where walking a tree can strictly more than serialisation: the replacer of JSON.stringify does not receive the stack of ancestors and is forced to mark any repeated reference. A walk has the stack, so GrCodeBlock and GrJsonViewer give different — and each in its own way correct — results on the same data.

The address of a node is readable

The key of a node is its path ($.items[3].name) rather than an ordinal number: it leaves in the copy event, and the expansion is set by it as well. A key with a dot or a space is escaped ($["a.b"]), otherwise the address would stop being an address.

The expansion is set by depth, not by a list

defaultExpandDepth expands the first N levels; expandAll() and collapseAll() from defineExpose switch the whole tree. Both buttons reset the user’s manual expansion — that is what is expected of “expand everything”.

The search runs over the key and the value

In someone else’s answer people look now for one, now for the other, so the predicate looks both at the name of the key and at the displayed value. The matching nodes are highlighted by the tree as a whole, and the path to them is expanded.

The panel above the tree — the search field and “Expand all / Collapse all” — is on by default. It can be removed (searchable: false) and filter(query), expandAll() and collapseAll() called from the outside — when a search bar already exists on the page and a second one would be redundant.

maxHeight bounds the viewing area with its own scroll even without virtual.

Limits

It does not edit and does not diff: the viewer shows what has arrived. There is no highlighting of the matched substring inside a string — the tree marks the node as a whole. The virtualisation switches on only together with maxHeight (GrTree): without a height there is nothing to compute the window from.