npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

joey-the-differ

v2.1.0

Published

JSON diffing on steroids

Downloads

41

Readme

Joey the Differ

JSON diffing on steroids.

🧬 Features

  • Custom differs
  • Blacklist
  • Preprocessors
  • Bulk files processing
  • CLI-friendly
  • Docker-ready

🧬 Installation

npm install joey-the-differ

🧬 Usage

Command line

Usage: joey-the-differ [options]

Options:
  -V, --version        output the version number
  -s, --source [file]  source file or directory, required
  -t, --target [file]  target file or directory, required
  -o, --output [file]  output file or directory, optional
  -c, --config [file]  config file (JS), optional
  -v, --verbose        verbose mode, optional
  -h, --help           display help for command

For instance, using npx:

npx joey-the-differ -s demo/source.json -t demo/target.json -c demo/options.js

or using Docker:

docker build . -t mawrkus/joey-the-differ
docker run -v ${PWD}:/tmp mawrkus/joey-the-differ -s /tmp/demo/source.json -t /tmp/demo/target.json -c /tmp/demo/options.js

Have a look at the demo folder to see the content of the files.

Bulk diffing

You can diff one source file against many, if target is a directory:

npx joey-the-differ -s demo/bulk/sources/1.json -t demo/bulk/targets -c demo/options.js

or many files against one target file, if source is a directory:

npx joey-the-differ -s demo/bulk/sources -t demo/bulk/targets/1.json -c demo/options.js

or you can diff matching pairs of files if source and target are directories:

npx joey-the-differ -s demo/bulk/sources -t demo/bulk/targets -c demo/options.js

In this case, the files with the same name in both source and target directories will be diffed.

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.

Node.js module

const JoeyTheDiffer = require('joey-the-differ');

const currentBookData = {
  id: 42,
  title: 'The Prince',
  author: {
    name: 'Niccolò',
    surname: 'Machiavelli',
    life: {
      bornOn: '3 May 1469',
      diedOn: '21 June 1527',
    },
  },
  publishedOn: '1532',
  reviewsCount: 9614,
  starsCount: 8562,
  genres: [{
    id: 4,
    name: 'classics',
  }, {
    id: 93,
    name: 'philosophy',
  }],
};

const newBookData = {
  id: 42,
  title: 'The Prince',
  author: {
    name: 'Niccolò',
    surname: 'Machiavelli',
    life: {
      diedOn: '21 June 1532',
      bornIn: 'Firenze',
    },
  },
  publishedOn: 1532,
  starsCount: null,
  genres: [{
    id: 4,
    name: 'CLASSIC',
  }, {
    name: 'PHILOSOPHY',
    booksCount: 843942,
  }, {
    id: 1,
    name: 'HISTORY',
  }],
};

const options = {
  allowNewTargetProperties: false,
  returnPathAsAnArray: false,
  blacklist: [
    'reviewsCount',
    'genres\\.(\\d+)\\.booksCount',
  ],
  preprocessors: {
    starsCount: (source, target) => ({
      source: source || 0,
      target: target || 0,
    }),
  },
  differs: {
    'starsCount': (source, target) => ({
      areEqual: source <= target,
      meta: {
        op: 'replace',
        reason: 'number of stars decreased',
        delta: target - source,
      },
    }),
    'genres\\.(\\d+)\\.name': (source, target) => ({
      areEqual: source.toLowerCase() === target.toLowerCase(),
      meta: {
        op: 'replace',
        reason: 'different genre names in lower case',
      },
    }),
  },
};

const joey = new JoeyTheDiffer(options);

const changes = joey.diff(currentBookData, newBookData);

// or with files:

/*
  const { JoeyTheFilesDiffer } = JoeyTheDiffer;
  const joey = new JoeyTheFilesDiffer(options);
  const [{ changes }] = await joey.diff('./demo/source.json', './demo/target.json');
*/

