Skip to content

Instantly share code, notes, and snippets.

@slawrence
Created February 7, 2013 22:09
Show Gist options
  • Save slawrence/4734699 to your computer and use it in GitHub Desktop.
Save slawrence/4734699 to your computer and use it in GitHub Desktop.
A basic "schema" generator/comaparator. Requires underscore for deep compare.
/*global _.isEqual, console, schemanator: true */
/*jslint browser: true */
(function () {
'use strict';
if (window.schemanator) {
console.error("schemanator already defined");
return;
}
/**
An object that can generate a schema and compare objects. The compare method will output
the differences in properties. 'Schemas' can be compared to determine if json service
'contracts' have changed.
Dependencies: console, _.isEqual
*/
window.schemanator = {};
/**
* Is an object enumerable?
*/
function isEnumerable(object) {
var prop;
if (typeof object === 'object') {
if (object) {
for (prop in object) {
if (object.hasOwnProperty(prop)) {
return true;
}
}
}
}
return false;
}
function isEmpty(obj) {
var prop;
for (prop in obj) {
if (obj.hasOwnProperty(prop)) {
return false;
}
}
return true;
}
/**
* Is an object an array?
*/
function isArray(val) {
if (typeof val === 'object') {
if (val) {
if (Object.prototype.toString.call(val) === '[object Array]') {
return true;
}
}
}
return false;
}
function isBothArray(a1, a2) {
return (isArray(a1) && isArray(a2));
}
function isAlreadyDefinedInArray(schema, schema_list) {
var i;
for (i = 0; i < schema_list.length; i += 1) {
if (_.isEqual(schema_list[i], schema)) {
return true;
}
}
return false;
}
/**
* Add a property to a object schema
*/
function addObjectProperty(property, object, schema) {
schema.properties = schema.properties || {};
//add this property to the object of properties
schema.properties[property] = {};
schema.properties[property].id = property;
if (isArray(object)) {
schema.properties[property].type = 'array';
} else {
schema.properties[property].type = typeof object;
}
}
function enumerate(object, schema) {
if (isArray(object)) {
if (object.length) {
enumerateArray(object, schema);
}
} else if (isEnumerable(object)) {
enumerateObject(object, schema);
} else {
schema.type = typeof object;
}
return schema;
}
/**
* Enumerate over an object and create a schema for each property
*/
function enumerateObject(object, schema) {
var prop, prop_schema, type;
if (!object || !schema) {
console.error('Object or Schema is undefined/null');
}
//enumerate over properties
for (prop in object) {
if (object.hasOwnProperty(prop)) {
addObjectProperty(prop, object[prop], schema);
enumerate(object[prop], schema.properties[prop]);
}
}
return schema;
}
// enumerate and add property to a array schema
function enumerateArray(array, schema) {
var i, temp_schema;
if (!array) {
console.error('Array or Schema is undefined/null');
}
//enumerate array properties
schema.items = [];
for (i = 0; i < array.length; i += 1) {
temp_schema = enumerate(array[i], {});
//check to see if other item schemas the same
if (!isAlreadyDefinedInArray(temp_schema, schema.items)) {
temp_schema.id = schema.items.length + "";
if (isArray(array[i])) {
temp_schema.type = 'array';
} else {
temp_schema.type = typeof array[i];
}
schema.items.push(temp_schema);
}
}
}
/**
* Do a deep comparision and return differences between two objects
*/
function compare(o1, o2) {
var prop, diff, ret = {};
if (typeof o1 === 'object' && typeof o2 === 'object') {
for (prop in o2) {
//don't check against o2's prototype properties
if (o2.hasOwnProperty(prop)) {
//see if the property exists in o1
if (o1.hasOwnProperty(prop)) {
//it does, compare values
if (typeof o2[prop] === 'object' && !isArray(o2[prop])) {
diff = compare(o1[prop], o2[prop]);
if (!isEmpty(diff)) {
ret[prop] = diff;
}
} else if (isArray(o1[prop]) && isArray(o2[prop])) {
if (_.isEqual(o1[prop], o2[prop])) {
//TODO: Improve the way arrays are 'compared'
//ret[prop] = "ARRAYS are different";
ret[prop] = o2[prop];
}
} else if (_.isEqual(o1[prop], o2[prop])) {
ret[prop] = o2[prop];
}
} else {
ret[prop] = o2[prop];
}
}
}
}
return ret;
}
/**
* Generate a "schema" from a json object. For now, not checking the prototype
*/
schemanator.generate = function (object) {
var schema = { id: '#' };
return enumerate(object, schema);
};
/**
* Compare two objects, two schemas, or an object and a schema. Objects will generate
* a scheme which is then compared. Since a 'schema' is generated first, this method only
* checks for differences in properties, not in their values.
*/
schemanator.schemaDiff = function (arg1, arg2) {
var schema1, schema2;
//helper function to convert to schema if not already a schema
function prepArgs(arg) {
return (arg.id === '#') ? arg : schemanator.generate(arg);
}
if (arg1 && arg2 && (typeof arg1 === 'object' && typeof arg2 === 'object')) {
return compare(prepArgs(arg1), prepArgs(arg2));
} else {
console.error('Arguments were undefined or were not objects');
}
};
/**
* Output what's different in arg2 compared to arg1
*/
schemanator.diff = function (arg1, arg2) {
return compare(arg1, arg2);
};
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment