@cryptoteep/jsoncraft
v0.1.0
Published
A zero-dependency, TypeScript-first JSON toolkit for querying, transforming, and converting JSON data.
Maintainers
Readme
jsoncraft
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.
- 🔁 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.
📦 Installation
npm install jsoncraft
# or
bun add jsoncraft
# or
pnpm add jsoncraftFor CLI-only usage, install globally:
npm install -g jsoncraft🚀 Quick start
As a library
import {
query, pick, omit, flatten, groupBy, sortBy,
convert, format, diff, merge, validate,
} from '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 childrenTransform
import { pick, omit, map, filter, renameKeys, flatten, unflatten,
groupBy, sortBy, chunk, unique, clone } from '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); // deduplicateConvert
import { convert, parse, stringify } from '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.
Format
import { format, formatCompact, colorize, size, countNodes, depth } from '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 depthDiff & Merge
import { diff, applyPatch, merge, mergeMany, intersect, union } from '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 concatenationValidate
import { validate, formatErrors } from '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.
