Skip to content

Latest commit

 

History

History
831 lines (597 loc) · 18.4 KB

SYNTAX.md

File metadata and controls

831 lines (597 loc) · 18.4 KB

Syntax

The following is a specification of the supported syntax.

Table of contents:

About

Layers

The specifiation is split into several layers.

[L1] Base syntax

Layer 1 (L1) is the default and defines the base syntax that must be supported.

[L2] Folded syntax

Layer 2 (L2) is a folded syntax that is more convenient to produce when matching multiple keys (in particular when the filters are not machine-produced).

This syntax can be transformed into the more explicit base syntax (unfolding). The folded syntax is often assumed to be easier to produce and consume. However, it often turns out to be limited and/or ambiguous when it comes to more complex nested filters.

An implementation of this specification is expected to implement this syntax (though not strictly demanded).

[L3] Custom extension

Layer 3 (L3) defines custom extension points. An implementation may choose to extend the mandated base syntax by providing custom extensions in these situations.

Example data

This document pro

[
    { "id" : 100, "name" : "Test", "age" : 20 },
    { "id" : 200, "name" : "Peter", "age" : 25 }
]

Filter Object

The core of this specification is that each and every filter is always expressed as simple JSON object like this:

{
}

Additional keys should be added to this filter object as per the following specification.

Basic matching

The basic syntax for matching the value of a key to a given value always looks like this:

{
    "key" : { "$comparator" : value }
}

The possible comparators (among with examples) are defined in the comparator section.

Nested keys

Nested keys are supported using dot notation like this:

{
    "name.first" : { "$comparator" : value }
}

This filter matches every object that has a "name" sub-object where the value of the key "first" matches the comparator.

[L2] Literal dot

In the case that your object keys actually include a dot in the name, you can use the \ prefix to escape the dot. Because the backslash has to be escaped by another backslash, the resulting filter looks like this:

{
    "dotted\\.key" : { "$comparator" : value }
}

Missing keys

Accessing the value of a key that does not exist will always yield a null value.

{
    "unknown" : { "$comparator" : value }
}

Will evaluate as if the key had a null value if the key "unknown" does not exist. This will check if the value null matches against the value of the comparator.

If you need to tell a non-existant key apart from a key that actually holds a null value, you can use the $contains comparator (object) as described in the section about root matching.

Negation

Every comparator can be negated by prefixing it with ! like this:

{
    "key" : { "!$comparator" : value }
}

This filter matches every object where the given comparator does NOT match the given value.

[L2] Double negation

Prefixing every comparator with a ! results in a negated comparator. Because of this, it's legal to double-negate comparators like this:

{
    "key" : { "!!!$comparator" : value }
}

Double-negation is effectively a NO-OP. Because of this, the above example is equivalent to this:

{
    "key" : { "!$comparator" : value }
}

Root matching

Despite matching keys within an object, one can also apply operators to the root object like this:

{
    "$comparator" : value
}

This filter matches if the object matches the comparator value. This is often used with the $contains comparator to check if a given key exists:

{
    "$contains" : "unknown"
}

Matches if the given key "unknown" exists within the root object.

[L2] Simple Matching

[L2] Matching scalar

This convenient shortcut syntax allows one to leave out the $is comparator for scalar values to compare against.

{
    "id" : 100
}

This is equivalent to the longer form

{
    "id" : {
        "$is" : 100
    }
}

This filter matches every object that has id=100.

[L2] Matching list

This convenient shortcut syntax allows one to leave out the $in comparator for a list of values to compare against.

{
    "id" : [
        100,
        200,
        300
    ]
}

This is equivalent to the longer form

{
    "id" : {
        "$in" : [
            100,
            200,
            300
        ]
    }
}

This filter matches every object that has either of id=100 OR id=200 OR id=300.

An empty list will never match.

[L2] Multiple matching

[L2] Matching multiple keys

{
    "id" : 100,
    "name" : "Test"
}

Matches every object that has both id=100 AND name=Test.

The same matching and comparator rules as above apply.

See also following chapter about combinators. The above example is a shorthand syntax for the following:

{
    "$and" : [
        {
            "id" : 100
        },
        {
            "name" : "Test"
        }
    ]
}

Because of these unfolding rules, an empty object will always match.

[L2] Matching multiple operators

{
    "age" : {
        "$gte" : 20,
        "$lte" : 30
    }
}

Matches every object that has both age>=20 AND age<=30.

