A zero-dependency, TypeScript-first JSON toolkit for querying, transforming, and converting JSON data.
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.
- 🔍 Query — A practical JSONPath engine with filters, slices, wildcards, and recursive descent.
- 🔁 Transform —
pick,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
intersectandunion. - ✅ Validate — A tiny Zod-inspired schema validator with coercion, enums, ranges, and defaults.
- 🖥️ CLI — A friendly
jccommand for the terminal, reading from files or stdin. - 📦 Zero dependencies — Only your runtime. Nothing else.
- 🔒 Type-safe — Strict TypeScript with full type inference.
npm install @cryptoteep/jsoncraft
# or
bun add @cryptoteep/jsoncraft
# or
pnpm add @cryptoteep/jsoncraftFor CLI-only usage, install globally:
npm install -g @cryptoteep/jsoncraftimport {
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: {...} }# 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.jsonThe 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 childrenimport { 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); // deduplicateimport { 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 TOMLSupported 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.
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 depthimport { 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 concatenationimport { 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).
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>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)
MIT © Cryptoteep
See LICENSE for the full text.
If jsoncraft saves you time, consider sponsoring the project
or just starring the repo — it really helps.