GrJsonViewer

  • ядро
  • Данные
  • API · 11 props
  • ⌨ клавиши

Берут, когда ответ чужого сервиса разбирают по полям.

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

Обзор

Когда брать

  • ответ чужого сервиса разбирают по полям — вебхук, ответ модели, тело ошибки: нужно найти ключ, а не прочитать документ целиком;
  • структура заранее неизвестна — форма приходит от бэкенда и меняется, и раскладывать её по колонкам не по чему;
  • значение нужно достать точечно — копируется узел вместе с путём, а не вся простыня;
  • данных много — свёртка, поиск и виртуализация держат ответ на тысячи узлов.

Когда взять другое

НужноБерите
Прочитать текст или JSON целиком и скопировать разомGrCodeBlock (пакет @feugene/granularity-code)
Дерево своих данных с выбором и чекбоксамиGrTree
Данные разложены по колонкам и известны заранееGrDataTable
Пара «характеристика → значение»GrDescriptionList

Playground контролы выводятся из API, а не пишутся руками

Сцена

Загружается…

0 изменено· состояние — в адресе

Примеры 3 у каждого — живое демо и его код

  • Тела вебхуков с читаемыми значениями

    default-expand-depth раскрывает ровно столько, сколько видно с одного взгляда, а copyable отдаёт поддерево как JSON-текст. Что значит значение, просмотрщик не знает — знает приложение: слот leaf рисует центы евро, ISO-даты датами, а URL ссылками, при этом копируется сырое значение.

    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"
  • Журнал запроса к модели в пределах

    Картинка в base64 и вектор из 1 536 чисел делают дерево нечитаемым и медленным. max-string-length обрезает картинку до заглушки — копирование всё равно отдаёт целое, — max-array-items останавливает вектор после двадцати чисел, а virtual держит в 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
    …:1516ещё
    usage:{2}
    input_tokens:1288
    output_tokens:31

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
size"xs" | "sm" | "md" | "lg" | undefinedundefinedКегль дерева и высота поиска и кнопок панели. Не задан — из `GrConfigProvider`, иначе `md`.
ariaLabelstring | undefinedundefinedИмя области для скринридера. Безымянное дерево объявляется просто «дерево».
virtualboolean | undefinedfalseОставлять в DOM только окно вокруг вьюпорта. Требует `maxHeight`.
maxHeightstring | number | undefinedundefinedВысота области просмотра со своим скроллом — и без `virtual`. Число — пиксели, строка — как есть.
rootLabelstring | undefinedundefinedПодпись корня. По умолчанию `$` — так адресуют путь JSONPath и dev-tools.
defaultExpandDepthnumber | undefinedundefinedСколько уровней раскрыто изначально.
maxStringLengthnumber | undefinedundefinedДлина строкового значения, после которой показ обрезается. Не косметика: запрос к модели с картинкой в base64 — это один лист на сотни тысяч символов, и ни свёртка, ни виртуализация по строкам его не берут. Копирование при этом отдаёт значение целиком.
maxArrayItemsnumber | undefinedundefinedСколько элементов массива разбирать до заглушки «ещё N».
searchableboolean | undefinedundefinedПанель над деревом: поле поиска и «Развернуть всё / Свернуть всё». По умолчанию **включена** (умолчание меняется через `GrConfigProvider`). `false` убирает панель целиком: поиск остаётся на `filter()`, раскрытие — на `expandAll()`/`collapseAll()` из `defineExpose`.
copyableboolean | undefinedundefinedКнопка копирования узла в строке.
valueобязательныйunknown—Показываемое значение. `unknown`, потому что приходит из БД или чужого сервиса.

Слоты

SlotTypeОписание
leaf{ node: GrJsonNode; }Значение листа: ссылка, дата, денежная сумма — оформление знает приложение.

События

EventСигнатураОписание
copy(event: "copy", payload: { path: string; value: unknown; }): void—

Методы

MethodsСигнатураОписание
filter(value: string) => voidОтфильтровать дерево по ключу или значению — тем же запросом, что в поле поиска. Пустая строка снимает фильтр.
expandAll() => voidРаскрыть все ветки.
collapseAll() => voidСвернуть все ветки, включая корень.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrJsonViewerConfigurableProps, GrJsonNodeKind, GrJsonNode } from '@feugene/granularity/components/GrJsonViewer'

Файлdist/types/components/GrJsonViewer/config.d.ts

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

Файлdist/types/components/GrJsonViewer/defaults.d.ts

GrJsonViewerConfigurablePropsinterface

Пропы GrJsonViewer, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrJsonViewer: { … } }">. Пороги обрезки здесь наравне с оформлением не случайно: «сколько показывать» — решение приложения, а не экрана. У админки с ответами модели и у панели с конфигами оно разное, и повторять его на каждом просмотрщике значит однажды забыть.

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

Файлdist/types/components/GrJsonViewer/jsonToNodes.d.ts

GrJsonNodeKindtype

Что за значение в узле. Решает и цвет, и то, раскрывается ли узел.

type GrJsonNodeKind = 'object' | 'array' | 'string' | 'number' | 'boolean' | 'null'
GrJsonNodetype
type GrJsonNode = {
    /** Читаемый адрес узла (`$.items[3].name`). Он же ключ для `GrTree`. */
    path: string;
    /** Имя ключа или индекс. Показывается слева от двоеточия. */
    label: string;
    kind: GrJsonNodeKind;
    /** Что видно в строке. У ветки — счётчик, у листа — значение, возможно обрезанное. */
    preview: string;
    /** Полное значение. Копирование берёт его, а не `preview`. */
    value: unknown;
    /** Показ обрезан: копирование всё равно отдаёт целое. */
    truncated: boolean;
    isLeaf: boolean;
    children?: GrJsonNode[];
};