See also following chapter about combinators. The above example is a shorthand syntax for the following:

{
    "$and" : [
        {
            "age" : { "$gte" : 20 }
        },
        {
            "age" : { "$lte" : 30 }
        }
    ]
}

Because of these unfolding rules, an empty object will always match.

Operators

This query language supports two classes of operators further described below:

Comparators

You can use any of the following comparison operators (comparators)

$is comparator

The $is comparator checks if the value of the key is strictly equal (type and value) to the given value.

{ "id" : { "$is" : 100 } }

This filter matches every object that has id=100.

Note that this comparators checks for strictly equal values and types.

{ "id" : { "$is" : "100" } }

This filter matches every object that has an id attribute of type string and id=100. In the above example, this does not match any object, because id is always of type number.

If you want to support multiple types, consider using the $in comparator and explicitly list all possible types.

$in comparator

The $in comparator checks if the value of the key is "in" (either of) the given list of values.

{ "id" : { "$in" : [ 100, 101, 102 ] } }

This filter matches every object that has either of id=100 OR id=101 OR id=102.

Note that this comparator checks for strictly equal values and types, just like the $is comparator.

{ "id" : { "$in" : [ "100", "101" ] } }

This filter matches every object that has an id attribute of type string and a value of either id=100 or id=101. In the above example, this does not match any object, because id is always of type number.

If you want to support multiple types, you can explicitly list all possible types like this:

{ "registered" : { "$in" : [ false, 0, null ] } }

This matches every object that has either of registered=false OR registered=0 or registered=null.

An empty list will never match.

This comparator exclusively accepts an array of possible values, passing anything else (e.g. single scalar or object etc.) is a syntax error.

This is not to be confused with the $contains comparator (array) which works the other way around: It checks if an array key value contains a single expected value.

$contains comparator

The $contains comparator checks if the given value is contained in the value of the key.

{ "key" : { "$contains" : value } }

This can be used for three things:

  • Check if a string/scalar key value contains the given substring value
  • Check if an array key value contains the given value element
  • Check if an object key value contains the given value key

This comparator expects a single possible value to match against. Passing an array of values will literally check for this array as a single value. If you want to accept multiple values, see below for the combinators.

$contains comparator (scalar)
{ "name" : { "$contains" : "ter" } }

This filter matches if the "name" string contains the substring "ter" (case-sensitive matching).

$contains comparator (array)
{ "tags" : { "$contains" : "new" } }

This filter matches if the "tags" array contains an element with the value "new".

This is not to be confused with the $in comparator which works the other way around: It checks if a key value is contained in a list of expected values.

$contains comparator (object)
{ "location" : { "$contains" : "name" } }

This filter matches if the "location" object contains a key with the name "name".

The $contains comparator can also be used to check if a key exists in the root object.

{
    "$contains" : "unknown"
}

See also the above section about root matching and the difference between matching missing keys.

