json-predicate
v0.9.5
Published
Javascript implementation of JSON predicate (by Snell)
Downloads
2,305
Readme
json-predicate
Check/Test/Validate if a block of JSON meets criteria defined by another block of JSON. This is a javascript implementation of the JSON Predicate (Snell) spec.
One likely use of this would be for passing data validations from the back-end to the front-end in a programmatic way. If your back-end is validating incoming data on an API, then it may be useful to send those same validations to the front-end so forms can be checked proactively for the user. The JSON Predicate spec defines the interchange, and this library takes care of reading those validations on the front-end. Now you just need a way to have your back-end serialize out your validations in the JSON predicate format. (Good luck with that!)
Please note that this library does not deal directly with JSON strings, themselves. Instead, it is assumed that the JSON has already been parsed into regular Javascript objects/arrays/expressions.
Given that this library deals with Javascript data (and not the actual JSON strings), it adds a few extra capabilities when it comes to type checking and regular expression matching.
Installation
node:
$ npm install json-predicate
Usage
var jsonPredicate = require('json-predicate');
var jsonTest = jsonPredicate.test;
var passesPredicate = jsonTest(inputData, predicate);
// --> True/False
Operations
- First Order Predicates
- Second Order Predicates
contains
Check if the string at path
includes the provided substring.
Add ignore_case: true
to make the check case-insensitive.
var data = {
a: {
b: 'Smart People on Ice!'
}
};
var predicate = {
op: 'contains',
path: '/a/b',
value: 'People'
}
jsonTest(data, predicate); // true
predicate = {
op: 'contains',
path: '/a/b',
value: 'sMaRt PeOpLe',
ignore_case: true
};
jsonTest(data, predicate); // true
defined
Check if the key at path
exists (is not undefined).
var data = {
a: {
b: 'You\'ve seen him too?',
c: null
}
};
var predicate = {
op: 'defined',
path: '/a/b'
}
jsonTest(data, predicate); // true
var predicate = {
op: 'defined',
path: '/a/c'
}
jsonTest(data, predicate); // true
predicate = {
op: 'defined',
path: '/a/z'
}
jsonTest(data, predicate); // false
ends
Check if the string at path
ends with the provided substring.
Add ignore_case: true
to make the check case-insensitive.
var data = {
a: {
b: 'Smart People on Ice!'
}
};
var predicate = {
op: 'ends',
path: '/a/b',
value: ' Ice!'
}
jsonTest(data, predicate); // true
predicate = {
op: 'ends',
path: '/a/b',
value: 'On ICE!',
ignore_case: true
};
jsonTest(data, predicate); // true
in
Check if the value at path
is included in the provided value array. Compares objects
deeply (using lodash _.isEqual under the
hood). ignore_case:true
can be passed to allow mismatched strings.
var data = {
firstName: 'Mitch',
lastName: 'Taylor'
}
var predicate = {
op: 'in',
path: '/firstName',
value: ['chris', 'mitch', 'kent', 'jordan'],
ignore_case: true
}
jsonTest(data, predicate); // true
var data = {
title: 'Everything',
host: {
firstName: 'Jerry',
lastName: 'Hathaway'
}
};
var predicate = {
op: 'in',
path: '/host',
value: [
{firstName: 'Jerry', lastName: 'Hathaway'},
{firstName: 'Albert', lastName: 'Einstein'}
]
}
jsonTest(data, predicate); // true
less
Check if the numeric value at path
is less than the provided value.
Returns false if either the value at path
or the predicate value is
non-numeric.
var data = {
a: 1984
}
var predicate = {
op: 'less',
path: '/a',
value: 1985
}
jsonTest(data, predicate); // true
matches
Check if the string value at path
satisfies the provided regex. The
predicate value can be given directly as a regex, or as a string to be turned
into a regex. ignore_case:true
can also be added to the predicate to make the
regex test case-insensitive.
var data = {
laser: {
e: '1x10^6J/l' // "... That's hotter than the sun!"
}
}
var predicate = {
op: 'matches',
path: '/laser/e',
value: '[1-9x^]*J\/l' // pass regex as string
}
jsonTest(data, predicate); // true
var predicate = {
op: 'matches',
path: '/laser/e',
value: /[1-9x^]*J\/l/ // or pass regex directly
}
jsonTest(data, predicate); // true
more
Check if the numeric value at path
is more than the provided value.
Returns false if either the value at path
or the predicate value is
non-numeric.
var data = {
a: 1984
}
var predicate = {
op: 'more',
path: '/a',
value: 1983
}
jsonTest(data, predicate); // true
starts
Check if the string at path
starts with the provided substring.
Add ignore_case: true
to make the check case-insensitive.
var data = {
a: {
b: 'Smart People on Ice!'
}
};
var predicate = {
op: 'starts',
path: '/a/b',
value: 'Smart People'
}
jsonTest(data, predicate); // true
predicate = {
op: 'starts',
path: '/a/b',
value: 'sMaRt',
ignore_case: true
};
jsonTest(data, predicate); // true
test
Check if the value at path
is equal to the provided value. Compares objects
deeply (using lodash _.isEqual under the
hood). ignore_case:true
can be passed to allow mismatched strings.
// Match strings:
var data = {
firstName: 'Mitch',
lastName: 'Taylor'
}
var predicate = {
op: 'test',
path: '/firstName',
value: 'mitch',
ignore_case: true
}
jsonTest(data, predicate); // true
// Match objects:
var data = {
title: 'Everything',
host: {
firstName: 'Jerry',
lastName: 'Hathaway'
}
};
var predicate = {
op: 'test',
path: '/host',
value: {firstName: 'Jerry', lastName: 'Hathaway'}
}
jsonTest(data, predicate); // true
// Match numbers:
var data = {
laser: {
wavelength: 600,
unit: 'nm'
}
}
var predicate = {
op: 'test',
path: '/laser/wavelength',
value: 600
}
jsonTest(data, predicate); // true
type
Check if the key at path
is of the type given in the predicate value.
Types can be:
number
string
boolean
object
array
null
undefined
date
(A string conforming to RFC3339 'full-date' spec)date-time
(A string conforming to RFC3339 'date-time' spec)time
(A string conforming to RFC3339 'full-time' spec)lang
(A string conforming to RFC4646 'Language-Tag' spec)lang-range
(A string conforming to RFC4647 'language-range')iri
(A string conforming to RFC3987 'IRI-reference' spec)absolute-iri
(A string conforming to RFC3987 'IRI' spec)
Note: date, date-time, time string matching is based on the following regular expressions. They may not exactly match the RFC specs. I invite pull requests or recommendations for libraries to depend on.
- date:
/^\d{4}-\d{2}-\d{2}$/
- date-time:
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?((?:[\+\-]\d{2}:\d{2})|Z)$/
- time:
/^\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?((?:[\+\-]\d{2}:\d{2})|Z)$/
Note: lang and lang-range string matching is based on the following regular expressions. They may not exactly match RFC specs. I invite pull requests or recommendations for libraries to depend on.
- lang:
/^[a-z]{2,3}(?:-[A-Z]{2,3}(?:-[a-zA-Z]{4})?)?$/
- lang-range:
/^\*|[A-Z]{1,8}(?:-[\*A-Za-z0-9]{1,8})?$/
Note: iri and absolute-iri string matching is based on the valid-url library. With
validUrl.isUri()
being used for'iri'
type, andvalidUrl.isWebUri()
being used for'absolute-iri'
. This is almost certainly not the correct behavior with respect to RFC3987 and the JSON-predicate spec. In invite pull requests or recommendations to improve this.
var data = {
num: 23,
str: "little pickles",
bool: true,
obj: { firstName: "Lazlo", lastName: "Hollyfeld" },
arr: ['tracking system', 'large spinning mirror'],
nil: null,
d: '1985-08-07',
dt: '1985-08-07T19:00:00Z',
t: '19:00:00-05:00',
l: 'en-US',
lr: 'CH-*',
iri: 'https://github.com/MalcolmDwyer/json-predicate#type',
absIri: 'https://github.com/MalcolmDwyer/json-predicate'
}
jsonTest(data, {op:'type', path:'/num', value:'number'}); // true
jsonTest(data, {op:'type', path:'/str', value:'string'}); // true
jsonTest(data, {op:'type', path:'/bool', value:'boolean'}); // true
jsonTest(data, {op:'type', path:'/obj', value:'object'}); // true
jsonTest(data, {op:'type', path:'/arr', value:'array'}); // true
jsonTest(data, {op:'type', path:'/nil', value:'null'}); // true
jsonTest(data, {op:'type', path:'/not_a_key', value:'undefined'}); // true
jsonTest(data, {op:'type', path:'/d', value:'date'}); // true
jsonTest(data, {op:'type', path:'/dt', value:'date-time'}); // true
jsonTest(data, {op:'type', path:'/t', value:'time'}); // true
jsonTest(data, {op:'type', path:'/l', value:'lang'}); // true
jsonTest(data, {op:'type', path:'/lr', value:'lang-range'}); // true
jsonTest(data, {op:'type', path:'/iri', value:'iri'}); // true
jsonTest(data, {op:'type', path:'/absIri', value:'absolute-iri'}); // true
undefined
Check if the key at path
does not exist (is undefined).
var data = {
a: {
b: 'You\'ve seen him too?',
c: null
}
};
predicate = {
op: 'undefined',
path: '/a/z'
}
jsonTest(data, predicate); // true
var predicate = {
op: 'undefined',
path: '/a/b'
}
jsonTest(data, predicate); // false
var predicate = {
op: 'undefined',
path: '/a/c'
}
jsonTest(data, predicate); // false
contained
Note: This operation is not included in the Snell json-predicate spec. If you are sharing predicates with other implementations, you should avoid using this op.
Check if the key at path
(which must be an array) contains the value given. (Inverse of in
, which has the array in the predicate and the single value in the data). Can compare strings, numbers, shallow and deep objects. Operation honors the ignore_case parameter when comparing string values.
var data = {
a: {
b: [1983, 1984, 1985]
}
}
var predicate = {
op: 'contained',
path: '/a/b',
value: 1984
}
jsonTest(data, predicate); // true
intersects
Note: This operation is not included in the Snell json-predicate spec. If you are sharing predicates with other implementations, you should avoid using this op.
Check if the key at path
(which must be an array) has any matching values in common with the provided array data (which also must be an array). As long as one or more elements in the two arrays match (numeric, string, shallow or deep object comparisons, etc.), then the test will return true. If either value is not an array, or if no array elements are found in common, then it returns false. Operation honors the ignore_case parameter for string comparisons.
var data = {
a: {
b: [1983, 1984, 1985]
}
}
var predicate = {
op: 'contained',
path: '/a/b',
value: [1984, 1988, 1992]
}
jsonTest(data, predicate); // true
and
Check if two or more sub-predicates are true.
var data = {
a: {
b: 'Much further, much faster!',
c: 'I don\'t know; I found it in one of the labs.'
}
}
predicate = {
op: 'and',
apply: [
{
op: 'contains',
path: '/a/b',
value: 'faster!'
},
{
op: 'contains',
path: '/a/c',
value: 'labs'
}
]
}
jsonTest(data, predicate); // true
Paths can also be defined in layers:
var data = {
a: {
b: 'Much further, much faster!',
c: 'I don\'t know; I found it in one of the labs.'
}
}
predicate = {
op: 'and',
path: '/a',
apply: [
{
op: 'contains',
path: '/b',
value: 'faster!'
},
{
op: 'contains',
path: '/c',
value: 'labs'
}
]
}
jsonTest(data, predicate); // true
or
Check if any of two or more sub-predicates are true.
var data = {
a: {
b: 'Much further, much faster!',
c: 'I don\'t know; I found it in one of the labs.'
}
}
predicate = {
op: 'or',
apply: [
{
op: 'contains',
path: '/a/b',
value: 'faster!' // true
},
{
op: 'contains',
path: '/a/c',
value: 'Get out, Lightman!' // false
}
]
}
jsonTest(data, predicate); // true
predicate = {
op: 'or',
apply: [
{
op: 'contains',
path: '/a/b',
value: 'Protovision, I have you now!' // false
},
{
op: 'contains',
path: '/a/c',
value: 'Get out, Lightman!' // false
}
]
}
jsonTest(data, predicate); // false
Paths can also be defined in layers:
var data = {
a: {
b: 'Much further, much faster!',
c: 'I don\'t know; I found it in one of the labs.'
}
}
predicate = {
op: 'or',
path: '/a',
apply: [
{
op: 'contains',
path: '/b',
value: 'faster!' // true
},
{
op: 'contains',
path: '/c',
value: 'Mr. Potatohead!' // false
}
]
}
jsonTest(data, predicate); // true
not
Check if two or more sub-predicates are all false. Equivalent to a logical NOR operation.
var data = {
a: {
b: 'Much further, much faster!',
c: 'I don\'t know; I found it in one of the labs.'
}
}
predicate = {
op: 'not',
apply: [
{
op: 'contains',
path: '/a/b',
value: 'Joshua, what are you doing?' // false
},
{
op: 'contains',
path: '/a/c',
value: 'Hi, Lightman!' // false
}
]
}
jsonTest(data, predicate); // true
Paths can also be defined in layers:
var data = {
a: {
b: 'Much further, much faster!',
c: 'I don\'t know; I found it in one of the labs.'
}
}
predicate = {
op: 'not',
path: '/a',
apply: [
{
op: 'contains',
path: '/b',
value: 'Joshua, what are you doing?' // false
},
{
op: 'contains',
path: '/c',
value: 'Hi, Lightman!' // false
}
]
}
jsonTest(data, predicate); // true