Файлdist/types/components/GrJsonViewer/GrJsonViewer.vue.d.ts

GrJsonViewerPropsinterface

JSON со свёрткой по узлам, поиском по ключу и значению и копированием узла. Дерево, клавиатуру и виртуализацию несёт GrTree; здесь — разбор unknown в узлы и разметка строки. Плоский текст целиком, который копируют в тикет, — GrCodeBlock.

interface GrJsonViewerProps {
    /** Показываемое значение. `unknown`, потому что приходит из БД или чужого сервиса. */
    value: unknown;
    /** Подпись корня. По умолчанию `$` — так адресуют путь JSONPath и dev-tools. */
    rootLabel?: string;
    /** Сколько уровней раскрыто изначально. */
    defaultExpandDepth?: number;
    /**
     * Длина строкового значения, после которой показ обрезается.
     *
     * Не косметика: запрос к модели с картинкой в base64 — это один лист на
     * сотни тысяч символов, и ни свёртка, ни виртуализация по строкам его не
     * берут. Копирование при этом отдаёт значение целиком.
     */
    maxStringLength?: number;
    /** Сколько элементов массива разбирать до заглушки «ещё N». */
    maxArrayItems?: number;
    /**
     * Панель над деревом: поле поиска и «Развернуть всё / Свернуть всё». По
     * умолчанию **включена** (умолчание меняется через `GrConfigProvider`).
     * `false` убирает панель целиком: поиск остаётся на `filter()`, раскрытие —
     * на `expandAll()`/`collapseAll()` из `defineExpose`.
     */
    searchable?: boolean;
    /** Кнопка копирования узла в строке. */
    copyable?: boolean;
    /** Оставлять в DOM только окно вокруг вьюпорта. Требует `maxHeight`. */
    virtual?: boolean;
    /**
     * Высота области просмотра со своим скроллом — и без `virtual`. Число —
     * пиксели, строка — как есть.
     */
    maxHeight?: string | number;
    /** Имя области для скринридера. Безымянное дерево объявляется просто «дерево». */
    ariaLabel?: string;
    /** Кегль дерева и высота поиска и кнопок панели. Не задан — из `GrConfigProvider`, иначе `md`. */
    size?: GrComponentSize;
}
GrJsonViewerEmitsinterface
interface GrJsonViewerEmits {
    /** Узел скопирован: путь для тикета и полное значение. */
    (e: 'copy', payload: {
        path: string;
        value: unknown;
    }): void;
}

Файлdist/types/components/GrJsonViewer/safelist.d.ts

grJsonViewerSafelistconst

Пуст: составных классов у просмотрщика нет. Цвет значения берётся из мапы jsonValueClass целым литералом, как и остальные классы grJsonViewerStyles.ts, и granum извлекает их из чанков компонента сам, включая общие.

const grJsonViewerSafelist: string[];

Файлdist/types/components/GrJsonViewer/index.d.ts

GrJsonViewerInstancetype
type GrJsonViewerInstance = ComponentExposed<typeof GrJsonViewerComponent>;

Доступность

Паттерн APG

tree (через GrTree)

КлавишаДействие
—своих клавиш нет: дерево внутри — GrTree, и весь его контракт действует без изменений. Поле поиска и кнопки свёртки над деревом — обычные контролы, каждый со своей остановкой Tab

Использование из прозы библиотеки

Показ обрезается, копирование — нет

Длинное строковое значение обрезается в строке (maxStringLength), длинный массив обрывается заглушкой «ещё N» (maxArrayItems). Это не косметика: запрос к модели с картинкой в base64 — один лист на сотни тысяч символов, и ни свёртка по узлам, ни виртуализация по строкам его не берут, потому что узел там один и строка одна.

В буфер при этом уходит полное значение узла вместе с путём: обрезка принадлежит показу, и вставить её обратно нельзя.

Повторная ссылка — не цикл

Маркер [Circular] достаётся только настоящему предку по цепочке, а объект, честно положенный в данные дважды, рисуется дважды.

Это тот случай, где обход дерева умеет строго больше сериализации: replacer у JSON.stringify стека предков не получает и вынужден метить любую повторную ссылку. Обход стек имеет, поэтому у GrCodeBlock и GrJsonViewer на одних и тех же данных разный — и каждый по-своему правильный — результат.

Адрес узла читается

Ключом узла служит путь ($.items[3].name), а не порядковый номер: он уходит в событие copy, по нему же задаётся раскрытие. Ключ с точкой или пробелом экранируется ($["a.b"]), иначе адрес перестал бы быть адресом.

Раскрытие задаётся глубиной, а не списком

defaultExpandDepth раскрывает первые N уровней; expandAll() и collapseAll() из defineExpose переключают всё дерево. Обе кнопки сбрасывают ручное раскрытие пользователя — от «раскрыть всё» этого и ждут.

Поиск идёт по ключу и по значению

В чужом ответе ищут то одно, то другое, поэтому предикат смотрит и на имя ключа, и на показанное значение. Совпавшие узлы дерево подсвечивает целиком и раскрывает к ним путь.

Панель над деревом — поле поиска и «Развернуть всё / Свернуть всё» — включена по умолчанию. Её можно убрать (searchable: false) и звать filter(query), expandAll() и collapseAll() снаружи — когда строка поиска на странице уже есть и вторая была бы лишней.

maxHeight ограничивает область просмотра своим скроллом и без virtual.

Границы

Не редактирует и не диффит: просмотрщик показывает то, что пришло. Подсветки совпавшей подстроки внутри строки нет — дерево отмечает узел целиком. Виртуализация включается только вместе с maxHeight (GrTree): без высоты окно считать не от чего.