Skip to content

Cryptoteep/jsoncraft

Repository files navigation

jsoncraft

A zero-dependency, TypeScript-first JSON toolkit for querying, transforming, and converting JSON data.

npm version npm downloads License: MIT Zero dependencies TypeScript Node.js


jsoncraft is a single, focused library and CLI for everything you do with JSON: query it (JSONPath), transform it (pick, omit, flatten, groupBy, ...), convert between formats (JSON ↔ YAML/CSV/JSONL/Properties/TOML), format it with colors, diff and merge it, and validate it against a lightweight schema — all with zero runtime dependencies.

It runs anywhere JavaScript does: Node.js, Bun, Deno, and modern browsers.

✨ Features

  • 🔍 Query — A practical JSONPath engine with filters, slices, wildcards, and recursive descent.
  • 🔁 Transformpick, omit, map, filter, renameKeys, flatten, unflatten, groupBy, sortBy, chunk, unique.
  • 🔄 Convert — Bidirectional conversion between JSON, YAML, CSV, JSONL, Java Properties, and TOML.
  • 🎨 Format — Pretty-print with ANSI colors, key sorting, depth limiting, compact mode.
  • 📊 Diff & Patch — Structural deep diff with JSONPath locations and patch application.
  • 🧬 Merge — Configurable deep merge with array strategies (replace / concat / merge-by-index), plus intersect and union.
  • ✅ Validate — A tiny Zod-inspired schema validator with coercion, enums, ranges, and defaults.
  • 🖥️ CLI — A friendly jc command for the terminal, reading from files or stdin.
  • 📦 Zero dependencies — Only your runtime. Nothing else.
  • 🔒 Type-safe — Strict TypeScript with full type inference.

📦 Installation

npm install @cryptoteep/jsoncraft
# or
bun add @cryptoteep/jsoncraft
# or
pnpm add @cryptoteep/jsoncraft

For CLI-only usage, install globally:

npm install -g @cryptoteep/jsoncraft

🚀 Quick start

As a library

import {
  query, pick, omit, flatten, groupBy, sortBy,
  convert, format, diff, merge, validate,
} from '@cryptoteep/jsoncraft';

// ── Query with JSONPath ─────────────────────────────────
const data = {
  store: {
    book: [
      { category: 'reference', author: 'Nigel Rees', price: 8.95 },
      { category: 'fiction', author: 'Herman Melville', price: 8.99 },
      { category: 'fiction', author: 'J.R.R. Tolkien', price: 22.99 },
    ],
  },
};

query(data, '$.store.book[*].author');
// → ['Nigel Rees', 'Herman Melville', 'J.R.R. Tolkien']

query(data, '$..book[?(@.price < 10)].author');
// → ['Nigel Rees', 'Herman Melville']

// ── Transform ──────────────────────────────────────────
const users = [{ id: 1, name: 'Alice', password: 'x' }, { id: 2, name: 'Bob', password: 'y' }];
users.map((u) => pick(u, ['id', 'name']));
// → [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]

flatten({ a: { b: { c: 1 } } });
// → { 'a.b.c': 1 }

groupBy([{ role: 'admin' }, { role: 'user' }, { role: 'admin' }], 'role');
// → { admin: [...], user: [...] }

// ── Convert formats ────────────────────────────────────
convert('[{"id":1},{"id":2}]', 'json', 'yaml');
// →
// - id: 1
// - id: 2

convert('a: 1\nb: 2', 'yaml', 'json');
// → '{"a":1,"b":2}'

// ── Diff & merge ───────────────────────────────────────
diff({ a: 1, b: 2 }, { a: 1, c: 3 });
// → [
//     { path: '$.b', type: 'remove', oldValue: 2 },
//     { path: '$.c', type: 'add', value: 3 },
//   ]

merge({ a: 1, b: { x: 1 } }, { b: { y: 2 }, c: 3 });
// → { a: 1, b: { x: 1, y: 2 }, c: 3 }

// ── Validate ───────────────────────────────────────────
const schema = {
  type: 'object',
  properties: {
    name: { type: 'string', required: true },
    age: { type: 'number', min: 0, max: 150 },
    role: { type: 'string', enum: ['admin', 'user'] },
  },
};
validate({ name: 'Alice', age: 30, role: 'admin' }, schema);
// → { valid: true, errors: [], value: {...} }

As a CLI

# Query JSON with JSONPath
jc query '$.store.book[*].author' books.json
jc query '$..book[?(@.price < 10)].title' books.json

# Pipe through stdin
cat data.json | jc query '$.users[*].name'

# Pick / omit keys
jc pick id,name users.json
jc omit password,token users.json

# Convert formats
jc convert --to yaml package.json
jc convert --from yaml --to json config.yaml
jc convert --to csv users.json

# Pretty-print with colors
jc format data.json
cat data.json | jc format --color

