Skip to content

Instantly share code, notes, and snippets.

@barahilia
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save barahilia/58e6d5c969d14fb1d36d to your computer and use it in GitHub Desktop.
Save barahilia/58e6d5c969d14fb1d36d to your computer and use it in GitHub Desktop.
JSON pattern matching
Allows match JSON objects by pattern. Intended for APIs comparison and verification.
E.g.:
compare(
{ a: 1, b: ['x', 'y'] }, // pattern
{ a: 42, b: ['first', 'second', 'any', 'last'] } // object for check
);
  • install node.js
  • npm install underscore
  • npm install -g jasmine-node
  • jasmine-node structure.spec.js
// The module intends to compare structure of 2 JSON
// objects. Should be used for API verification
// The function throws on invalid pattern
// First step - return true/false
// Second step - explain how the structures differ
var _ = require("underscore");
var validateArray = function (pattern) {
if(pattern.length === 0) {
throw "Invalid pattern - empty array"
}
_.each(_.tail(pattern), function (elem, index) {
if(! compare(pattern[0], elem)) {
throw "Inconsistency in pattern array at indexes 0 and " + (index+1);
}
});
};
var compareArray = function (elemPattern, obj) {
if(_.isArray(obj)) {
if(obj.length === 0) {
return false;
}
return _.every(obj, function (elem) {
return compare(elemPattern, elem);
});
}
else {
return false;
}
};
var compareObject = function (pattern, obj) {
if(_.isObject(obj)) {
return _.every(_.keys(pattern), function (key) {
if(obj.hasOwnProperty(key)) {
return compare(pattern[key], obj[key]);
}
else {
return false;
}
});
}
else {
return false;
}
};
var compare = function (pattern, obj) {
if(_.isNumber(pattern)) {
return _.isNumber(obj);
}
else if(_.isString(pattern)) {
return _.isString(obj);
}
else if(_.isArray(pattern)) {
validateArray(pattern);
return compareArray(pattern[0], obj);
}
else if(_.isObject(pattern)) {
return compareObject(pattern, obj);
}
throw "Unknown type of pattern: " + (typeof pattern);
};
exports.compare = compare;
var comp = require("./structure").compare;
describe("Comparator", function () {
var prepare = function (a, b) {
return function () {
comp(a, b);
};
};
it("should find equal two numbers", function () {
expect(comp(1, 2)).toBeTruthy();
});
it("should differ number and string", function () {
expect(comp(1, "aa")).toBeFalsy();
});
it("should differ string and number", function () {
expect(comp("aa", 1)).toBeFalsy();
});
it("should find equal two strings", function () {
expect(comp("aa", "bb")).toBeTruthy();
});
it("should differ number and object", function () {
expect(comp(1, { })).toBeFalsy();
});
it("should differ object and number", function () {
expect(comp({ }, 1)).toBeFalsy();
});
it("should find equal two objects", function () {
expect(comp({ }, { })).toBeTruthy();
});
it("should differ objects with different keys", function () {
expect(comp({ a: 1 }, { b: 1 })).toBeFalsy();
});
it("should find equal objects with same keys", function () {
expect(comp({ a: 1 }, { a: 2 })).toBeTruthy();
});
it("should find equal object whose keys are a superset of pattern's keys", function () {
expect(comp({ a: 1 }, { a: 2, b: 1 })).toBeTruthy();
expect(comp({ a: 1, b: 2 }, { a: 2, c: 1, b: 1 })).toBeTruthy();
expect(comp({ }, { a: 2, b: 1 })).toBeTruthy();
});
it("should find equal two arrays of numbers", function () {
expect(comp([ 1 ], [ 2 ])).toBeTruthy();
expect(comp([ 1, 3 ], [ 2 ])).toBeTruthy();
expect(comp([ 1, 3 ], [ 2, 4, 6, 8, 10 ])).toBeTruthy();
expect(comp([ 1, 3 ], [ 1, 2, 3 ])).toBeTruthy();
});
it("should throw on empty pattern array", function () {
expect(prepare([ ], [ ])).toThrow();
expect(prepare([ ], [ 2 ])).toThrow();
expect(prepare([ ], [ 2, 4, 6, 8, 10 ])).toThrow();
});
it("should find an empty array to differ", function () {
expect(comp([ 1 ], [ ])).toBeFalsy();
expect(comp([ 1, 2 ], [ ])).toBeFalsy();
});
it("should differ between arrays of numbers and strings", function () {
expect(comp([ "aa" ], [ 1, 2 ])).toBeFalsy();
expect(comp([ 1, 2 ], [ "a", "b" ])).toBeFalsy();
});
it("should found any array element not matching pattern", function () {
expect(comp([ "aa" ], [ "a", 1 ])).toBeFalsy();
expect(comp([ "aa" ], [ 1, "a" ])).toBeFalsy();
expect(comp([ 1, 2 ], [ 1, 2, "a" ])).toBeFalsy();
expect(comp([ 1, 2 ], [ 1, "a", 2 ])).toBeFalsy();
expect(comp([ 1, 2 ], [ "a", 1, 2 ])).toBeFalsy();
});
it("should throw on inconsistent pattern array", function () {
expect(prepare([ 1, "a" ], [ ])).toThrow();
expect(prepare([ 1, "a" ], [ 2 ])).toThrow();
expect(prepare([ "a", 1 ], [ ])).toThrow();
expect(prepare([ "a", 1 ], [ 2 ])).toThrow();
expect(prepare([ "a", { } ], [ ])).toThrow();
expect(prepare([ { }, "a" ], [ 2 ])).toThrow();
expect(prepare([ 1, 2, 3, "a" ], [ ])).toThrow();
expect(prepare([ 1, 2, 3, "a", 4 ], [ ])).toThrow();
});
it("should find equal objects with values of same structure", function () {
expect(comp({ a: 1 }, { a: 2})).toBeTruthy();
expect(comp({ a: 'a', b: 2 }, { a: 'b', b: 3})).toBeTruthy();
expect(
comp(
{ a: 'a', b: 'a', c: [1, 2] },
{ a: 'b', b: 'b', c: [3, 4]}
)
).toBeTruthy();
});
it("should differ objects with values of different structure", function () {
expect(comp({ a: 1 }, { a: 'a'})).toBeFalsy();
expect(comp({ a: 'a', b: 'a' }, { a: 'b', b: 3})).toBeFalsy();
expect(
comp(
{ a: 'a', b: 'a', c: [1, 2] },
{ a: 'b', b: 'b', c: ['a', 'b']}
)
).toBeFalsy();
});
it("should compare complex objects", function () {
expect(
prepare(
{ a: 'a', b: 'a', c: [1, 2, { a: 1} ] },
{ a: 'b', b: 'b', c: ['a', 'b']}
)
).toThrow();
expect(
comp(
{ a: 'a', b: 'a', c: [1, 2] },
{ a: 'b', b: { d: 'a' }, c: [1, 4]}
)
).toBeFalsy();
expect(
comp(
{ a: 'a', b: { d: 'b' }, c: [1, 2] },
{ a: 'b', b: { d: 'a' }, c: [1, 4]}
)
).toBeTruthy();
expect(
comp(
{ a: 'a', b: { d: 'b', e: ['a'] }, c: [1, 2] },
{ a: 'b', b: { d: 'a', e: ['a', 'b', 'c'] }, c: [1, 4]}
)
).toBeTruthy();
expect(
comp(
{ a: 'a', b: { d: 'b', e: ['a'] }, c: [1, 2] },
{ a: 'b', b: { d: 'a', e: [] }, c: [ 3 ]}
)
).toBeFalsy();
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment