├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── doc
├── basic.png
├── examples.js
└── mapper.png
├── index.js
├── package.json
└── test
├── .eslintrc.json
└── test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [package.json]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "@satazor/eslint-config/es5",
5 | "@satazor/eslint-config/addons/node"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.*
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /screenshots
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 | - "4"
5 | - "5"
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 IndigoUnited
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # diff-json-structure
2 |
3 | [![NPM version][npm-image]][npm-url] [![Downloads][downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency status][david-dm-image]][david-dm-url] [![Dev Dependency status][david-dm-dev-image]][david-dm-dev-url] [![Greenkeeper badge][greenkeeper-image]][greenkeeper-url]
4 |
5 |
6 | [npm-url]:https://npmjs.org/package/diff-json-structure
7 | [downloads-image]:http://img.shields.io/npm/dm/diff-json-structure.svg
8 | [npm-image]:http://img.shields.io/npm/v/diff-json-structure.svg
9 | [travis-url]:https://travis-ci.org/IndigoUnited/node-diff-json-structure
10 | [travis-image]:http://img.shields.io/travis/IndigoUnited/node-diff-json-structure/master.svg
11 | [david-dm-url]:https://david-dm.org/IndigoUnited/node-diff-json-structure
12 | [david-dm-image]:https://img.shields.io/david/IndigoUnited/node-diff-json-structure.svg
13 | [david-dm-dev-url]:https://david-dm.org/IndigoUnited/node-diff-json-structure?type=dev
14 | [david-dm-dev-image]:https://img.shields.io/david/dev/IndigoUnited/node-diff-json-structure.svg
15 | [greenkeeper-image]:https://badges.greenkeeper.io/IndigoUnited/node-diff-json-structure.svg
16 | [greenkeeper-url]:https://greenkeeper.io/
17 |
18 | Get the structural diff of two JSON objects, using [diff](https://www.npmjs.com/package/diff)'s internally which is a module used by several test frameworks.
19 |
20 |
21 | It is considered a structural difference whenever:
22 |
23 | - items are added or removed to objects and arrays
24 | - the type of the item changes
25 |
26 |
27 | ## Installation
28 |
29 | `$ npm install diff-json-structure`
30 |
31 |
32 | ## Usage
33 |
34 | `diff(oldObj, newObj, [options])`
35 |
36 | Calculates the structural diff between `oldObj` and `newObj`, returning an array of parts.
37 |
38 | Available options:
39 |
40 | - typeMapper - A function that lets you override types for specific paths
41 | - .. and any option that [diff](https://www.npmjs.com/package/diff)'s `.diffJson()` method supports
42 |
43 |
44 | ### Examples
45 |
46 | Simple usage:
47 |
48 | ```js
49 | var diff = require('diff-json-structure');
50 | var chalk = require('chalk');
51 |
52 | // Utility function to visually print the diff
53 | // Tweak it at your own taste
54 | function printDiff(parts) {
55 | parts.forEach(function (part) {
56 | part.value
57 | .split('\n')
58 | .filter(function (line) { return !!line; })
59 | .forEach(function (line) {
60 | if (part.added) {
61 | process.stdout.write(chalk.green('+ ' + line) + '\n');
62 | } else if (part.removed) {
63 | process.stdout.write(chalk.red('- ' + line) + '\n');
64 | } else {
65 | process.stdout.write(chalk.dim(' ' + line) + '\n');
66 | }
67 | });
68 | });
69 |
70 | process.stdout.write('\n');
71 | }
72 |
73 | var oldObject = {
74 | environment: 'dev',
75 | googleAppId: 'UA-3234432-22',
76 | socialProviders: ['facebook'],
77 | libraries: {
78 | jquery: './node_modules/jquery',
79 | },
80 | };
81 |
82 | var newObj = {
83 | environment: 'prod',
84 | dbHost: '127.0.0.1:9000',
85 | socialProviders: ['facebook', 'twitter'],
86 | libraries: {
87 | jquery: './node_modules/jquery/jquery',
88 | moment: './node_modules/moment/moment',
89 | },
90 | };
91 |
92 | printDiff(diff(oldObj, newObj));
93 | ```
94 |
95 |
96 |
97 |
98 | Usage with `options.typeMapper` to ignore differences of socialProvider items of the previous example:
99 |
100 | ```js
101 | printDiff(diff(oldObj, newObj, {
102 | typeMapper: function (path, value, prop, subject) {
103 | // path is a string that contains the full path to this value
104 | // e.g.: 'libraries.jquery' and 'socialProviders[0]'
105 |
106 | // You may return custom types here.. if nothing is returned, the normal
107 | // flow of identifying the structure recursively will continue
108 | if (path === 'socialProviders') {
109 | return 'array';
110 | }
111 | },
112 | }));
113 | ```
114 |
115 |
116 |
117 |
118 | ## Tests
119 |
120 | `$ npm test`
121 |
122 |
123 | ## License
124 |
125 | Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
126 |
--------------------------------------------------------------------------------
/doc/basic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoUnited/node-diff-json-structure/71c4d10b4fb236fc09eb534d4dfe5042ea0fed44/doc/basic.png
--------------------------------------------------------------------------------
/doc/examples.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var diff = require('../');
4 | var chalk = require('chalk');
5 |
6 | var oldObj;
7 | var newObj;
8 |
9 | function printDiff(parts) {
10 | parts.forEach(function (part) {
11 | part.value
12 | .split('\n')
13 | .filter(function (line) { return !!line; })
14 | .forEach(function (line) {
15 | if (part.added) {
16 | process.stdout.write(chalk.green('+ ' + line) + '\n');
17 | } else if (part.removed) {
18 | process.stdout.write(chalk.red('- ' + line) + '\n');
19 | } else {
20 | process.stdout.write(chalk.dim(' ' + line) + '\n');
21 | }
22 | });
23 | });
24 |
25 | process.stdout.write('\n');
26 | }
27 |
28 | oldObj = {
29 | environment: 'dev',
30 | googleAppId: 'UA-3234432-22',
31 | socialProviders: ['facebook'],
32 | libraries: {
33 | jquery: './node_modules/jquery',
34 | },
35 | };
36 |
37 | newObj = {
38 | environment: 'prod',
39 | dbHost: '127.0.0.1:9000',
40 | socialProviders: ['facebook', 'twitter'],
41 | libraries: {
42 | jquery: './node_modules/jquery/jquery',
43 | moment: './node_modules/moment/moment',
44 | },
45 | };
46 |
47 | printDiff(diff(oldObj, newObj));
48 | printDiff(diff(oldObj, newObj, {
49 | typeMapper: function (path) {
50 | if (path === 'socialProviders') {
51 | return 'array';
52 | }
53 | },
54 | }));
55 |
--------------------------------------------------------------------------------
/doc/mapper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/IndigoUnited/node-diff-json-structure/71c4d10b4fb236fc09eb534d4dfe5042ea0fed44/doc/mapper.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var typeOf = require('typeof');
4 | var cloneDeep = require('lodash.clonedeep');
5 | var assign = require('lodash.assign');
6 | var isPlainObject = require('is-plain-object');
7 | var forEach = require('deep-for-each');
8 | var jsdiff = require('diff');
9 |
10 | function mapToType(value, options) {
11 | options = assign({
12 | typeMapper: null,
13 | }, options);
14 |
15 | value = cloneDeep(value);
16 |
17 | forEach(value, function (value, prop, subject, path) {
18 | var type = options.typeMapper && options.typeMapper(path, value, prop, subject);
19 |
20 | if (type) {
21 | subject[prop] = '<' + type + '>';
22 | } else if (!Array.isArray(value) && !isPlainObject(value)) {
23 | subject[prop] = '<' + typeOf(value) + '>';
24 | }
25 | });
26 |
27 | return value;
28 | }
29 |
30 | function diff(oldObj, newObj, options) {
31 | oldObj = mapToType(oldObj, options);
32 | newObj = mapToType(newObj, options);
33 |
34 | return jsdiff.diffJson(oldObj, newObj, options);
35 | }
36 |
37 | module.exports = diff;
38 | module.exports.mapToType = mapToType;
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "diff-json-structure",
3 | "version": "1.0.8",
4 | "description": "Get the structural diff of two JSON objects",
5 | "main": "index.js",
6 | "scripts": {
7 | "lint": "eslint '{*.js,test/**/*.js}'",
8 | "test": "mocha --bail"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/IndigoUnited/node-diff-json-structure/issues/"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/IndigoUnited/node-diff-json-structure.git"
16 | },
17 | "keywords": [
18 | "diff",
19 | "json",
20 | "structure",
21 | "structural"
22 | ],
23 | "author": "IndigoUnited (http://indigounited.com)",
24 | "license": "MIT",
25 | "devDependencies": {
26 | "@satazor/eslint-config": "^3.0.0",
27 | "chalk": "^2.0.0",
28 | "deep-filter": "^1.0.0",
29 | "eslint": "^3.0.0",
30 | "expect.js": "^0.3.1",
31 | "mocha": "^3.0.2"
32 | },
33 | "dependencies": {
34 | "deep-for-each": "^1.0.0",
35 | "diff": "^3.0.0",
36 | "is-plain-object": "^2.0.1",
37 | "lodash.assign": "^4.0.0",
38 | "lodash.clonedeep": "^4.0.1",
39 | "typeof": "^1.0.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/test/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | }
5 | }
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var expect = require('expect.js');
4 | var diff = require('../');
5 |
6 | describe('diff-json-structure', function () {
7 | describe('map-to-type', function () {
8 | it('should deep clone stuff during the process', function () {
9 | var obj = {
10 | some: 'prop',
11 | array: [1, 2, { other: 'prop' }],
12 | nested: {
13 | foo: 'bar',
14 | },
15 | regexp: /foo/,
16 | bool: true,
17 | null: null,
18 | undefined: undefined,
19 | };
20 |
21 | diff.mapToType(obj);
22 |
23 | expect(obj.some).to.be('prop');
24 | expect(obj.array).to.eql([1, 2, { other: 'prop' }]);
25 | });
26 |
27 | it('should correctly map values to types', function () {
28 | var obj = {
29 | some: 'prop',
30 | array: [1, 2, { other: 'prop' }],
31 | nested: {
32 | foo: 'bar',
33 | },
34 | regexp: /foo/,
35 | bool: true,
36 | null: null,
37 | undefined: undefined,
38 | };
39 |
40 | expect(diff.mapToType(obj)).to.eql({
41 | some: '',
42 | array: ['', '', { other: '' }],
43 | nested: {
44 | foo: '',
45 | },
46 | regexp: '',
47 | bool: '',
48 | null: '',
49 | undefined: '',
50 | });
51 | });
52 |
53 | it('should respect typeMapper option', function () {
54 | var obj = {
55 | some: 'prop',
56 | array: [1, 2, { other: 'prop' }],
57 | nested: {
58 | foo: 'bar',
59 | },
60 | regexp: /foo/,
61 | bool: true,
62 | null: null,
63 | undefined: undefined,
64 | };
65 |
66 | expect(diff.mapToType(obj, {
67 | typeMapper: function (path) {
68 | switch (path) {
69 | case 'array[2]':
70 | return 'object';
71 | case 'nested.foo':
72 | return 'any';
73 | default:
74 | }
75 | },
76 | })).to.eql({
77 | some: '',
78 | array: ['', '', '