# Diff and merge
jc diff old.json new.json
jc merge base.json override.json

# Validate against a schema
jc validate schema.json data.json

# Stats and structure
jc stats big-file.json
jc flatten nested.json

📖 Documentation

Query (JSONPath)

The query engine supports a practical subset of JSONPath:

Expression Meaning
$ The root object
.key Object property
['key'] Object property (bracket form)
[index] Array index (supports negative)
[start:stop] Array slice
[*] Wildcard — all elements / all keys
..key Recursive descent
[?(expr)] Filter expression

Filter expressions support:

  • @ — current node
  • @.field — field of current node
  • Comparisons: == != < <= > >=
  • exists(@.field) — existence check
query(data, '$.store.book[0].title');          // first book's title
query(data, '$.store.book[-1]');                // last book
query(data, '$.store.book[0:2]');               // first two books (slice)
query(data, '$..author');                       // all authors anywhere
query(data, '$.store.book[?(@.price < 10)]');   // books under 10
query(data, '$.store.book[?(@.category == "fiction")]');
query(data, '$.store.*');                       // all store children

Transform

import { pick, omit, map, filter, renameKeys, flatten, unflatten,
         groupBy, sortBy, chunk, unique, clone } from '@cryptoteep/jsoncraft';

clone(value);                    // deep clone
pick(obj, ['id', 'name']);       // keep only these keys
omit(obj, ['password']);         // remove these keys
map(arr, fn);                    // map array
filter(arr, predicate);          // filter array
renameKeys(obj, { old: 'new' }); // rename keys
flatten(obj);                    // { a: { b: 1 } } → { 'a.b': 1 }
unflatten(obj);                  // { 'a.b': 1 } → { a: { b: 1 } }
groupBy(arr, 'role');            // group array by key
sortBy(arr, 'age', 'desc');      // sort array
chunk(arr, 3);                   // split into chunks
unique(arr);                     // deduplicate

Convert

import { convert, parse, stringify } from '@cryptoteep/jsoncraft';

convert(input, 'json', 'yaml');         // JSON string → YAML string
parse(input, 'csv');                    // parse CSV into JSON
stringify(value, 'toml');               // serialize to TOML

Supported formats: json, yaml, csv, jsonl, properties, toml.

Note: The YAML and TOML implementations cover the common subset used in configuration files. They are not full-spec implementations. For complex documents, consider a dedicated parser.

Format

import { format, formatCompact, colorize, size, countNodes, depth } from '@cryptoteep/jsoncraft';

format(value, { indent: 2, color: true, sortKeys: true });
formatCompact(value);
colorize(jsonString);
size(value);        // bytes when JSON-stringified
countNodes(value);  // total nodes in tree
depth(value);       // max nesting depth

Diff & Merge

import { diff, applyPatch, merge, mergeMany, intersect, union } from '@cryptoteep/jsoncraft';

const entries = diff(before, after);
const restored = applyPatch(before, entries);

merge(a, b, { arrays: 'concat' });
mergeMany([a, b, c]);
intersect(a, b);   // keys in both, deeply equal
union(arrA, arrB); // deduplicated concatenation

Validate

import { validate, formatErrors } from '@cryptoteep/jsoncraft';

const schema = {
  type: 'object',
  properties: {
    port: { type: 'number', min: 1, max: 65535, default: 3000 },
    host: { type: 'string', required: true },
    debug: { type: 'boolean', coerce: true },
    mode: { type: 'string', enum: ['dev', 'staging', 'prod'] },
  },
};

const result = validate(config, schema);
if (!result.valid) {
  console.error(formatErrors(result));
}

Schema nodes support: type, required, default, enum, min, max, pattern (strings), coerce, items (arrays), properties (objects).

🌳 Browser & Deno support

jsoncraft ships as ESM with no Node.js-specific imports in the core library (only the CLI uses node:fs and node:process). Import it directly in a browser or Deno project:

<script type="module">
  import { query } from 'https://esm.sh/jsoncraft';
  console.log(query(data, '$.users[*].name'));
</script>

🤝 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for guidelines on how to get started, coding standards, and the pull-request process.

Some areas where help is especially appreciated:

  • 🐛 Bug reports and reproducible test cases
  • 📝 Documentation improvements and translations
  • 🧪 More format edge cases (YAML/TOML/CSV roundtrips)
  • 🔌 New transform operators and query filter functions
  • 🎨 CLI enhancements (interactive mode, autocompletion)

📜 License

MIT © Cryptoteep

See LICENSE for the full text.

💖 Sponsors

If jsoncraft saves you time, consider sponsoring the project or just starring the repo — it really helps.

About

A zero-dependency, TypeScript-first JSON toolkit for querying, transforming, and converting JSON data. JSONPath, YAML/CSV/TOML conversion, diff, merge, validate — runs on Node.js, Bun, Deno, and browsers.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors