npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@json-table/core

v0.5.0

Published

Library for creating tables from JSON data

Downloads

716

Readme

@json-table/core

Set of tools for converting JSON data into tables (HTML, XLSX, ASCII).

Install

npm install @json-table/core

How it works

Every JSON value is drawn as a rectangular block:

  • a primitive (string, number, boolean, null) is a single cell;
  • an object lays its properties out side by side, each key becoming a header above its value;
  • an array stacks its items vertically, numbering each one.

Because every block is a rectangle, they compose naturally: a value nested inside another simply takes its place in the parent's layout, and neighbouring blocks align along shared edges — which is why arbitrarily deep JSON always produces a well-formed table.

Finally, repetition is removed: when all items of an array begin with the same header band (or the same index column), it is lifted out and drawn once at the top/left instead of repeating per item.

Rendering then just traces this layout into whatever output you need — ASCII, HTML, XLSX, or your own format.

Usage

import {
  makeTreeFactory,
  joinPrimitiveArrayValues,
  toASCII,
  toHTML,
} from "@json-table/core";

const createTree = makeTreeFactory({
  cornerCellValue: "№",
  createHeader: (key) => key,
  createIndex: (i) => i + 1,
  joinArrayValues: joinPrimitiveArrayValues,
});

const tree = createTree(data);

const asciiTable = toASCII(tree);

/* Or */

const htmlTable = toHTML(tree);

Input data:

{
  "key": "val",
  "primitiveArr": [1, "two", false],
  "object": {
    "key1": "value1",
    "key2": 789,
    "key3": {
      "nestedKey": "nestedVal"
    }
  },
  "nestedArray": [
    {
      "name": "John",
      "age": 30,
      "isStud": false
    },
    {
      "name": "Alice",
      "age": 25,
      "isStud": true
    }
  ]
}

Output:

+-----+---------------+---------------------------+--------------------------+
| key | primitiveArr  |          object           |         nestedArray      |
+-----+---------------+--------+------+-----------+---+-------+-----+--------+
|     |               |  key1  | key2 |   key3    | № | name  | age | isStud |
|     |               +--------+------+-----------+---+-------+-----+--------+
| val | 1, two, false |        |      | nestedKey | 1 | John  |  30 | false  |
|     |               | value1 |  789 +-----------+---+-------+-----+--------+
|     |               |        |      | nestedVal | 2 | Alice |  25 | true   |
+-----+---------------+--------+------+-----------+---+-------+-----+--------+

Factory options

makeTreeFactory<V>(options) accepts:

| Option | Default | Description | | --- | --- | --- | | cornerCellValue | — (required) | Value of the top-left corner cell above lifted header bands. | | createHeader | — (required) | (key, record) => leaf — header cell for an object key. | | createIndex | — (required) | (i, array) => leaf — index cell for array item i. | | createLeaf | identity | (value) => leaf — formats data leaves (joined arrays included). Headers, indexes, corners and filler never pass here. | | joinArrayValues | undefined | (values) => leaf \| undefined — merges an array into one leaf; return undefined to render it as a table. Receives raw input order, before key stabilization. Use joinPrimitiveArrayValues to comma-join all-primitive arrays (the pre-0.5 behavior). | | emptyCellValue | () => "" | ({ type, width, height }) => leaf — placeholder for content-less cells. type is "gap" (sizing filler), "empty-array" or "empty-object". | | isProportionalResize | allows 100% growth | (lcm, max) => boolean — guards LCM-scaling of sibling heights. Use makeProportionalResizeGuard(threshold); when it rejects, short columns are padded with filler instead. | | collapseIndexes | false | Flatten nested arrays into dotted index paths (1.2, …) instead of nested index columns. | | stabilizeOrderOfPropertiesInArraysOfObjects | true | Reorder keys of objects inside an array by stable first-seen position, so columns line up. | | deduplicateHeaders | true | Lift the header band common to all array items on top of the table. Set false to keep per-row headers. | | isHeaderEqual | Object.is | (a, b) => boolean — custom equality for header values during band lifting. Needed when headers carry objects (see below). |

Input handling

  • Objects with a toJSON() method are unwrapped via toJSON() first.
  • Objects exposing a [TO_TABLE]() method (import TO_TABLE from @json-table/core) bypass parsing with their prebuilt Tree.
  • Every array level renders its index column — no special cases, so [x] stays distinguishable from x and [{ ... }] from { ... }. joinArrayValues runs before that, uniformly (empty arrays never reach it: they render as the emptyCellValue filler). In collapseIndexes mode nested levels flatten into dotted paths ([[123]]1.1 | 123); a flat singleton ([x]) flattened nothing and renders bare, as do empty arrays.
  • Empty arrays render as one empty-array filler cell, empty objects as one empty-object filler cell (see emptyCellValue).
  • Headers carrying objects never lift by identity alone: pass isHeaderEqual to compare them (e.g. by label). See the interactive table example.

Renderers

  • toASCII(tree, { format })format is ASCIITableFormat.MySQL (default) or ASCIITableFormat.MarkdownLike.
  • toHTML(tree)<table> with colspan/rowspan; header, index and corner cells are wrapped in <b>.
  • XLSX — see @json-table/xlsx.

[!TIP] Writing your own renderer is easy — cells() walks the tree and yields every cell with its position and span:

import { cells } from "@json-table/core";

for (const { node, x, y, width, height } of cells(tree)) {
  // node.type: "header" | "index" | "corner" | "leaf"
}

For HTML-like renderers, rows() groups cells into visual rows (one array per <tr>, ordered left-to-right; rows fully covered by rowspans are present but empty):

import { rows } from "@json-table/core";

for (const row of rows(tree)) {
  // each row: cells starting in it, sorted by x
}

See tree-to-html for a complete minimal renderer.

Interactive table example.

Layout transforms

Trees can be transformed in place (or structurally) before rendering:

import {
  transposeTree,
  horizontalMirrorInPlace,
  verticalMirrorInPlace,
  normalizeExtentsInPlace,
} from "@json-table/core";
  • transposeTree(tree) — reflect over the main diagonal (rows become columns). Returns a new tree.
  • horizontalMirrorInPlace(tree) — reverse column order.
  • verticalMirrorInPlace(tree) — reverse row order.
  • normalizeExtentsInPlace(tree) — recompute container extents bottom-up (sum along / max across). Useful after manual tree surgery.

Benchmarks

pnpm --filter @json-table/core bench

Runs the vitest bench harness (src/*.bench.ts, excluded from tests, builds and publishes): factory presets (default, collapseIndexes, joinPrimitiveArrayValues) over fixtures plus synthetic large inputs (5k-row lifting-pipeline stress, wide records, deep nesting), and toASCII/toHTML over prebuilt trees. Deliberately outside the turbo pipeline and CI — perf PRs must quote before/after numbers from it.

Legacy Block API

The pre-0.4 Block/Table pipeline (as published in @json-table/[email protected]) remains available unmodified under a subpath export:

import {
  makeTableFactory,
  makeBlockFactory,
  blockToASCII,
  blockToHTML,
} from "@json-table/core/legacy";

It mirrors the 0.3.0 export surface (root model plus block, block-matrix, block-to-ascii, block-to-html and json-to-table modules) in a single entry point. Prefer the root Tree-based API for new code.

License

MIT