{"id":19987794,"url":"https://github.com/mawrkus/joey-the-differ","last_synced_at":"2025-06-26T07:32:39.590Z","repository":{"id":46952783,"uuid":"254595170","full_name":"mawrkus/joey-the-differ","owner":"mawrkus","description":"🧬  JSON diffing on steroids","archived":false,"fork":false,"pushed_at":"2021-09-20T17:58:42.000Z","size":76,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-15T06:37:39.574Z","etag":null,"topics":["compare","comparison","diff","difference","diffing","json"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mawrkus.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-10T09:25:57.000Z","updated_at":"2021-09-20T17:58:45.000Z","dependencies_parsed_at":"2022-09-15T14:00:20.979Z","dependency_job_id":null,"html_url":"https://github.com/mawrkus/joey-the-differ","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/mawrkus/joey-the-differ","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Fjoey-the-differ","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Fjoey-the-differ/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Fjoey-the-differ/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Fjoey-the-differ/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mawrkus","download_url":"https://codeload.github.com/mawrkus/joey-the-differ/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mawrkus%2Fjoey-the-differ/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259964328,"owners_count":22938724,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["compare","comparison","diff","difference","diffing","json"],"created_at":"2024-11-13T04:38:32.766Z","updated_at":"2025-06-26T07:32:39.562Z","avatar_url":"https://github.com/mawrkus.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Joey the Differ\n\nJSON diffing on steroids.\n\n## 🧬 Features\n\n- Custom differs\n- Blacklist\n- Preprocessors\n- Bulk files processing\n- CLI-friendly\n- Docker-ready\n\n## 🧬 Installation\n\n```shell\nnpm install joey-the-differ\n```\n\n## 🧬 Usage\n\n### Command line\n\n```text\nUsage: joey-the-differ [options]\n\nOptions:\n  -V, --version        output the version number\n  -s, --source [file]  source file or directory, required\n  -t, --target [file]  target file or directory, required\n  -o, --output [file]  output file or directory, optional\n  -c, --config [file]  config file (JS), optional\n  -v, --verbose        verbose mode, optional\n  -h, --help           display help for command\n```\n\nFor instance, using [npx](https://github.com/npm/npx):\n\n```shell\nnpx joey-the-differ -s demo/source.json -t demo/target.json -c demo/options.js\n```\n\nor using [Docker](https://www.docker.com/why-docker):\n\n```shell\ndocker build . -t mawrkus/joey-the-differ\ndocker run -v ${PWD}:/tmp mawrkus/joey-the-differ -s /tmp/demo/source.json -t /tmp/demo/target.json -c /tmp/demo/options.js\n```\n\nHave a look at the [demo folder](./demo) to see the content of the files.\n\n#### Bulk diffing\n\nYou can diff one `source` file against many, if `target` is a directory:\n\n```shell\nnpx joey-the-differ -s demo/bulk/sources/1.json -t demo/bulk/targets -c demo/options.js\n```\n\nor many files against one `target` file, if `source` is a directory:\n\n```shell\nnpx joey-the-differ -s demo/bulk/sources -t demo/bulk/targets/1.json -c demo/options.js\n```\n\nor you can diff matching pairs of files if `source` and `target` are directories:\n\n```shell\nnpx joey-the-differ -s demo/bulk/sources -t demo/bulk/targets -c demo/options.js\n```\n\nIn this case, the files with the same name in both `source` and `target` directories will be diffed.\n\n`output` can be either a file or a directory. In case of a directory, for each file matched, a file with the same name will be created.\n\n### Node.js module\n\n```js\nconst JoeyTheDiffer = require('joey-the-differ');\n\nconst currentBookData = {\n  id: 42,\n  title: 'The Prince',\n  author: {\n    name: 'Niccolò',\n    surname: 'Machiavelli',\n    life: {\n      bornOn: '3 May 1469',\n      diedOn: '21 June 1527',\n    },\n  },\n  publishedOn: '1532',\n  reviewsCount: 9614,\n  starsCount: 8562,\n  genres: [{\n    id: 4,\n    name: 'classics',\n  }, {\n    id: 93,\n    name: 'philosophy',\n  }],\n};\n\nconst newBookData = {\n  id: 42,\n  title: 'The Prince',\n  author: {\n    name: 'Niccolò',\n    surname: 'Machiavelli',\n    life: {\n      diedOn: '21 June 1532',\n      bornIn: 'Firenze',\n    },\n  },\n  publishedOn: 1532,\n  starsCount: null,\n  genres: [{\n    id: 4,\n    name: 'CLASSIC',\n  }, {\n    name: 'PHILOSOPHY',\n    booksCount: 843942,\n  }, {\n    id: 1,\n    name: 'HISTORY',\n  }],\n};\n\nconst options = {\n  allowNewTargetProperties: false,\n  returnPathAsAnArray: false,\n  blacklist: [\n    'reviewsCount',\n    'genres\\\\.(\\\\d+)\\\\.booksCount',\n  ],\n  preprocessors: {\n    starsCount: (source, target) =\u003e ({\n      source: source || 0,\n      target: target || 0,\n    }),\n  },\n  differs: {\n    'starsCount': (source, target) =\u003e ({\n      areEqual: source \u003c= target,\n      meta: {\n        op: 'replace',\n        reason: 'number of stars decreased',\n        delta: target - source,\n      },\n    }),\n    'genres\\\\.(\\\\d+)\\\\.name': (source, target) =\u003e ({\n      areEqual: source.toLowerCase() === target.toLowerCase(),\n      meta: {\n        op: 'replace',\n        reason: 'different genre names in lower case',\n      },\n    }),\n  },\n};\n\nconst joey = new JoeyTheDiffer(options);\n\nconst changes = joey.diff(currentBookData, newBookData);\n\n// or with files:\n\n/*\n  const { JoeyTheFilesDiffer } = JoeyTheDiffer;\n  const joey = new JoeyTheFilesDiffer(options);\n  const [{ changes }] = await joey.diff('./demo/source.json', './demo/target.json');\n*/\n\nconsole.log(changes);\n/*\n[\n  {\n    \"path\": \"author.life.bornOn\",\n    \"source\": \"3 May 1469\",\n    \"meta\": {\n      \"op\": \"remove\",\n      \"reason\": \"value disappeared\"\n    }\n  },\n  {\n    \"path\": \"author.life.diedOn\",\n    \"source\": \"21 June 1527\",\n    \"target\": \"21 June 1532\",\n    \"meta\": {\n      \"op\": \"replace\",\n      \"reason\": \"different strings\"\n    }\n  },\n  {\n    \"path\": \"author.life.bornIn\",\n    \"target\": \"Firenze\",\n    \"meta\": {\n      \"op\": \"add\",\n      \"reason\": \"value appeared\"\n    }\n  },\n  {\n    \"path\": \"publishedOn\",\n    \"source\": \"1532\",\n    \"target\": 1532,\n    \"meta\": {\n      \"op\": \"replace\",\n      \"reason\": \"type changed from \\\"string\\\" to \\\"number\\\"\"\n    }\n  },\n  {\n    \"path\": \"starsCount\",\n    \"source\": 8562,\n    \"target\": null,\n    \"meta\": {\n      \"op\": \"replace\",\n      \"reason\": \"number of stars decreased\",\n      \"delta\": -8562,\n      \"preprocessor\": {\n        \"source\": 8562,\n        \"target\": 0\n      }\n    }\n  },\n  {\n    \"path\": \"genres.0.name\",\n    \"source\": \"classics\",\n    \"target\": \"CLASSIC\",\n    \"meta\": {\n      \"op\": \"replace\",\n      \"reason\": \"different genre names in lower case\"\n    }\n  },\n  {\n    \"path\": \"genres.1.id\",\n    \"source\": 93,\n    \"meta\": {\n      \"op\": \"remove\",\n      \"reason\": \"value disappeared\"\n    }\n  },\n  {\n    \"path\": \"genres.2\",\n    \"target\": {\n      \"id\": 1,\n      \"name\": \"HISTORY\"\n    },\n    \"meta\": {\n      \"op\": \"add\",\n      \"reason\": \"value appeared\"\n    }\n  }\n]\n*/\n```\n\n## 🧬 API\n\n### JoeyTheDiffer\n\n#### constructor options\n\n| Name  | Type  | Default | Description | Example |\n| ---   | ---   | ---     | ---         | ---     |\n| `allowNewTargetProperties` | Boolean | false | To allow or not diffing properties that exist in `target` but not in `source` | |\n| `returnPathAsAnArray` | Boolean | false | To return the path to the changed value as an array in the results (resolves ambiguity when keys contain dots) | |\n| `blacklist` | String[] | [] | An array of regular expressions used to match specific properties identified by their path | `'genres\\\\.(\\\\d+)\\\\.booksCount'` will prevent diffing the `booksCount` property of all the `genres` array elements (objects) |\n| `preprocessors` | Object | {} | Preprocessors, associating a regular expression to a transform function  | See \"Usage\" above |\n| `differs` | Object | {} | Custom differs, associating a regular expression to a diffing function  | See \"Usage\" above |\n| `extendedTypesDiffer` | Function | null | Custom differ for non-JSON types | Receives the same parameters as a any diffing function |\n\n#### diff(source, target)\n\nCompares `source` to `target` by recursively visiting all `source` properties and diffing them with the corresponding properties in `target`.\n\nIf a `blacklist` option is passed, it is used to prevent diffing specific properties identified by their path, in `source` and in `target`.\n\nIf `allowNewTargetProperties` is set to `true`, the properties that exist in `target` but not in `source` won't appear in the changes.\n\nIf custom differs are passed, they are used to compare the `source` and `target` properties matched by the regular expressions provided.\n\nIf preprocessors are passed, they act prior to diffing, to transform the `source` and `target` values matched by the regular expressions provided.\n\nAll JSON primitive values will be compared using strict equality (`===`).\n\n```js\nconst changes = joey.diff(source, target);\n```\n\n`changes` is an array of differences where each element is like:\n\n```js\n{\n  path: 'path.to.value',\n  source: 'source value',\n  target: 'target value',\n  meta: {\n    op: 'the operation that happened on the value: add, remove, or replace',\n    reason: 'an explanation of why the source and target values are not equal',\n    preprocessor: {\n      source: 'source value after preprocessing',\n      target: 'target value after preprocessing',\n    },\n    // ...\n    // and any other value returned by your custom differs or by Joey in the future\n  },\n}\n```\n\n### JoeyTheFilesDiffer\n\n#### constructor options\n\nSame as `JoeyTheDiffer`.\n\n#### async diff(sourcePath, targetPath, optionalOutputPath)\n\n```js\nconst { JoeyTheFilesDiffer } = require('joey-the-differ');\n\nconst joey = new JoeyTheFilesDiffer(options);\n\nconst results = await joey.diff(sourcePath, targetPath, optionalOutputPath);\n```\n\n`results` is an array of objects like:\n\n```js\n[\n  {\n    source: 'path to the source file',\n    target: 'path to the target file',\n    changes: [\n      // see above\n    ],\n  },\n  {\n    // ...\n  },\n]\n```\n\nYou can diff:\n\n- one `source` file against many, if `target` is a directory\n- many source files against one `target`, if `source` is a directory\n- matching pairs of files if `source` and `target` are directories (the files with the same names in both `source` and `target` will be diffed)\n\n`optionalOutputPath` can be either a file or a directory. In case of a directory, for each file matched, a file with the same name will be created. For diffing one file against one file, it must be a file.\n\n## 🧬 Contribute\n\n- Fork: `git clone https://github.com/mawrkus/joey-the-differ.git`\n- Create your feature branch: `git checkout -b feature/my-new-feature`\n- Commit your changes: `git commit -am 'Added some feature'`\n- Check the test: `npm run test`\n- Push to the branch: `git push origin my-new-feature`\n- Submit a pull request :D\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmawrkus%2Fjoey-the-differ","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmawrkus%2Fjoey-the-differ","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmawrkus%2Fjoey-the-differ/lists"}