package/package.json000664 001750 001750 0000002562 13173001355013024 0ustar00000000 000000 { "name": "json-schema-compare", "version": "0.2.2", "description": "Compare json schemas smarter.", "main": "src/index.js", "scripts": { "eslint": "eslint src test", "test": "npm run eslint && nyc --reporter=html --reporter=text mocha test/specs", "develop": "mocha test/specs --recursive --watch", "coverage": "nyc report --reporter=text-lcov | coveralls" }, "directories": { "lib": "src", "test": "test" }, "repository": { "type": "git", "url": "git+https://github.com/mokkabonna/json-schema-compare.git" }, "keywords": [ "json", "schema", "jsonschema", "json-schema", "comparison" ], "author": "Martin Hansen", "license": "MIT", "bugs": { "url": "https://github.com/mokkabonna/json-schema-compare/issues" }, "homepage": "https://github.com/mokkabonna/json-schema-compare#readme", "devDependencies": { "chai": "^4.1.2", "coveralls": "^3.0.0", "eslint": "^4.8.0", "eslint-config": "^0.3.0", "eslint-config-standard": "^10.2.1", "eslint-plugin-import": "^2.7.0", "eslint-plugin-node": "^5.2.0", "eslint-plugin-promise": "^3.5.0", "eslint-plugin-standard": "^3.0.1", "json-schema-ref-parser": "^3.3.1", "json-stringify-safe": "^5.0.1", "mocha": "^4.0.1", "nyc": "^11.2.1", "sinon": "^4.0.1" }, "dependencies": { "lodash": "^4.17.4" } } package/.npmignore000664 001750 001750 0000000042 13166374463012543 0ustar00000000 000000 node_modules .nyc_output coverage package/README.md000664 001750 001750 0000003310 13172734316012016 0ustar00000000 000000 # json-schema-compare [![Build Status](https://travis-ci.org/mokkabonna/json-schema-compare.svg?branch=master)](https://travis-ci.org/mokkabonna/json-schema-compare) [![Coverage Status](https://coveralls.io/repos/github/mokkabonna/json-schema-compare/badge.svg?branch=master)](https://coveralls.io/github/mokkabonna/json-schema-compare?branch=master) > Compare json schemas correctly ```bash npm install json-schema-compare --save ``` ```js var compare = require('json-schema-compare') var isEqual = compare({ title: 'title 1', type: ['object'], uniqueItems: false, dependencies: { name: ['age', 'lastName'] }, required: ['name', 'age', 'name'] }, { title: 'title 2', type: 'object', required: ['age', 'name'], dependencies: { name: ['lastName', 'age'] }, properties: { name: { minLength: 0 } } }, { ignore: ['title'] }) console.log(isEqual) // => true ``` Compare json schemas correctly. - Ignores sort for arrays where sort does not matter, like required, enum, type, anyOf, oneOf, anyOf, dependencies (if array) - Compares correctly type when array or string - Ignores duplicate values before comparing - For schemas and sub schemas `undefined`, `true` and `{}` are equal - For minLength, minItems and minProperties `undefined` and `0` are equal - For uniqueItems, `undefined` and `false` are equal ## Options **ignore** array - default: `[]` Ignores certain keywords, useful to exclude meta keywords like title, description etc or custom keywords. If all you want to know if they are the same in terms of validation keywords. ## Contributing Create tests for new functionality and follow the eslint rules. ## License MIT © [Martin Hansen](http://martinhansen.com) package/LICENSE000664 001750 001750 0000002056 13172726030011544 0ustar00000000 000000 MIT License Copyright (c) 2017 Martin Hansen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/.eslintrc.js000664 001750 001750 0000000336 13166374463013011 0ustar00000000 000000 module.exports = { extends: 'standard', rules: { 'space-before-function-paren': ['error', { anonymous: 'ignore', named: 'ignore', asyncArrow: 'ignore' }] } } package/.editorconfig000664 001750 001750 0000000301 13166374463013217 0ustar00000000 000000 root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 package/src/index.js000664 001750 001750 0000011600 13173001223012755 0ustar00000000 000000 var isEqual = require('lodash/isEqual') var sortBy = require('lodash/sortBy') var uniq = require('lodash/uniq') var uniqWith = require('lodash/uniqWith') var defaults = require('lodash/defaults') var intersectionWith = require('lodash/intersectionWith') var isPlainObject = require('lodash/isPlainObject') var isBoolean = require('lodash/isBoolean') var normalizeArray = val => Array.isArray(val) ? val : [val] var undef = val => val === undefined var keys = obj => isPlainObject(obj) || Array.isArray(obj) ? Object.keys(obj) : [] var has = (obj, key) => obj.hasOwnProperty(key) var stringArray = arr => sortBy(uniq(arr)) var undefEmpty = val => undef(val) || (Array.isArray(val) && val.length === 0) var keyValEqual = (a, b, key, compare) => b && has(b, key) && a && has(a, key) && compare(a[key], b[key]) var undefAndZero = (a, b) => (undef(a) && b === 0) || (undef(b) && a === 0) || isEqual(a, b) var falseUndefined = (a, b) => (undef(a) && b === false) || (undef(b) && a === false) || isEqual(a, b) var emptySchema = schema => undef(schema) || isEqual(schema, {}) || schema === true var emptyObjUndef = schema => undef(schema) || isEqual(schema, {}) var isSchema = val => undef(val) || isPlainObject(val) || val === true || val === false function undefArrayEqual(a, b) { if (undefEmpty(a) && undefEmpty(b)) { return true } else { return isEqual(stringArray(a), stringArray(b)) } } function unsortedNormalizedArray(a, b) { a = normalizeArray(a) b = normalizeArray(b) return isEqual(stringArray(a), stringArray(b)) } function schemaGroup(a, b, key, compare) { var allProps = uniq(keys(a).concat(keys(b))) if (emptyObjUndef(a) && emptyObjUndef(b)) { return true } else if (emptyObjUndef(a) && keys(b).length) { return false } else if (emptyObjUndef(b) && keys(a).length) { return false } return allProps.every(function(key) { var aVal = a[key] var bVal = b[key] if (Array.isArray(aVal) && Array.isArray(bVal)) { return isEqual(stringArray(a), stringArray(b)) } else if (Array.isArray(aVal) && !Array.isArray(bVal)) { return false } else if (Array.isArray(bVal) && !Array.isArray(aVal)) { return false } return keyValEqual(a, b, key, compare) }) } function items(a, b, key, compare) { if (isPlainObject(a) && isPlainObject(b)) { return compare(a, b) } else if (Array.isArray(a) && Array.isArray(b)) { return schemaGroup(a, b, key, compare) } else { return isEqual(a, b) } } function unsortedArray(a, b, key, compare) { var uniqueA = uniqWith(a, compare) var uniqueB = uniqWith(b, compare) var inter = intersectionWith(uniqueA, uniqueB, compare) return inter.length === Math.max(uniqueA.length, uniqueB.length) } var comparers = { title: isEqual, uniqueItems: falseUndefined, minLength: undefAndZero, minItems: undefAndZero, minProperties: undefAndZero, required: undefArrayEqual, enum: undefArrayEqual, type: unsortedNormalizedArray, items: items, anyOf: unsortedArray, allOf: unsortedArray, oneOf: unsortedArray, properties: schemaGroup, patternProperties: schemaGroup, dependencies: schemaGroup } var acceptsUndefined = [ 'properties', 'patternProperties', 'dependencies', 'uniqueItems', 'minLength', 'minItems', 'minProperties', 'required' ] var schemaProps = ['additionalProperties', 'additionalItems', 'contains', 'propertyNames', 'not'] function compare(a, b, options) { options = defaults(options, { ignore: [] }) if (emptySchema(a) && emptySchema(b)) { return true } if (!isSchema(a) || !isSchema(b)) { throw new Error('Either of the values are not a JSON schema.') } if (a === b) { return true } if (isBoolean(a) && isBoolean(b)) { return a === b } if ((a === undefined && b === false) || (b === undefined && a === false)) { return false } if ((undef(a) && !undef(b)) || (!undef(a) && undef(b))) { return false } var allKeys = uniq(Object.keys(a).concat(Object.keys(b))) if (options.ignore.length) { allKeys = allKeys.filter(k => options.ignore.indexOf(k) === -1) } if (!allKeys.length) { return true } function innerCompare(a, b) { return compare(a, b, options) } return allKeys.every(function(key) { var aValue = a[key] var bValue = b[key] if (schemaProps.indexOf(key) !== -1) { return compare(aValue, bValue, options) } var comparer = comparers[key] if (!comparer) { comparer = isEqual } // do simple lodash check first if (isEqual(aValue, bValue)) { return true } if (acceptsUndefined.indexOf(key) === -1) { if ((!has(a, key) && has(b, key)) || (has(a, key) && !has(b, key))) { return aValue === bValue } } var result = comparer(aValue, bValue, key, innerCompare) if (!isBoolean(result)) { throw new Error('Comparer must return true or false') } return result }) } module.exports = compare package/.travis.yml000664 001750 001750 0000000103 13166374463012653 0ustar00000000 000000 language: node_js after_success: npm run coverage node_js: - '8' package/test/.eslintrc000664 001750 001750 0000000222 13166375163013345 0ustar00000000 000000 { "globals": { "describe": true, "it": true, "beforeEach": true, "afterEach": true, "before": true, "after": true } } package/test/specs/index.spec.js000664 001750 001750 0000020107 13172732130015224 0ustar00000000 000000 var chai = require('chai') var compareModule = require('../../src') var expect = chai.expect var compare = function(a, b, expected, options) { var result = compareModule(a, b, options) expect(result).to.equal(expected) } describe('comparison', function() { describe('validation only', function() { it('checks the readme example', function() { compare({ title: 'title 1', type: ['object'], uniqueItems: false, dependencies: { name: ['age', 'lastName'] }, required: ['name', 'age', 'name'] }, { title: 'title 2', type: 'object', required: ['age', 'name'], dependencies: { name: ['lastName', 'age'] }, properties: { name: { minLength: 0 } } }, false, { ignore: ['title'] }) }) it('compares false and undefined', function() { compare(undefined, false, false) }) it('compares required unsorted', function() { compare({ required: ['test', 'rest'] }, { required: ['rest', 'test', 'rest'] }, true) }) it('compares equal required empty array and undefined', function() { compare({ required: [] }, {}, true) compare({ required: ['fds'] }, {}, false) }) it('compares equal properties empty object and undefined', function() { compare({ properties: {} }, {}, true) }) it('compares properties', function() { compare({ properties: { foo: { type: 'string' } } }, { properties: { foo: { type: 'string' } } }, true) }) it('compares equal patternProperties empty object and undefined', function() { compare({ patternProperties: {} }, {}, true) }) it('compares equal dependencies empty object and undefined', function() { compare({ dependencies: {} }, {}, true) }) it('compares type unsorted', function() { compare({ type: ['string', 'array'] }, { type: ['array', 'string', 'array'] }, true) compare({}, { type: [] }, false) compare({ type: 'string' }, { type: ['string'] }, true) }) it('compares equal an empty schema, true and undefined', function() { compare({}, true, true) compare({}, undefined, true) compare(false, false, true) compare(true, true, true) }) it('ignores any in ignore list', function() { compare({ title: 'title' }, { title: 'foobar' }, true, {ignore: ['title']}) }) it('diffs this', function() { compare({ type: ['string'], minLength: 5 }, { type: ['string'] }, false) }) it('sorts anyOf before comparing', function() { compare({ anyOf: [ { type: 'string' }, { type: 'integer' } ] }, { anyOf: [ { type: 'integer' }, { type: 'string' } ] }, true) compare({ anyOf: [ { type: 'string' }, { type: 'integer' } ] }, { anyOf: [ { type: 'integer' }, { type: 'string' }, { type: ['string'], minLength: 5, fdsafads: '34534' } ] }, false) compare({ anyOf: [ { type: 'string' }, { type: 'integer' } ] }, { anyOf: [ { type: 'integer' }, { type: 'array' } ] }, false) compare({ anyOf: [ { type: 'string' }, { type: ['string'] }, { type: 'integer' } ] }, { anyOf: [ { type: 'integer' }, { type: 'string' } ] }, true) }) it('sorts allOf before comparing', function() { compare({ allOf: [ { type: 'string' }, { type: 'integer' } ] }, { allOf: [ { type: 'integer' }, { type: 'string' } ] }, true) compare({ allOf: [ { type: 'string' }, { type: 'integer' } ] }, { allOf: [ { type: 'integer' }, { type: 'string' }, { type: ['string'], minLength: 5, fdsafads: '34534' } ] }, false) compare({ allOf: [ { type: 'string' }, { type: 'integer' } ] }, { allOf: [ { type: 'integer' }, { type: 'array' } ] }, false) compare({ allOf: [ { type: 'string' }, { type: ['string'] }, { type: 'integer' } ] }, { allOf: [ { type: 'integer' }, { type: 'string' } ] }, true) }) it('sorts oneOf before comparing', function() { compare({ oneOf: [ { type: 'string' }, { type: 'integer' } ] }, { oneOf: [ { type: 'integer' }, { type: 'string' } ] }, true) compare({ oneOf: [ { type: 'string' }, { type: 'integer' } ] }, { oneOf: [ { type: 'integer' }, { type: 'string' }, { type: ['string'], minLength: 5, fdsafads: '34534' } ] }, false) compare({ oneOf: [ { type: 'string' }, { type: 'integer' } ] }, { oneOf: [ { type: 'integer' }, { type: 'array' } ] }, false) compare({ oneOf: [ { type: 'string' }, { type: ['string'] }, { type: 'integer' } ] }, { oneOf: [ { type: 'integer' }, { type: 'string' } ] }, true) }) it('compares enum unsorted', function() { compare({ enum: ['abc', '123'] }, { enum: ['123', 'abc', 'abc'] }, true) }) it('compares dependencies value if array unsorted', function() { compare({ dependencies: { foo: ['abc', '123'] } }, { dependencies: { foo: ['123', 'abc', 'abc'] } }, true) }) it('compares items SORTED', function() { compare({ items: [true, false] }, { items: [true, true] }, false) compare({ items: [{}, false] }, { items: [true, false] }, true) }) it('compares equal uniqueItems false and undefined', function() { compare({ uniqueItems: false }, {}, true) }) it('compares equal minLength undefined and 0', function() { compare({ minLength: 0 }, {}, true) }) it('compares equal minItems undefined and 0', function() { compare({ minItems: 0 }, {}, true) }) it('compares equal minProperties undefined and 0', function() { compare({ minProperties: 0 }, {}, true) }) }) describe('complete', function() { it('includes all properties, like title') }) })