Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
A lightweight, zero-dependency CLI tool to compare multiple JSON files and identify missing (non-common) keys.
It generates a per-file report of unique keys to help identify schema drift.

Perfect for auditing translation files, config sets, or API mocks.
### Why jsonkdiff?
Standard diff tools (like `git diff`) show line-by-line changes. `@grrtbrtr/jsonkdiff` focuses on **structural completeness**. It answers the question: *"I added a new key to my English translation file; did I forget to add it to the other 5 languages?"*

## Features

Expand All @@ -29,16 +30,44 @@ npm install -g @grrtbrtr/jsonkdiff
jsonkdiff file1.json file2.json file3.json
```

## Example output
## Usage example: auditing i18n files

If `en.json` has `{"auth": {"login": "Log In"}}` and `es.json` is empty, `jsonkdiff` will report:
**file-a.json (English)**
```JSON
{
"es.json": [
"auth",
"auth.login"
]
}
{ "nav": { "home": "Home", "about": "About" }, "logout": "Log Out" }
```

**file-b.json (French)**
```JSON
{ "nav": { "home": "Accueil" } }
```

**Running:**
```bash
npx @grrtbrtr/jsonkdiff file-a.json file-b.json
```

**Output:**
```
--- JSON Key Diff Report ---

File: b.json
× Missing key: nav.about
× Missing key: logout
```

## CI/CD integration

`jsonkdiff` follows standard Unix exit codes, making it usable in CI/CD pipelines (GitHub Actions, GitLab CI, etc.):

- **Exit code `0`**: No missing keys found (Success).
- **Exit code `1`**: Missing keys detected or an error occurred (Failure).

**Example: failing a GitHub Action if keys are missing**

```yaml
- name: Audit JSON Schemas
run: npx @grrtbrtr/jsonkdiff locales/en.json locales/fr.json
```

## License
Expand Down
2 changes: 1 addition & 1 deletion bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const files = process.argv.slice(2);

if (files.length < 2) {
console.log(`\n${STYLES.bold}Usage:${STYLES.reset} jsonkdiff <file1.json> <file2.json> ...`);
process.exit(0);
process.exit(1);
}

async function main() {
Expand Down
27 changes: 23 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
{
"name": "@grrtbrtr/jsonkdiff",
"version": "1.0.2",
"version": "1.1.0",
"description": "Compare multiple JSON files and find non-common keys. Generates a per-file report of unique keys to help identify schema drift.",
"keywords": [
"json",
"json-keys",
"json-diff",
"json-compare",
"json-comparison",
"diff",
"json-schema-validator",
"i18n-audit",
"i18n-checker",
"i18n-validator",
"localization-tools",
"localization-helper",
"translation-check",
"schema",
"schema-drift",
"comparison",
"deep-diff",
"recursive-compare",
"zero-dependencies",
"cli",
"devtools"
"cli-tool",
"devtools",
"config-validator",
"deep-comparison"
],
"homepage": "https://github.com/grrtbrtr/jsonkdiff#readme",
"bugs": {
Expand All @@ -23,8 +36,14 @@
},
"license": "GPL-3.0-only",
"author": "Gerrit Bertier <gerrit.bertier@gmail.com>",
"funding": "https://github.com/grrtbrtr",
"type": "module",
"sideEffects": false,
"main": "src/index.js",
"exports": {
".": "./src/index.js",
"./package.json": "./package.json"
},
"bin": {
"jsonkdiff": "bin/cli.js"
},
Expand Down
9 changes: 7 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ import { getDeepKeys } from './utils/object-utils.js';
*/
export function computeKeyDiff(datasets) {
const allKeys = new Set();

const processedData = datasets.map(ds => {
const keys = getDeepKeys(ds.object);
keys.forEach(k => allKeys.add(k));
return { name: ds.name, keys };

return {
name: ds.name,
keySet: new Set(keys),
};
});

const report = {};
for (const item of processedData) {
report[item.name] = [...allKeys].filter(k => !item.keys.includes(k));
report[item.name] = [...allKeys].filter(k => !item.keySet.has(k));
}
return report;
}
Expand Down