Observed since 20 Oct, 19:39 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
3dcsv
Pipe-separated comma-separated values — infinitely dimensional data in a 2D CSV.
Pipe-separated comma-separated values — represent complex, multi-valued data in a single 2D CSV. One table, one header row, list-valued cells via a dimension delimiter (default |).
Yes, it’s still a 2D grid. The “3D” is aspirational.
bun add 3dcsv
# or
npm install 3dcsv
For typed rows with runtime validation (recommended):
bun add 3dcsv zod
import { parse, stringify } from "3dcsv";
const csv = `name,age,tags
Alice,30,"js|ts|rust"
Bob,25,"hiking|photography"`;
const { headers, rows } = parse(csv);
// headers: ['name', 'age', 'tags']
// rows: [
// { name: 'Alice', age: '30', tags: ['js', 'ts', 'rust'] },
// { name: 'Bob', age: '25', tags: ['hiking', 'photography'] }
// ]
const back = stringify({ headers, rows });
// roundtrip: back === csv
Pass a Zod schema; return type is inferred. No type assertions.
import { parse } from "3dcsv";
import { z } from "zod";
const personSchema = z.object({
name: z.string(),
age: z.coerce.number(),
tags: z.array(z.string()),
});
const result = parse(csv, { schema: personSchema });
// result.rows is { name: string; age: number; tags: string[] }[]
// Optional runtime validation
const validated = parse(csv, { schema: personSchema, validate: true });
type Person = { name: string; age: number; tags: string[] };
const result = parse<Person>(csv);
// result.rows is Person[]
const result = parse(csv);
result.rows; // default: array of row objects
result.toRows(); // always: Cell[][] (array of arrays)
result.toObjects(); // always: Record<string, Cell>[]
Come back down to earth and expand list-valued cells into one row per element (join-table style). Then stringify to get plain CSV with no pipes.
const csv = `name,tags
Alice,"js|ts|rust"`;
const flat = parse(csv).flatten();
// flat.rows: [{ name: "Alice", tags: "js" }, { name: "Alice", tags: "ts" }, ...]
const regularCsv = stringify(flat); // no pipes
Optional: parse(csv).flatten({ columns: ["tags"] }) to expand only specific columns.
Run a local server and open a React table view in the browser:
bunx 3dcsv visualize
# or with a file (relative to cwd):
bunx 3dcsv visualize example.csv
Opens http://localhost:3847 (or PORT env). List-valued cells render as badges. The UI is built with React and bundled with Bun; run bun run dev:ui to watch for changes and bun run dev:server to run the server.
If you do nothing, you get { headers: string[]; rows: unknown[] }. Typed rows are opt-in via schema or generic.
parse(input, options?) — Parse 3D CSV. Options: dimensionDelimiters, header, columns, asObjects, schema, validate.parse(csv).flatten(options?) — Expand array cells into one row per element (Cartesian product when multiple columns); returns Parsed3DCSV with all scalar rows. Options: columns?: string[].stringify(data, options?) — Stringify to 3D CSV. Accepts { headers, rows } or array of row objects (headers inferred).bunx 3dcsv visualize [file.csv] — Start a local server and open a table UI; optional file path (relative to cwd).It’s just CSV with pipes in the cells. Sometimes that’s all you need. Sometimes.
|)."a|b|c" is one cell whose value is the list ["a", "b", "c"]."" inside quoted fields).Changesets for versioning: run bun run changeset when you change the package. Merge the “Version Packages” PR to publish. See .changeset/README.md.
MIT
3D CSV: because one dimension was never enough. We’re not saying it’s a good idea — we’re just saying it works.
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
@changesets/cliEnvironment{
"local_name": "@changesets/cli",
"alias": false,
"peer_metadata": null
} | ^2.27.0 | development |
@types/reactEnvironment{
"local_name": "@types/react",
"alias": false,
"peer_metadata": null
} | ^18.3.0 | development |
@types/react-domEnvironment{
"local_name": "@types/react-dom",
"alias": false,
"peer_metadata": null
} | ^18.3.0 | development |
bun-typesEnvironment{
"local_name": "bun-types",
"alias": false,
"peer_metadata": null
} | latest | development |
reactEnvironment{
"local_name": "react",
"alias": false,
"peer_metadata": null
} | ^18.3.0 | development |
react-domEnvironment{
"local_name": "react-dom",
"alias": false,
"peer_metadata": null
} | ^18.3.0 | development |
typescriptEnvironment{
"local_name": "typescript",
"alias": false,
"peer_metadata": null
} | ^5.3.0 | development |
zodEnvironment{
"local_name": "zod",
"alias": false,
"peer_metadata": {
"optional": true
}
} | >=3.0.0 | peer |
Source distribution
{
"integrity": "sha512-uzGsyCF3H8hsM9j8j4Yo8sneBvOLMKPK7NqvOV18a2PHqIBeA8oo7ZaySs/z8U10S9Hwpaxs0o7AMdSwNJeAsw==",
"shasum": "ef6d2574b8c37a7f923e37d7bfd54e1400366792",
"tarball": "https://registry.npmjs.org/3dcsv/-/3dcsv-0.3.0.tgz",
"fileCount": 23,
"unpackedSize": 1007901,
"signatures": [
{
"keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U",
"sig": "MEUCIQDhExdHaay1F4ZwK6b8e9C/Kj9GYJ9M+2T/KXKS+q7l9gIgG4pC8gy/W/1BBiIpb/+eDPaRyM3x+u4XzwGiQ3wBZFw="
}
]
}Observed since 20 Oct, 19:39 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:750b4fe741e629f9d05b8f9e4a5fb2b01b5b72702a229fcf2d4e7518acd07b2f