console.log(changes);
/*
[
  {
    "path": "author.life.bornOn",
    "source": "3 May 1469",
    "meta": {
      "op": "remove",
      "reason": "value disappeared"
    }
  },
  {
    "path": "author.life.diedOn",
    "source": "21 June 1527",
    "target": "21 June 1532",
    "meta": {
      "op": "replace",
      "reason": "different strings"
    }
  },
  {
    "path": "author.life.bornIn",
    "target": "Firenze",
    "meta": {
      "op": "add",
      "reason": "value appeared"
    }
  },
  {
    "path": "publishedOn",
    "source": "1532",
    "target": 1532,
    "meta": {
      "op": "replace",
      "reason": "type changed from \"string\" to \"number\""
    }
  },
  {
    "path": "starsCount",
    "source": 8562,
    "target": null,
    "meta": {
      "op": "replace",
      "reason": "number of stars decreased",
      "delta": -8562,
      "preprocessor": {
        "source": 8562,
        "target": 0
      }
    }
  },
  {
    "path": "genres.0.name",
    "source": "classics",
    "target": "CLASSIC",
    "meta": {
      "op": "replace",
      "reason": "different genre names in lower case"
    }
  },
  {
    "path": "genres.1.id",
    "source": 93,
    "meta": {
      "op": "remove",
      "reason": "value disappeared"
    }
  },
  {
    "path": "genres.2",
    "target": {
      "id": 1,
      "name": "HISTORY"
    },
    "meta": {
      "op": "add",
      "reason": "value appeared"
    }
  }
]
*/

🧬 API

JoeyTheDiffer

constructor options

| Name | Type | Default | Description | Example | | --- | --- | --- | --- | --- | | allowNewTargetProperties | Boolean | false | To allow or not diffing properties that exist in target but not in source | | | returnPathAsAnArray | Boolean | false | To return the path to the changed value as an array in the results (resolves ambiguity when keys contain dots) | | | 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) | | preprocessors | Object | {} | Preprocessors, associating a regular expression to a transform function | See "Usage" above | | differs | Object | {} | Custom differs, associating a regular expression to a diffing function | See "Usage" above | | extendedTypesDiffer | Function | null | Custom differ for non-JSON types | Receives the same parameters as a any diffing function |

diff(source, target)

Compares source to target by recursively visiting all source properties and diffing them with the corresponding properties in target.

If a blacklist option is passed, it is used to prevent diffing specific properties identified by their path, in source and in target.

If allowNewTargetProperties is set to true, the properties that exist in target but not in source won't appear in the changes.

If custom differs are passed, they are used to compare the source and target properties matched by the regular expressions provided.

If preprocessors are passed, they act prior to diffing, to transform the source and target values matched by the regular expressions provided.

All JSON primitive values will be compared using strict equality (===).

const changes = joey.diff(source, target);

changes is an array of differences where each element is like:

{
  path: 'path.to.value',
  source: 'source value',
  target: 'target value',
  meta: {
    op: 'the operation that happened on the value: add, remove, or replace',
    reason: 'an explanation of why the source and target values are not equal',
    preprocessor: {
      source: 'source value after preprocessing',
      target: 'target value after preprocessing',
    },
    // ...
    // and any other value returned by your custom differs or by Joey in the future
  },
}

JoeyTheFilesDiffer

constructor options

Same as JoeyTheDiffer.

async diff(sourcePath, targetPath, optionalOutputPath)

const { JoeyTheFilesDiffer } = require('joey-the-differ');

const joey = new JoeyTheFilesDiffer(options);

const results = await joey.diff(sourcePath, targetPath, optionalOutputPath);

results is an array of objects like:

[
  {
    source: 'path to the source file',
    target: 'path to the target file',
    changes: [
      // see above
    ],
  },
  {
    // ...
  },
]

You can diff:

  • one source file against many, if target is a directory
  • many source files against one target, if source is a directory
  • matching pairs of files if source and target are directories (the files with the same names in both source and target will be diffed)

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.

🧬 Contribute

  • Fork: git clone https://github.com/mawrkus/joey-the-differ.git
  • Create your feature branch: git checkout -b feature/my-new-feature
  • Commit your changes: git commit -am 'Added some feature'
  • Check the test: npm run test
  • Push to the branch: git push origin my-new-feature
  • Submit a pull request :D