Object Pattern structures for JavaScript
ISC License
Object Pattern structures for JavaScript.
You can try them out on the Object Pattern playground
object-pattern
provides a fast and complete analog of Regular Expressions
but aimed to describe generic object structures instead of strings.
What was in my mind while creating this? Simple: creating EventEmitters that support listening to object structures as the event descriptions.
A prime example of when this would be useful are REST interfaces.
object-pattern
could be used as a drop-in replacement of regular
endpoints in a Express.js or
Sinatra like routing framework.
From NPM:
npm install --save object-pattern
From Bower:
bower install --save object-pattern
object-pattern
supports CommonJS, AMD and globals, so feel free to require
it as best fits you. There is also a minified version in object-pattern.min.js
.
The most straightforward way of using object-pattern
is with the notation
language (Object Pattern Notation):
var OP = require("object-pattern");
var pattern = OP.parse("property:'value'");
pattern.match({ property: "value" }); // => true!
Exactly like Regular Expressions, Object Patterns match if the target object contains at the very least the structure described. If you do:
var OP = require("object-pattern");
var pattern = OP.parse("property:'value'");
pattern.match({
property: "value",
something: ["extra", { and: "complex" } ]
}); // => true!
...it will still return true
since the property property
with value
"value"
exists in the object.
You can also use wildcards for the value:
var OP = require("object-pattern");
var pattern = OP.parse("property:*");
pattern.match({ property: 50 }); // => true
pattern.match({ property: "else" }); // => true
pattern.match({ property: false }); // => true
pattern.match({ property: { structure: true } }); // => still true!
...and also wildcards for the property name:
var OP = require("object-pattern");
var pattern = OP.parse("*:value");
pattern.match({ anything: "value" }); // => true
You can make sure that a property with some value doesn't exist:
var OP = require("object-pattern");
var pattern = OP.parse('!property:value');
pattern.match({ property: "value" }); // => false
You can also make sure that the value is of a certain JSON type:
var OP = require("object-pattern");
var pattern = OP.parse('property:<number>');
pattern.match({ property: 20 }); // => true
pattern.match({ property: -7.20 }); // => true
pattern.match({ property: "-7.20" }); // => false
There are also array patterns, both standalone:
var OP = require("object-pattern");
var pattern = OP.parse('/user/<number>');
pattern.match(["user", 50]); // => true
...and within an object structure:
var OP = require("object-pattern");
var pattern = OP.parse('method:GET,path:/articles/*');
pattern.match({
method: "GET",
path: ["articles", "rest-is-awesome"]
}); // => true
There are also ellipsis, to match any amount of items in an array.
var OP = require("object-pattern");
var pattern = OP.parse('/**/saturn');
pattern.match(["planets", "saturn"]); // => true
pattern.match(["planets", 6, "many-moons", "saturn"]); // => true
pattern.match(["saturn"]); // => true
Ellipsis matchers are non-greedy.
var OP = require("object-pattern");
var pattern = OP.parse('/**/saturn/**/moons');
pattern.match(["saturn", 62, "moons"]); // => true
pattern.match(["saturn", 62, "moons", "saturn", 62, "moons"]); // => false
Object and array structures can be nested:
var OP = require("object-pattern");
var pattern = OP.parse('userAgent:(os:"linux",browser:"firefox")');
pattern.match({
userAgent: {
os: "linux",
browser: "firefox",
device: "tablet"
}
}); // => true
var OP = require("object-pattern");
var pattern = OP.parse('/[/users/*]/[/articles/*]');
pattern.match([
["users", 60],
["articles", 70]
]); // => true
Once you have the pattern structure, you can stringify it again into OPN:
var OP = require("object-pattern");
var pattern = OP.parse('some:structure,withTypes:<number>');
pattern.toString(); // => "some:'structure',withTypes:<number>"
Keep reading for a complete reference of the object-pattern
types (the
structures into which the OPN string compiles while parsed) and how to
interact with them low-level.
Most of the object-pattern
structure looks for descendants of Matchable
or ArrayMatchable
to delegate matching operations. You can create your
own object matching structures by inheriting from those functions and
assembling the pattern structure yourself. The only drawback is that it's
not going to be available to be parsed from the OPN, but that could be OK
in many cases (and you can always pull request if you think your
extension should be standard).
All of this structures are accessible from the main object. To create an
ObjectPattern
structure from scratch, you coud do, for example:
var OP = require("object-pattern");
new OP.ObjectPattern(
new OP.ExactProperty("property", "value")
);
A common parent for all matchables. The interface that they are supposed to
implement (although Matchable
itself does not) is to expose a match
method that returns either true
or false
.
Returns true
if the value of any property is ===
to the assigned value.
false
otherwise. If initialized with an inheritor of Matchable
it will
forward the match
to the matchable instead.
Usage:
// Static value property
var wildcardProperty = new WildcardProperty("public"):
wildcardProperty.match({"project": "public"}); // => true
// Matchable
var matchable = new Matchable();
matchable.match = function () { return true } ;
wildcardProperty = new WildcardProperty(matchable);
wildcardProperty.match({"property": "value"}); // => true
Returns true
if there is a property with the given name which value is
===
to the assigned value. false
otherwise.
If initialized with an inheritor of Matchable
it will
forward the match
to the matchable, if the property exists.
Usage:
// Static value property
var exactProperty = new ExactProperty("project", "public"):
exactProperty.match({"project": "public"}); // => true
// Matchable
var matchable = new Matchable();
matchable.match = function () { return true } ;
exactProperty = new ExactProperty("property", matchable);
exactProperty.match({"property": "value"}); // => true
// Matchable but property missing
var matchable = new Matchable();
matchable.match = function () { return true };
exactProperty = new ExactProperty("project", matchable);
exactProperty.match({"property": "value"}); // => false
Delegates the matching to the sent matchable and negates the result.
Usage:
var matchable = new Matchable();
matchable.match = function () {
return true;
}
var negator = new Negator(matchable);
negator.match({"here": "ignored"}); // => false
Returns the &&
result of calling the match
method in each properties
,
forwarding the argument.
Usage:
var property = new ObjectPattern(
new ExactProperty("public", true),
new WildcardProperty("value"),
new ExactProperty("timestamp", 123456789)
)
property.match({
"public": true,
"anyProp": "value",
"timestamp": 123456789
}) // => true
Returns always true
except if the argument is undefined
.
Usage:
var wildcardValue = new WildcardValue();
wildcardValue.match("something"); // => true
If initialized with a Function
, returns true
only if the argument if
instanceof
the Function
.
If initialized with the following String
arguments, it returns true
:
number
literal.string
literal.array
literal.object
literal.true
or false
Usage:
var Type = function () {};
var typedValue = new TypedValue(Type);
typedValue.match(new Type()) // => true
Handles ArrayMatchable
s, combining their results to return a final
Boolean
value representing whether the Array
was or not a match.
Usage:
var arrayMatcher = new ArrayPattern(
new TypedValue( 'number' ),
'user',
new WildcardValue(),
new ArrayEllipsis( 9 )
);
arrayMatcher.match([6, 'user', 9]); // => false
arrayMatcher.match([-56.2, 'user', 'extra', 9]); // => true
A common parent for all descriptors of Array
components. ArrayMatchable
s
have a slightly different interface than regular Matchable
s because they
need to send back the chunk of the Array that wasn't consumed by the current
pattern so that the ArrayPattern
can forward it to the next
ArrayMatchable
.
The ArrayEllipsis
represents a variable length pattern, and it's behavior
depends on how it is configured.
ArrayEllipsis
will create a catch allMatchable
to the ArrayEllipsis
will cause it toMatchable
. ThatMatchable
is called the termination of the ellipsis pattern. If aArrayEllipsis
will stop, return true
in matched
Array
in unmatched
.Usage:
var arrayEllipsis = new ArrayEllipsis();
var result = arrayEllipsis.match(['element', 2, {}]);
result.matched; // => true
result.unmatched; // => []
With termination:
var arrayEllipsis = new ArrayEllipsis(new TypedValue('string'));
var result = arrayEllipsis.match([2, 4, 'text', 'extra']);
result.matched; // => true
result.unmatched; // => ['extra']
Parses an OPN (Object Pattern Notation) string and returns the corresponding pattern structure.
Usage:
var parse = require("object-pattern").parse
var pattern = parse("name:*,age:<number>")
pattern.match({
name: "Alex",
age: 24
}) // => true
For more examples please refer to the OPN examples
Parses an endpoint constructed as a JavaScript object and returns the corresponding pattern structure.
Usage:
var parseObject = require("object-pattern").parseObject
var pattern = parse({ name: "*", age: "<number>"})
pattern.match({
name: "Alex",
age: 24
}) // => true
Copyright 2014 Xavier Via
ISC license.
See LICENSE attached.