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 ─────────────────────────────────constdata={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 ──────────────────────────────────────────constusers=[{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: 2convert('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 ───────────────────────────────────────────constschema={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 titlequery(data,'$.store.book[-1]');// last bookquery(data,'$.store.book[0:2]');// first two books (slice)query(data,'$..author');// all authors anywherequery(data,'$.store.book[?(@.price < 10)]');// books under 10query(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 clonepick(obj,['id','name']);// keep only these keysomit(obj,['password']);// remove these keysmap(arr,fn);// map arrayfilter(arr,predicate);// filter arrayrenameKeys(obj,{old: 'new'});// rename keysflatten(obj);// { a: { b: 1 }} → { 'a.b': 1 }unflatten(obj);// { 'a.b': 1 } → { a: { b: 1 }}groupBy(arr,'role');// group array by keysortBy(arr,'age','desc');// sort arraychunk(arr,3);// split into chunksunique(arr);// deduplicateimport{convert,parse,stringify}from'@cryptoteep/jsoncraft';convert(input,'json','yaml');// JSON string → YAML stringparse(input,'csv');// parse CSV into JSONstringify(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-stringifiedcountNodes(value);// total nodes in treedepth(value);// max nesting depthimport{diff,applyPatch,merge,mergeMany,intersect,union}from'@cryptoteep/jsoncraft';constentries=diff(before,after);constrestored=applyPatch(before,entries);merge(a,b,{arrays: 'concat'});mergeMany([a,b,c]);intersect(a,b);// keys in both, deeply equalunion(arrA,arrB);// deduplicated concatenationimport{validate,formatErrors}from'@cryptoteep/jsoncraft';constschema={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']},},};constresult=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:
<scripttype="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.