Checking for nested keys is currently not supported (see issue #14). If you want to check if key "a.b.c" exists, you can use the following workaround:

{
    "a.b" : {
        "$contains" : "c"
    }
}

$lt comparator

The $lt comparator checks if the value of the key is "less than" the given value.

{ "id" : { "$lt" : 100 } }

This filter matches every object that has an id of less than 100, i.e. it matches 99, but does not match 100.

$lte comparator

The $lte comparator checks if the value of the key is "less than or equal to" the given value.

{ "id" : { "$lte" : 100 } }

This filter matches every object that has an id of less than or equal to 100, i.e. it matches 99, it matches 100 but does not match 101.

$gt comparator

The $gt comparator checks if the value of the key is "greater than" the given value.

{ "id" : { "$gt" : 100 } }

This filter matches every object that has an id of greater than 100, i.e. it matches 101, but does not match 100.

$gte comparator

The $gte comparator checks if the value of the key is "greater than or equal to" the given value.

{ "id" : { "$gte" : 100 } }

This filter matches every object that has an id of greater than or equal to 100, i.e. it matches 101, it matches 100, but does not match 99.

[L2] $not comparator

The $not comparator is a shorthand for negated matching.

It accepts either a scalar value or a list of values. Every other type (e.g. object etc.) is a syntax error.

[L2] Not scalar

The $not comparator can be used for scalar values like this:

{ "id" : { "$not" : 100 } }

The above example can also be written explicitly like this:

{ "id" : { "!$is" : 100 } }

This filter matches every object that does NOT have id=100.

[L2] Not list

The $not comparator can be used for lists like this:

{ "id" : { "$not" : [ 100, 200 ] } }

The above example can also be written explicitly like this:

{ "id" : { "!$in" : [ 100, 200 ] } }

This filter matches every object that does NOT have (id=100 OR id=200).

[L3] Additional comparators

An implementation may choose to define additional custom operators like $regex, $starts, $ends and others.

[L3] Common comparators

An implementation may choose to define some of the common operators as a fallback:

{ "id" : { ">=" : 100 } }
{ "id" : { "$gt" : 100 } }

Combinators

Combinators allow one to combine multiple filters to a bigger filter rule.

$and combinator

$and list

Expects a list of filters like this:

{ "$and" : [ filter, … ] }

Only matches if each and every of the given filters in the list do match. Does not match if any of the given filters does not match.

{
    "$and" : [
        {
            "id" : 100
        },
        {
            "name" : "Test"
        }
    ]
}

An empty $and list will always match. Providing only a single filter expression is supported.

[L2] $and object

Also accepts a folded L2 object like this:

{
    "$and" : {
        "id" : 100,
        "name" : "Test"
    }
}

Matches every object that has both id=100 AND name=Test.

The above example can be unfolded to the explicit L1 list form:

{
    "$and" : [
        {
            "id" : 100
        },
        {
            "name" : "Test"
        }
    ]
}

Because of these unfolding rules, an empty object will always match.

$or combinator

$or list

Expects a list of filters like this:

{ "$or" : [ filter, … ] }

Matches it one of the given filters in the list matches. Does not match if none of the given filters in the list match.

An empty $or list will never match. Providing only a single filter expression is supported.

[L2] $or object

Also accepts a folded L2 object like this:

{
    "$or" : {
        "id" : 100,
        "name" : "Test"
    }
}

Matches every object that has either id=100 OR name=Test.

The above example can be unfolded to the explicit L1 list form:

{
    "$or" : [
        {
            "id" : 100
        },
        {
            "name" : "Test"
        }
    ]
}

Because of these unfolding rules, an empty object will never match.

[L2] $not combinator

Negating combinators is achieved by prefixing them with !. The $not combinator is a convenience shorthand for !$and.

[L2] $not list

Expects a list of filter like this:

{
    "$not" : [ filter, filter ]
}

Only matches if one of the given filters in the list does NOT match. Does not match if each and every of the given filters does match.

The above example can also be written like this:

{
    "!$and" : [ filter, filter ]
}

Because of these unfolding rules, an empty list will never match.

L2 $not object

Can also be used with a filter object like this:

{
    "$not" : filter
}

Only matches if the filter does not match. Does not match if the filter matches.

Accepts an object like this:

{
    "$not" : {
        "id" : {
            "$is" : 100
        }
    }
}

Matches every object that does NOT have id=100.

The above example can also be written like this:

{
    "!$and" : {
        "id" : {
            "$is" : 100
        }
    }
}

This is equivalent to negated matching like this:

{
    "id" : {
        "!$is" : 100
    }
}

Also accepts a folded L2 object like this:

{
    "$not" : {
        "id" : 100,
        "name" : "Test"
    }
}

Matches every object that does NOT have (id=100 AND name=Test).

The above example can be unfolded to the explicit L1 basic matching form:

{
    "$not" : {
        "id" : {
            "$is" : 100
        },
        "name" : {
            "$is" : "Test"
        }
    }
}

Which can then be transformed to the explicit negation form:

{
    "!$and" : {
        "id" : {
            "$is" : 100
        },
        "name" : {
            "$is" : "Test"
        }
    }
}

Due to De Morgan's laws this is equivalent to:

{
    "$or" : {
        "id" : {
            "!$is" : 100
        },
        "name" : {
            "!$is" : "Test"
        }
    }
}

Because of these unfolding rules, an empty object will never match.

[L3] Additional combinators

An implementation may choose to implement additional combinators like $xor, $xnor and others.

[L3] Common combinators

An implementation may choose to implement some common combinators.

A common alias of the L2 $not combinator:

{ "$nand" : filters }
{ "!$and" : filters }

Or the disjunctive equivalent of the L2 $not combinator:

{ "$nor" : filters }
{ "!$or" : filters }

Or common names for the negated possible L3 $xor combinator:

{ "$xnor" : filters }
{ "!$xor" : filters }

Nesting

Nesting filters is supported.